Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
384
test/git-hooks-pre-commit.test.ts
Normal file
384
test/git-hooks-pre-commit.test.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
// Git hook tests validate pre-commit hook behavior and scripts.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { cleanupTempDirs, makeTempRepoRoot } from "./helpers/temp-repo.js";
|
||||
|
||||
const baseGitEnv = {
|
||||
GIT_CONFIG_NOSYSTEM: "1",
|
||||
GIT_TERMINAL_PROMPT: "0",
|
||||
};
|
||||
const baseRunEnv: NodeJS.ProcessEnv = { ...process.env, ...baseGitEnv };
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
const run = (cwd: string, cmd: string, args: string[] = [], env?: NodeJS.ProcessEnv) => {
|
||||
return execFileSync(cmd, args, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
env: env ? { ...baseRunEnv, ...env } : baseRunEnv,
|
||||
}).trim();
|
||||
};
|
||||
|
||||
type FailedCommand = {
|
||||
status: number;
|
||||
stderr: string;
|
||||
stdout: string;
|
||||
};
|
||||
|
||||
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");
|
||||
};
|
||||
|
||||
function writeExecutable(dir: string, name: string, contents: string): void {
|
||||
writeFileSync(path.join(dir, name), contents, {
|
||||
encoding: "utf8",
|
||||
mode: 0o755,
|
||||
});
|
||||
}
|
||||
|
||||
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"),
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(dir, "scripts", "pre-commit", "run-node-tool.sh"),
|
||||
"#!/usr/bin/env bash\nexit 0\n",
|
||||
{
|
||||
encoding: "utf8",
|
||||
mode: 0o755,
|
||||
},
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(dir, "scripts", "pre-commit", "filter-staged-files.mjs"),
|
||||
"process.exit(0);\n",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const fakeBinDir = path.join(dir, "bin");
|
||||
mkdirSync(fakeBinDir, { recursive: true });
|
||||
writeExecutable(fakeBinDir, "node", "#!/usr/bin/env bash\nexit 0\n");
|
||||
return fakeBinDir;
|
||||
}
|
||||
|
||||
function installFormattingRecorder(dir: string): string {
|
||||
const logPath = path.join(dir, "hook-tool.log");
|
||||
writeFileSync(
|
||||
path.join(dir, "scripts", "pre-commit", "filter-staged-files.mjs"),
|
||||
`const files = process.argv.slice(3).filter((arg) => arg !== "--");
|
||||
for (const file of files) {
|
||||
if (file.endsWith(".ts")) {
|
||||
process.stdout.write(file);
|
||||
process.stdout.write("\0");
|
||||
}
|
||||
}
|
||||
`,
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(dir, "scripts", "pre-commit", "run-node-tool.sh"),
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf '%s\n' "$*" >> ${JSON.stringify(logPath)}
|
||||
`,
|
||||
{
|
||||
encoding: "utf8",
|
||||
mode: 0o755,
|
||||
},
|
||||
);
|
||||
return logPath;
|
||||
}
|
||||
|
||||
function installRunNodeToolFixture(dir: string): void {
|
||||
mkdirSync(path.join(dir, "scripts", "pre-commit"), { recursive: true });
|
||||
symlinkSync(
|
||||
path.join(process.cwd(), "scripts", "pre-commit", "run-node-tool.sh"),
|
||||
path.join(dir, "scripts", "pre-commit", "run-node-tool.sh"),
|
||||
);
|
||||
}
|
||||
|
||||
function splitNonEmptyLines(output: string): string[] {
|
||||
const lines: string[] = [];
|
||||
for (const line of output.split("\n")) {
|
||||
if (line) {
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function readFormatterLog(logPath: string): string[] {
|
||||
if (!existsSync(logPath)) {
|
||||
return [];
|
||||
}
|
||||
return splitNonEmptyLines(readFileSync(logPath, "utf8"));
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTempDirs(tempDirs);
|
||||
});
|
||||
|
||||
describe("git-hooks/pre-commit (integration)", () => {
|
||||
it("does not treat staged filenames as git-add flags (e.g. --all)", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
|
||||
// Use the real hook script and lightweight helper stubs.
|
||||
const fakeBinDir = installPreCommitFixture(dir);
|
||||
// Create an untracked file that should NOT be staged by the hook.
|
||||
writeFileSync(path.join(dir, "secret.txt"), "do-not-stage\n", "utf8");
|
||||
|
||||
// Stage a maliciously-named file. Older hooks using `xargs git add` could run `git add --all`.
|
||||
writeFileSync(path.join(dir, "--all"), "flag\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "--all"]);
|
||||
|
||||
// Run the hook directly (same logic as when installed via core.hooksPath).
|
||||
run(dir, "bash", ["git-hooks/pre-commit"], {
|
||||
PATH: `${fakeBinDir}:${process.env.PATH ?? ""}`,
|
||||
});
|
||||
|
||||
const staged = splitNonEmptyLines(run(dir, "git", ["diff", "--cached", "--name-only"]));
|
||||
expect(staged).toEqual(["--all"]);
|
||||
});
|
||||
|
||||
it("skips formatting staged files while a merge commit is in progress", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-merge-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
installPreCommitFixture(dir);
|
||||
const logPath = installFormattingRecorder(dir);
|
||||
|
||||
writeFileSync(path.join(dir, "changed.ts"), "export const value = 1;\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "changed.ts"]);
|
||||
run(dir, "git", [
|
||||
"-c",
|
||||
"user.name=Test User",
|
||||
"-c",
|
||||
"user.email=test@example.invalid",
|
||||
"commit",
|
||||
"-q",
|
||||
"-m",
|
||||
"initial",
|
||||
]);
|
||||
run(dir, "git", ["checkout", "-q", "-b", "side"]);
|
||||
writeFileSync(path.join(dir, "changed.ts"), "export const value = 2;\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "changed.ts"]);
|
||||
run(dir, "git", [
|
||||
"-c",
|
||||
"user.name=Test User",
|
||||
"-c",
|
||||
"user.email=test@example.invalid",
|
||||
"commit",
|
||||
"-q",
|
||||
"-m",
|
||||
"side change",
|
||||
]);
|
||||
run(dir, "git", ["checkout", "-q", "main"]);
|
||||
run(dir, "git", [
|
||||
"-c",
|
||||
"user.name=Test User",
|
||||
"-c",
|
||||
"user.email=test@example.invalid",
|
||||
"merge",
|
||||
"--no-commit",
|
||||
"--no-ff",
|
||||
"side",
|
||||
]);
|
||||
|
||||
expect(existsSync(path.join(dir, ".git", "MERGE_HEAD"))).toBe(true);
|
||||
expect(run(dir, "git", ["diff", "--cached", "--name-only"])).toBe("changed.ts");
|
||||
|
||||
run(dir, "bash", ["git-hooks/pre-commit"]);
|
||||
|
||||
expect(readFormatterLog(logPath)).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["cherry-pick", "CHERRY_PICK_HEAD", "file"],
|
||||
["revert", "REVERT_HEAD", "file"],
|
||||
["rebase head", "REBASE_HEAD", "file"],
|
||||
["merge rebase state", "rebase-merge", "dir"],
|
||||
["apply rebase state", "rebase-apply", "dir"],
|
||||
])("skips formatting staged files while %s metadata is present", (_label, gitPath, kind) => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-sequencer-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
installPreCommitFixture(dir);
|
||||
const logPath = installFormattingRecorder(dir);
|
||||
|
||||
writeFileSync(path.join(dir, "changed.ts"), "export const value = 1;\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "changed.ts"]);
|
||||
|
||||
const metadataPath = path.join(dir, ".git", gitPath);
|
||||
if (kind === "dir") {
|
||||
mkdirSync(metadataPath, { recursive: true });
|
||||
} else {
|
||||
writeFileSync(metadataPath, "sequencer state\n", "utf8");
|
||||
}
|
||||
|
||||
run(dir, "bash", ["git-hooks/pre-commit"]);
|
||||
|
||||
expect(readFormatterLog(logPath)).toEqual([]);
|
||||
});
|
||||
|
||||
it("still formats staged files during a normal commit", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-normal-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
installPreCommitFixture(dir);
|
||||
const logPath = installFormattingRecorder(dir);
|
||||
|
||||
writeFileSync(path.join(dir, "changed.ts"), "export const value = 1;\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "changed.ts"]);
|
||||
|
||||
run(dir, "bash", ["git-hooks/pre-commit"]);
|
||||
|
||||
expect(readFormatterLog(logPath)).toEqual([
|
||||
"oxfmt --write --no-error-on-unmatched-pattern changed.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not run the changed-scope check for non-doc staged changes", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-no-check-changed-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
|
||||
const fakeBinDir = installPreCommitFixture(dir);
|
||||
writeFileSync(path.join(dir, "package.json"), '{"name":"tmp"}\n', "utf8");
|
||||
writeFileSync(path.join(dir, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n", "utf8");
|
||||
writeExecutable(
|
||||
fakeBinDir,
|
||||
"pnpm",
|
||||
"#!/usr/bin/env bash\necho 'pnpm should not run from pre-commit' >&2\nexit 99\n",
|
||||
);
|
||||
|
||||
writeFileSync(path.join(dir, "tracked.txt"), "hello\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "tracked.txt"]);
|
||||
|
||||
run(dir, "bash", ["git-hooks/pre-commit"], {
|
||||
PATH: `${fakeBinDir}:${process.env.PATH ?? ""}`,
|
||||
});
|
||||
|
||||
expect(run(dir, "git", ["diff", "--cached", "--name-only"])).toBe("tracked.txt");
|
||||
});
|
||||
|
||||
it("does not re-add staged paths that are ignored by the current .gitignore", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-ignored-staged-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
|
||||
const fakeBinDir = installPreCommitFixture(dir);
|
||||
mkdirSync(path.join(dir, ".agents", "skills", "discord-clawd"), { recursive: true });
|
||||
writeFileSync(path.join(dir, ".gitignore"), ".agents/skills/discord-clawd/\n", "utf8");
|
||||
writeFileSync(
|
||||
path.join(dir, ".agents", "skills", "discord-clawd", "SKILL.md"),
|
||||
"# Discord Clawd\n",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
run(dir, "git", ["add", "--", ".gitignore"]);
|
||||
run(dir, "git", ["add", "-f", "--", ".agents/skills/discord-clawd/SKILL.md"]);
|
||||
|
||||
run(dir, "bash", ["git-hooks/pre-commit"], {
|
||||
PATH: `${fakeBinDir}:${process.env.PATH ?? ""}`,
|
||||
});
|
||||
|
||||
const staged = splitNonEmptyLines(run(dir, "git", ["diff", "--cached", "--name-only"]));
|
||||
expect(staged).toEqual([".agents/skills/discord-clawd/SKILL.md", ".gitignore"]);
|
||||
});
|
||||
|
||||
it("ignores FAST_COMMIT because the hook is already formatting-only", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-fast-");
|
||||
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
|
||||
|
||||
const fakeBinDir = installPreCommitFixture(dir);
|
||||
writeFileSync(path.join(dir, "package.json"), '{"name":"tmp"}\n', "utf8");
|
||||
writeFileSync(path.join(dir, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n", "utf8");
|
||||
|
||||
writeExecutable(
|
||||
fakeBinDir,
|
||||
"pnpm",
|
||||
"#!/usr/bin/env bash\necho 'pnpm should not run when FAST_COMMIT is enabled' >&2\nexit 99\n",
|
||||
);
|
||||
|
||||
writeFileSync(path.join(dir, "tracked.txt"), "hello\n", "utf8");
|
||||
run(dir, "git", ["add", "--", "tracked.txt"]);
|
||||
|
||||
run(dir, "bash", ["git-hooks/pre-commit"], {
|
||||
PATH: `${fakeBinDir}:${process.env.PATH ?? ""}`,
|
||||
FAST_COMMIT: "1",
|
||||
});
|
||||
|
||||
expect(run(dir, "git", ["diff", "--cached", "--name-only"])).toBe("tracked.txt");
|
||||
});
|
||||
});
|
||||
|
||||
describe("scripts/pre-commit/run-node-tool.sh", () => {
|
||||
it("runs the installed local tool without invoking pnpm", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-run-node-tool-local-");
|
||||
installRunNodeToolFixture(dir);
|
||||
writeFileSync(path.join(dir, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n", "utf8");
|
||||
|
||||
const fakeBinDir = path.join(dir, "bin");
|
||||
const toolBinDir = path.join(dir, "node_modules", ".bin");
|
||||
mkdirSync(fakeBinDir, { recursive: true });
|
||||
mkdirSync(toolBinDir, { recursive: true });
|
||||
writeExecutable(
|
||||
fakeBinDir,
|
||||
"pnpm",
|
||||
"#!/usr/bin/env bash\necho 'pnpm should not run from run-node-tool' >&2\nexit 99\n",
|
||||
);
|
||||
writeExecutable(toolBinDir, "oxfmt", "#!/usr/bin/env bash\nprintf 'local:%s\\n' \"$*\"\n");
|
||||
|
||||
expect(
|
||||
run(dir, "bash", ["scripts/pre-commit/run-node-tool.sh", "oxfmt", "--write", "a.ts"], {
|
||||
PATH: `${fakeBinDir}:${process.env.PATH ?? ""}`,
|
||||
}),
|
||||
).toBe("local:--write a.ts");
|
||||
});
|
||||
|
||||
it("fails before pnpm can hydrate dependencies when node_modules is missing", () => {
|
||||
const dir = makeTempRepoRoot(tempDirs, "openclaw-run-node-tool-missing-deps-");
|
||||
installRunNodeToolFixture(dir);
|
||||
writeFileSync(path.join(dir, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n", "utf8");
|
||||
|
||||
const fakeBinDir = path.join(dir, "bin");
|
||||
const markerPath = path.join(dir, "pnpm-called");
|
||||
mkdirSync(fakeBinDir, { recursive: true });
|
||||
writeExecutable(
|
||||
fakeBinDir,
|
||||
"pnpm",
|
||||
`#!/usr/bin/env bash\ntouch ${JSON.stringify(markerPath)}\nexit 99\n`,
|
||||
);
|
||||
|
||||
const result = runFailure(
|
||||
dir,
|
||||
"bash",
|
||||
["scripts/pre-commit/run-node-tool.sh", "oxfmt", "--write", "a.ts"],
|
||||
{ PATH: `${fakeBinDir}:${process.env.PATH ?? ""}` },
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain(
|
||||
"Missing repo dependencies: cannot run oxfmt without node_modules.",
|
||||
);
|
||||
expect(existsSync(markerPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user