mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
* fix(git-hooks): format ordinary commits with orphan rebase refs * test(git-hooks): group operation-state boundary coverage * test(gateway): settle synthetic restart before teardown
159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import {
|
|
copyFileSync,
|
|
existsSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
symlinkSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import path from "node:path";
|
|
import { makeTempDir as makeTempRepoRoot } from "./helpers/temp-dir.js";
|
|
|
|
const baseGitEnv = {
|
|
GIT_CONFIG_NOSYSTEM: "1",
|
|
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
GIT_TERMINAL_PROMPT: "0",
|
|
GIT_CONFIG_COUNT: "4",
|
|
GIT_CONFIG_KEY_0: "user.name",
|
|
GIT_CONFIG_VALUE_0: "Hook Test",
|
|
GIT_CONFIG_KEY_1: "user.email",
|
|
GIT_CONFIG_VALUE_1: "hook@example.invalid",
|
|
GIT_CONFIG_KEY_2: "commit.gpgSign",
|
|
GIT_CONFIG_VALUE_2: "false",
|
|
GIT_CONFIG_KEY_3: "init.templateDir",
|
|
GIT_CONFIG_VALUE_3: "",
|
|
};
|
|
export const rulePath = ".git/private rules.txt";
|
|
export const ruleSetting = "hooks.blockedLiteralsFile";
|
|
export const literals = ["GUARD_SYNTHETIC_ALPHA", "GUARD_SYNTHETIC_BETA_[x].*42"] as const;
|
|
|
|
export const run = (cwd: string, cmd: string, args: string[] = [], env?: NodeJS.ProcessEnv) => {
|
|
return execFileSync(cmd, args, {
|
|
cwd,
|
|
encoding: "utf8",
|
|
stdio: "pipe",
|
|
timeout: 20_000,
|
|
maxBuffer: 24 * 1024 * 1024,
|
|
env: {
|
|
PATH: process.env.PATH,
|
|
HOME: cwd,
|
|
TMPDIR: cwd,
|
|
LC_ALL: "C",
|
|
TZ: "UTC",
|
|
...baseGitEnv,
|
|
...env,
|
|
},
|
|
}).trim();
|
|
};
|
|
|
|
type FailedCommand = {
|
|
status: number;
|
|
stderr: string;
|
|
stdout: string;
|
|
};
|
|
|
|
export const runFailure = (
|
|
cwd: string,
|
|
cmd: string,
|
|
args: string[] = [],
|
|
env?: NodeJS.ProcessEnv,
|
|
): FailedCommand => {
|
|
try {
|
|
run(cwd, cmd, args, env);
|
|
} catch (error) {
|
|
if (error instanceof Error && "status" in error) {
|
|
const failure = error as Error & { status?: number; stderr?: string; stdout?: string };
|
|
return {
|
|
status: failure.status ?? 1,
|
|
stderr: failure.stderr ?? "",
|
|
stdout: failure.stdout ?? "",
|
|
};
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
throw new Error("expected command to fail");
|
|
};
|
|
|
|
export function writeExecutable(dir: string, name: string, contents: string): void {
|
|
writeFileSync(path.join(dir, name), contents, {
|
|
encoding: "utf8",
|
|
mode: 0o755,
|
|
});
|
|
}
|
|
|
|
export function installPreCommitFixture(dir: string): string {
|
|
mkdirSync(path.join(dir, "git-hooks"), { recursive: true });
|
|
mkdirSync(path.join(dir, "scripts", "pre-commit"), { recursive: true });
|
|
symlinkSync(
|
|
path.join(process.cwd(), "git-hooks", "pre-commit"),
|
|
path.join(dir, "git-hooks", "pre-commit"),
|
|
);
|
|
for (const name of [
|
|
"run-node-tool.sh",
|
|
"filter-staged-files.mjs",
|
|
"guard-staged-content.mjs",
|
|
"format-staged.sh",
|
|
]) {
|
|
copyFileSync(
|
|
path.join(process.cwd(), "scripts/pre-commit", name),
|
|
path.join(dir, "scripts/pre-commit", name),
|
|
);
|
|
}
|
|
const privateRules = path.resolve(
|
|
dir,
|
|
run(dir, "git", ["rev-parse", "--git-path", "private rules.txt"]),
|
|
);
|
|
writeFileSync(privateRules, `${literals.join("\n")}\n`, { mode: 0o600 });
|
|
run(dir, "git", ["config", "--local", ruleSetting, privateRules]);
|
|
mkdirSync(path.join(dir, "node_modules/.bin"), { recursive: true });
|
|
// Stdin mode must echo the blob or the hook treats the empty output as formatter failure.
|
|
writeExecutable(
|
|
path.join(dir, "node_modules/.bin"),
|
|
"oxfmt",
|
|
'#!/bin/sh\ncase "$*" in *--stdin-filepath=*) cat ;; esac\nexit 0\n',
|
|
);
|
|
|
|
const fakeBinDir = path.join(dir, "bin");
|
|
mkdirSync(fakeBinDir, { recursive: true });
|
|
return fakeBinDir;
|
|
}
|
|
|
|
export function createContentGuardFixture(tempDirs: string[]): string {
|
|
const dir = makeTempRepoRoot(tempDirs, "openclaw-content-guard-");
|
|
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
|
installPreCommitFixture(dir);
|
|
return dir;
|
|
}
|
|
|
|
export function stageContent(dir: string, name: string, content: string | Buffer): void {
|
|
mkdirSync(path.dirname(path.join(dir, name)), { recursive: true });
|
|
writeFileSync(path.join(dir, name), content);
|
|
run(dir, "git", ["--literal-pathspecs", "add", "-f", "--", name]);
|
|
}
|
|
|
|
export const commitArgs = ["-c", "core.hooksPath=git-hooks", "commit", "-qm", "guard proof"];
|
|
|
|
export function installFormattingRecorder(dir: string, body = ""): string {
|
|
const logPath = path.join(dir, "hook-tool.log");
|
|
writeExecutable(
|
|
path.join(dir, "node_modules/.bin"),
|
|
"oxfmt",
|
|
`#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
printf 'oxfmt %s\n' "$*" >> hook-tool.log
|
|
case "$*" in *--stdin-filepath=*) cat ;; esac
|
|
${body}
|
|
`,
|
|
);
|
|
return logPath;
|
|
}
|
|
|
|
export function readFormatterLog(logPath: string): string[] {
|
|
if (!existsSync(logPath)) {
|
|
return [];
|
|
}
|
|
return readFileSync(logPath, "utf8").split("\n").filter(Boolean);
|
|
}
|