-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reduce our runner as its more than we need, and in so doing make our …
…output cleaner and more colorful, with progress indicators and such
- Loading branch information
Showing
8 changed files
with
52 additions
and
198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"], "."); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,126 +1,28 @@ | ||
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; | ||
import { spawn, SpawnOptions } from "child_process"; | ||
|
||
export async function runCommand( | ||
command: string, | ||
args: string[], | ||
cwd: string | null, | ||
): Promise<void> { | ||
return new Promise((resolve, reject) => { | ||
const options: SpawnOptions = cwd | ||
? { cwd, stdio: ["inherit", "inherit", "inherit"] } | ||
: { stdio: ["inherit", "inherit", "inherit"] }; | ||
|
||
const proc = spawn(command, args, options); | ||
|
||
proc.on("error", (error) => { | ||
console.error(`Error: ${error.message}`); | ||
reject(error); | ||
}); | ||
|
||
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; | ||
proc.on("close", (code) => { | ||
if (code !== 0) { | ||
reject(new Error(`Command failed with exit code ${code}`)); | ||
} else { | ||
throw "dev.ts programming error"; | ||
} | ||
} | ||
|
||
let maxLength = 0; | ||
for (let pre in this.prefixColors) { | ||
if (pre.length > maxLength) { | ||
maxLength = pre.length; | ||
} | ||
} | ||
|
||
return `\x1b[38;5;${color}m ${prefix.padStart(maxLength)}|\x1b[0m`; | ||
} | ||
|
||
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; | ||
} | ||
|
||
// 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 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 (let 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("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}`; | ||
} | ||
resolve(); | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} |