workspace-tools
Version:
A collection of utilities that are useful in a git-controlled monorepo managed by one of these tools:
144 lines • 5.37 kB
JavaScript
;
//
// Basic git wrappers
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.processGitOutput = exports.gitFailFast = exports.git = exports.clearGitObservers = exports.addGitObserver = exports.GitError = void 0;
const child_process_1 = require("child_process");
class GitError extends Error {
constructor(message, originalError, gitOutput) {
if (originalError instanceof Error) {
super(`${message}: ${originalError.message}`);
}
else if (gitOutput?.stderr) {
super(`${message} -- stderr:\n${gitOutput.stderr}`);
}
else {
super(message);
}
this.originalError = originalError;
this.gitOutput = gitOutput;
}
}
exports.GitError = GitError;
/**
* A global maxBuffer override for all git operations.
* Bumps up the default to 500MB instead of 1MB.
* Override this value with the `GIT_MAX_BUFFER` environment variable.
*/
const defaultMaxBuffer = process.env.GIT_MAX_BUFFER ? parseInt(process.env.GIT_MAX_BUFFER) : 500 * 1024 * 1024;
const isDebug = !!process.env.GIT_DEBUG;
const observers = [];
let observing;
/**
* Adds an observer for the git operations, e.g. for testing
* @returns a function to remove the observer
*/
function addGitObserver(observer) {
observers.push(observer);
return () => removeGitObserver(observer);
}
exports.addGitObserver = addGitObserver;
/** Clear all git observers */
function clearGitObservers() {
observers.splice(0, observers.length);
}
exports.clearGitObservers = clearGitObservers;
/** Remove a git observer */
function removeGitObserver(observer) {
const index = observers.indexOf(observer);
if (index > -1) {
observers.splice(index, 1);
}
}
/**
* Runs git command - use this for read-only commands, or if you'd like to explicitly check the
* result and implement custom error handling.
*
* The caller is responsible for validating the input.
* `shell` will always be set to false.
*/
function git(args, options) {
if (args.some((arg) => arg.startsWith("--upload-pack"))) {
// This is a security issue and not needed for any expected usage of this library.
throw new GitError("git command contains --upload-pack, which is not allowed: " + args.join(" "));
}
const gitDescription = `git ${args.join(" ")}`;
const { throwOnError, description = gitDescription, debug = isDebug, ...spawnOptions } = options || {};
debug && console.log(gitDescription);
let results;
try {
// this only throws if git isn't found or other rare cases
results = (0, child_process_1.spawnSync)("git", args, { maxBuffer: defaultMaxBuffer, ...spawnOptions });
}
catch (e) {
throw new GitError(`${description} failed (while spawning process)`, e);
}
const output = {
...results,
// these may be undefined if stdio: inherit is set
stderr: (results.stderr || "").toString().trimEnd(),
stdout: (results.stdout || "").toString().trimEnd(),
success: results.status === 0,
};
if (debug) {
console.log("exited with code " + results.status);
output.stdout && console.log("git stdout:\n", output.stdout);
output.stderr && console.warn("git stderr:\n", output.stderr);
}
// notify observers, flipping the observing bit to prevent infinite loops
if (!observing) {
observing = true;
for (const observer of observers) {
observer(args, output);
}
observing = false;
}
if (!output.success && throwOnError) {
throw new GitError(`${description} failed${output.stderr ? `\n${output.stderr}` : ""}`, undefined, output);
}
return output;
}
exports.git = git;
/**
* Run a git command. Use this for commands that make critical changes to the filesystem.
* If it fails, throw an error and set `process.exitCode = 1` (unless `options.noExitCode` is set).
*
* The caller is responsible for validating the input.
* `shell` will always be set to false.
*/
function gitFailFast(args, options) {
const gitResult = git(args, options);
if (!gitResult.success) {
if (!options?.noExitCode) {
process.exitCode = 1;
}
throw new GitError(`CRITICAL ERROR: running git command: git ${args.join(" ")}!
${gitResult.stdout?.toString().trimEnd()}
${gitResult.stderr?.toString().trimEnd()}`);
}
}
exports.gitFailFast = gitFailFast;
/**
* Processes git command output by splitting it into lines and filtering out empty lines.
* Also filters out `node_modules` lines if specified in options.
*
* If the command failed with stderr output, an error is thrown.
*
* @param output - The git command output to process
* @returns An array of lines (presumably file paths), or an empty array if the command failed
* without stderr output.
* @internal
*/
function processGitOutput(output, options) {
if (!output.success) {
// If the intent was to throw on failure, `throwOnError` should have been set for the git command.
return [];
}
return output.stdout
.split(/\n/)
.map((line) => line.trim())
.filter((line) => !!line && (!options?.excludeNodeModules || !line.includes("node_modules")));
}
exports.processGitOutput = processGitOutput;
//# sourceMappingURL=git.js.map