Skip to content

Commit

Permalink
Merge branch 'pretty' into subz
Browse files Browse the repository at this point in the history
  • Loading branch information
mdial89f committed Aug 6, 2024
2 parents 8aa528a + 9955ebb commit eeedb56
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 195 deletions.
28 changes: 11 additions & 17 deletions bin/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Argv } from "yargs";
import {
checkIfAuthenticated,
LabeledProcessRunner,
runCommand,
project,
region,
writeUiEnvFile,
Expand All @@ -14,8 +14,6 @@ import {
} from "@aws-sdk/client-cloudfront";
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";

const runner = new LabeledProcessRunner();

export const deploy = {
command: "deploy",
describe: "deploy the project",
Expand All @@ -24,19 +22,15 @@ export const deploy = {
},
handler: async (options: { stage: string; stack?: string }) => {
await checkIfAuthenticated();
await runner.run_command_and_output(
"CDK Deploy",
["cdk", "deploy", "-c", `stage=${options.stage}`, "--all"],
await runCommand(
"cdk",
["deploy", "-c", `stage=${options.stage}`, "--all"],
".",
);

await writeUiEnvFile(options.stage);

await runner.run_command_and_output(
"Build",
["bun", "run", "build"],
"react-app",
);
await runCommand("bun", ["run", "build"], "react-app");

const { s3BucketName, cloudfrontDistributionId } = JSON.parse(
(
Expand All @@ -63,14 +57,14 @@ export const deploy = {
// There's a mime type issue when aws s3 syncing files up
// Empirically, this issue never presents itself if the bucket is cleared just before.
// Until we have a neat way of ensuring correct mime types, we'll remove all files from the bucket.
await runner.run_command_and_output(
"S3 Clean",
["aws", "s3", "rm", `s3://${s3BucketName}/`, "--recursive"],
await runCommand(
"aws",
["s3", "rm", `s3://${s3BucketName}/`, "--recursive"],
".",
);
await runner.run_command_and_output(
"S3 Sync",
["aws", "s3", "sync", buildDir, `s3://${s3BucketName}/`],
await runCommand(
"aws",
["s3", "sync", buildDir, `s3://${s3BucketName}/`],
".",
);

Expand Down
15 changes: 4 additions & 11 deletions bin/cli/src/commands/docs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Argv } from "yargs";
import { LabeledProcessRunner } from "../lib";

const runner = new LabeledProcessRunner();
import { runCommand } from "../lib";

export const docs = {
command: "docs",
Expand All @@ -14,17 +12,12 @@ export const docs = {
default: false,
}),
handler: async ({ stop }: { stop: boolean }) => {
await runner.run_command_and_output(
"Stop any existing container.",
["docker", "rm", "-f", "jekyll"],
"docs",
);
await runCommand("docker", ["rm", "-f", "jekyll"], "docs");

if (!stop) {
await runner.run_command_and_output(
"Run docs at http://localhost:4000",
await runCommand(
"docker",
[
"docker",
"run",
"--rm",
"-i",
Expand Down
16 changes: 3 additions & 13 deletions bin/cli/src/commands/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Argv } from "yargs";
import { checkIfAuthenticated, LabeledProcessRunner } from "../lib";

const runner = new LabeledProcessRunner();
import { checkIfAuthenticated, runCommand } from "../lib";

export const e2e = {
command: "e2e",
Expand All @@ -14,16 +12,8 @@ export const e2e = {
}),
handler: async ({ ui }: { ui: boolean }) => {
await checkIfAuthenticated();
await runner.run_command_and_output(
"Install playwright",
["bun", "playwright", "install", "--with-deps"],
".",
);
await runCommand("bun", ["playwright", "install", "--with-deps"], ".");

await runner.run_command_and_output(
ui ? "e2e:ui tests" : "e2e tests",
["bun", ui ? "e2e:ui" : "e2e"],
".",
);
await runCommand("bun", [ui ? "e2e:ui" : "e2e"], ".");
},
};
11 changes: 2 additions & 9 deletions bin/cli/src/commands/install.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { LabeledProcessRunner } from "../lib";

const runner = new LabeledProcessRunner();
import { runCommand } from "../lib";

export const install = {
command: "install",
describe: "install all project dependencies",
handler: async () => {
await runner.run_command_and_output(
"Install",
["bun", "install"],
".",
true,
);
await runCommand("bun", ["install"], ".");
},
};
10 changes: 4 additions & 6 deletions bin/cli/src/commands/logs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Argv } from "yargs";
import {
checkIfAuthenticated,
LabeledProcessRunner,
runCommand,
project,
region,
setStageFromBranch,
Expand All @@ -20,8 +20,6 @@ import prompts from "prompts";

const lambdaClient = new LambdaClient({ region });

const runner = new LabeledProcessRunner();

export const logs = {
command: "logs",
describe: "Stream a lambda's cloudwatch logs.",
Expand Down Expand Up @@ -79,9 +77,9 @@ export const logs = {
const lambdaLogGroup = await getLambdaLogGroup(lambda);

// Stream the logs
await runner.run_command_and_output(
"stream awslogs",
["awslogs", "get", lambdaLogGroup, "-s10m", "--watch"],
await runCommand(
"awslogs",
["get", lambdaLogGroup, "-s10m", "--watch"],
".",
);
},
Expand Down
10 changes: 2 additions & 8 deletions bin/cli/src/commands/test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Argv } from "yargs";
import { LabeledProcessRunner } from "../lib";

const runner = new LabeledProcessRunner();
import { runCommand } from "../lib";

export const test = {
command: "test",
Expand Down Expand Up @@ -33,10 +31,6 @@ export const test = {
if (argv.ui) {
testCommand = "test:ui";
}
await runner.run_command_and_output(
"Unit Tests",
["bun", "run", testCommand],
".",
);
await runCommand("bun", ["run", testCommand], ".");
},
};
16 changes: 3 additions & 13 deletions bin/cli/src/commands/ui.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { Argv } from "yargs";
import {
checkIfAuthenticated,
LabeledProcessRunner,
runCommand,
setStageFromBranch,
writeUiEnvFile,
} from "../lib";

const runner = new LabeledProcessRunner();

export const ui = {
command: "ui",
describe: "Run react-server locally against an aws backend",
Expand All @@ -18,15 +16,7 @@ export const ui = {
await checkIfAuthenticated();
const stage = options.stage || (await setStageFromBranch());
await writeUiEnvFile(stage, true);
await runner.run_command_and_output(
`Build`,
["bun", "run", "build"],
"react-app",
);
await runner.run_command_and_output(
`Run`,
["bun", "run", "dev"],
`react-app`,
);
await runCommand("bun", ["run", "build"], "react-app");
await runCommand("bun", ["run", "dev"], `react-app`);
},
};
148 changes: 30 additions & 118 deletions bin/cli/src/lib/runner.ts
Original file line number Diff line number Diff line change
@@ -1,126 +1,38 @@
import { spawn } from "child_process";

// LabeledProcessRunner is a command runner that interleaves the output from different
// calls to run_command_and_output each with their own prefix
export class LabeledProcessRunner {
private prefixColors: Record<string, string> = {};
private colors = [
"1",
"2",
"3",
"4",
"5",
"6",
"9",
"10",
"11",
"12",
"13",
"14",
];

// formattedPrefix pads the prefix for a given process so that all prefixes are
// right aligned in your terminal.
private formattedPrefix(prefix: string): string {
let color: string;

if (prefix! in this.prefixColors) {
color = this.prefixColors[prefix];
} else {
const frontColor = this.colors.shift();
if (frontColor != undefined) {
color = frontColor;
this.colors.push(color);
this.prefixColors[prefix] = color;
} else {
throw "dev.ts programming error";
}
}

let maxLength = 0;
for (const pre in this.prefixColors) {
if (pre.length > maxLength) {
maxLength = pre.length;
}
}

return `\x1b[38;5;${color}m ${prefix.padStart(maxLength)}|\x1b[0m`;
import { spawn, SpawnOptions } from "child_process";
import path from "path";

export async function runCommand(
command: string,
args: string[],
cwd: string | null,
): Promise<void> {
// Resolve the full path of the working directory
const fullPath = cwd ? path.resolve(cwd) : null;

// Print the command and arguments
console.log(`Executing command: ${command} ${args.join(" ")}`);
if (fullPath) {
console.log(`Working directory: ${fullPath}`);
}

private sanitizeInput(input) {
// A basic pattern that allows letters, numbers, dashes, underscores, and periods
// Adjust the pattern to fit the expected input format
const sanitizedInput = input.replace(/[^a-zA-Z0-9-_.]/g, "");
return sanitizedInput;
}
return new Promise((resolve, reject) => {
const options: SpawnOptions = fullPath
? { cwd: fullPath, stdio: ["inherit", "inherit", "inherit"] }
: { stdio: ["inherit", "inherit", "inherit"] };

// run_command_and_output runs the given shell command and interleaves its output with all
// other commands run via this method.
//
// prefix: the prefix to display at the start of every line printed by this command
// cmd: an array containing the command and all arguments to the command to be run
// cwd: optional directory to change into before running the command
// returns a promise that errors if the command exits error and resolves on success
async run_command_and_output(
prefix: string,
cmd: string[],
cwd: string | null,
catchAll = false,
silenced: {
open?: boolean;
stdout?: boolean;
stderr?: boolean;
close?: boolean;
} = {},
) {
silenced = {
...{ open: false, stdout: false, stderr: false, close: false },
...silenced,
};
const proc_opts = cwd ? { cwd } : {};
const proc = spawn(command, args, options);

const command = this.sanitizeInput(cmd[0]);
const args = cmd.slice(1);

const proc = spawn(command, args, proc_opts);
const paddedPrefix = `[${prefix}]`;
if (!silenced.open)
process.stdout.write(`${paddedPrefix} Running: ${cmd.join(" ")}\n`);

const handleOutput = (data: Buffer, prefix: string, silenced: boolean) => {
const paddedPrefix = this.formattedPrefix(prefix);
if (!silenced)
for (const line of data.toString().split("\n")) {
process.stdout.write(`${paddedPrefix} ${line}\n`);
}
};

proc.stdout.on("data", (data) =>
handleOutput(data, prefix, silenced.stdout!),
);
proc.stderr.on("data", (data) =>
handleOutput(data, prefix, silenced.stderr!),
);

return new Promise<void>((resolve, reject) => {
proc.on("error", (error) => {
if (!silenced.stderr)
process.stdout.write(`${paddedPrefix} A PROCESS ERROR: ${error}\n`);
reject(error);
});
proc.on("error", (error) => {
console.error(`Error: ${error.message}`);
reject(error);
});

proc.on("close", (code) => {
if (!silenced.close)
process.stdout.write(`${paddedPrefix} Exit: ${code}\n`);
// If there's a failure and we haven't asked to catch all...
if (code != 0 && !catchAll) {
// This is not my area.
// Deploy failures don't get handled and show up here with non zero exit codes
// Here we throw an error. Not sure what's best.
throw `Exit ${code}`;
}
proc.on("close", (code) => {
if (code !== 0) {
reject(new Error(`Command failed with exit code ${code}`));
} else {
resolve();
});
}
});
}
});
}

0 comments on commit eeedb56

Please sign in to comment.