From 00a5db443aacf1e3a9899c3be762b380caf53219 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 6 Aug 2026 18:09:59 -0700 Subject: [PATCH] refactor: remove obsolete commit helper --- .../skills/openclaw-changelog-update/SKILL.md | 2 +- .../skills/openclaw-pr-maintainer/SKILL.md | 2 +- .../skills/openclaw-test-performance/SKILL.md | 2 +- .agents/skills/openclaw-testing/SKILL.md | 4 +- .github/instructions/copilot.instructions.md | 2 +- AGENTS.md | 6 +- CHANGELOG.md | 1 + CONTRIBUTING.md | 2 +- scripts/README.md | 2 +- scripts/committer | 289 ------------------ test/scripts/committer.test.ts | 279 ----------------- test/scripts/test-projects.test.ts | 5 +- 12 files changed, 14 insertions(+), 582 deletions(-) delete mode 100755 scripts/committer delete mode 100644 test/scripts/committer.test.ts diff --git a/.agents/skills/openclaw-changelog-update/SKILL.md b/.agents/skills/openclaw-changelog-update/SKILL.md index 6ccb52ac73ec..d4357353742f 100644 --- a/.agents/skills/openclaw-changelog-update/SKILL.md +++ b/.agents/skills/openclaw-changelog-update/SKILL.md @@ -253,7 +253,7 @@ every human `Thanks @...` attribution. generated checks are explicitly skipped - `git diff --check` - for docs/changelog-only changes, no broad tests are required -- commit with `scripts/committer "docs(changelog): refresh YYYY.M.PATCH notes" CHANGELOG.md` +- stage `CHANGELOG.md` and commit with `git commit -m "docs(changelog): refresh YYYY.M.PATCH notes"` - record the new commit as the Release SHA and require `git diff --name-only ..` to print only `CHANGELOG.md` diff --git a/.agents/skills/openclaw-pr-maintainer/SKILL.md b/.agents/skills/openclaw-pr-maintainer/SKILL.md index 4ec382f3bf86..71c9e566f3cc 100644 --- a/.agents/skills/openclaw-pr-maintainer/SKILL.md +++ b/.agents/skills/openclaw-pr-maintainer/SKILL.md @@ -356,7 +356,7 @@ gh search issues --repo openclaw/openclaw --match title,body --limit 50 \ - Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed. - Before landing any PR with non-trivial code changes, run fresh `$autoreview` until no accepted/actionable findings remain; prior CI, ClawSweeper, or manual review is not a substitute. Skip only for truly trivial/docs-only changes or when the user explicitly opts out. - When an agent is landing or merging a PR targeting `main`, use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init `, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init `, validate them with `scripts/pr review-validate-artifacts `, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run ` and `scripts/pr merge-run `. The Testbox flag is mandatory for agents: it verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full `pnpm` gates locally. Do not rebase only because `main` advanced; behind-main drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. -- Use `scripts/committer "" ` for scoped commits instead of manual `git add` and `git commit`. +- Use standard Git commands and stage only the files intended for each commit. - Keep commit messages concise and action-oriented. - Group related changes; avoid bundling unrelated refactors. - Use `.github/pull_request_template.md` for PR submissions and `.github/ISSUE_TEMPLATE/` for issues. diff --git a/.agents/skills/openclaw-test-performance/SKILL.md b/.agents/skills/openclaw-test-performance/SKILL.md index 6c4ac6ef8d22..f5ea96cf432d 100644 --- a/.agents/skills/openclaw-test-performance/SKILL.md +++ b/.agents/skills/openclaw-test-performance/SKILL.md @@ -63,7 +63,7 @@ test:extensions:batch ` or plugin-inspector command 8. Update the running report when requested or when this thread is tracking one. Include before/after commands, artifacts, coverage notes, verification, and next attack order. -9. Commit with `scripts/committer "" ` and push when the +9. Stage the intended paths, commit with standard Git, and push when the user asked for commits/pushes. Stage only files touched for this attack. ## Plugin-Suite Workflow diff --git a/.agents/skills/openclaw-testing/SKILL.md b/.agents/skills/openclaw-testing/SKILL.md index 82a9dae5f4f1..dbb1da773c8d 100644 --- a/.agents/skills/openclaw-testing/SKILL.md +++ b/.agents/skills/openclaw-testing/SKILL.md @@ -118,12 +118,12 @@ sync the current checkout on every run, and stop it before handoff. static checks with ready dependencies. Untrusted repository tooling never runs locally. Full suites and computationally intensive commands run remotely. - Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets. -- Use `scripts/committer "" ` when committing; stage only your files. +- Use standard Git commands when committing; stage only your files. - If dependencies are missing on the selected remote box, run `pnpm install` there, retry once, then report the first actionable error. Do not reconcile or reinstall a local Codex worktree merely to run validation. - In a Codex worktree or linked/sparse checkout, do not run direct local - `pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, or `scripts/committer`. Use + `pnpm test*`, `pnpm check*`, or `pnpm crabbox:run`. Use `node scripts/crabbox-wrapper.mjs` for remote proof and `node scripts/check-changed.mjs` for classify-first changed checks. Use `node scripts/run-vitest.mjs` for bounded focused local proof when the diff --git a/.github/instructions/copilot.instructions.md b/.github/instructions/copilot.instructions.md index e5bb1b6334c2..d81ed4be33de 100644 --- a/.github/instructions/copilot.instructions.md +++ b/.github/instructions/copilot.instructions.md @@ -61,4 +61,4 @@ - **Tests**: `pnpm test` - **Build**: `pnpm build` -If you are coding together with a human, do NOT use scripts/committer, but git directly and run the above commands manually to ensure quality. +If you are coding together with a human, use Git directly and run the above commands manually to ensure quality. diff --git a/AGENTS.md b/AGENTS.md index af621d185cea..ad27b6ad32ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -185,8 +185,8 @@ Skills own workflows; root owns hard policy and routing. Product direction and m - Testbox mechanics: warm from the task checkout; ownership is checkout-path scoped; `--reclaim` only for intentional transfer, and it does not retarget the remote checkout — never cross repos. One lease, one active command; never sync/reclaim during a run; base/head changed means stop and rewarm — never override stale lease checks. Warmup must print a lease id; silent success is unusable — verify before reuse, else fall back to one-shot `run`. Wrapper reuse requires its local SSH key; missing after restart/handoff means warm fresh. Direct lease: `blacksmith testbox run`; Crabbox wrapper reuse needs a wrapper-created lease. Status/stop: `blacksmith testbox status|stop --id ` — id is not positional, no status `--json` flag. Delegated runs reject `--fresh-pr` and `--stop-after`; sync current checkout, workflow owns lifecycle. Compound commands: `bash -lc`, never `sh -lc`; job env uses Bash `declare`. Testbox owns Chromium; never pass Crabbox `--browser` to `provider=blacksmith-testbox`. - Crabbox mechanics: a Crabbox request means real scenario proof — install/update/call/repro the user path, not just copied tests run remotely. Final timing JSON = proof complete; if portal sync hangs after it, interrupt the wrapper only. Wrapper `stop` has no `--timing-json`; use `node scripts/crabbox-wrapper.mjs stop --provider --id `. Sparse-sync temp checkout may claim a kept Testbox; repo-path reuse needs `--reclaim`. Dirty-sync generator proof: compare hashes before/after; `git diff` includes the synced patch. - Visual proof: use Crabbox, set up like a user, then screenshot-verify. No harness/bypass/shortcut unless explicitly asked. -- In Codex or linked worktrees, direct local `pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, and `scripts/committer` can trigger pnpm dependency reconciliation or install prompts. Prefer `node` wrappers locally and Crabbox/Testbox for pnpm-gated proof. -- Repo-native PR worktree may omit `node_modules`; prove remotely, then use `git commit --no-verify`, not `scripts/committer`. +- In Codex or linked worktrees, direct local `pnpm test*`, `pnpm check*`, and `pnpm crabbox:run` can trigger pnpm dependency reconciliation or install prompts. Prefer `node` wrappers locally and Crabbox/Testbox for pnpm-gated proof. +- Repo-native PR worktrees may omit `node_modules`; prove remotely, then use `git commit --no-verify`. - Release-branch formatting: Testbox or existing binary; never local `pnpm exec` reconciliation. Targeted local format/lint: existing `./node_modules/.bin/*`; never `pnpm exec` reconciliation. - Parallel agents share the checkout; never switch its branch while sibling work runs. - QA CLI `--output-dir` must be repo-relative. @@ -331,7 +331,7 @@ Mechanics only; policy lives above. ## Git -- Commit via `scripts/committer "" `; stage intended files only. +- Commit with standard Git commands; stage intended files only. - Commits: conventional-ish, concise, grouped. - No manual stash/autostash unless explicit. Branch switches ok when useful; no new worktrees unless requested. - `main`: no merge commits; rebase on latest `origin/main` before push. After one green run plus clean rebase sanity, do not chase moving `main` with repeated full gates. diff --git a/CHANGELOG.md b/CHANGELOG.md index c388b5fe4eb8..1479bda32506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Changes +- **Developer workflow:** remove the obsolete scoped-commit helper and use standard Git commands in isolated worktrees. - **Plugin uninstall cleanup:** remove exact recorded install paths from `plugins.load.paths` for marketplace, npm, and other managed installs while preserving parent, child, prefix, and unrelated paths. - Fixed Crabbox hydration on unprivileged cloud sandboxes by falling back to a user-writable pnpm store when the shared `/var/cache/crabbox` cache is unavailable, preserving the hardlink import mode after hydration, and making Docker an explicit routed capability instead of an implicit install requirement. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eca706cb5404..57deb891bde0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,7 +54,7 @@ For coordinated change sets that genuinely need more than 20 PRs, join the **#cl - Keep PRs takeover-ready: open them from a branch maintainers can push to. For fork PRs, leave GitHub's **Allow edits by maintainers** option enabled so maintainers can finish urgent fixes, changelog entries, or merge prep when needed. If GitHub shows **Allow edits and access to secrets by maintainers**, enable it only when that workflow/secrets access is acceptable and say so in the PR. - Do not edit `CHANGELOG.md` in contributor PRs. Maintainers or ClawSweeper add the changelog entry when landing user-facing changes. - Run tests: `pnpm build && pnpm check && pnpm test` -- For iterative local commits, `scripts/committer --fast "message" ` skips commit hooks. Only use it when you've already run equivalent targeted validation for the touched surface. +- For iterative local commits after running equivalent targeted validation for the touched surface, `git commit --no-verify` skips commit hooks. - For extension/plugin changes, run the fast local lane first: - `pnpm test:extension ` - `pnpm test:extension --list` to see valid extension ids diff --git a/scripts/README.md b/scripts/README.md index 7d8e6e608b51..4de7d56ed550 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -40,7 +40,7 @@ new directory taxonomy. | Live proof | `pnpm test:live` | Live checks require the matching environment and credentials. | | Release checks | `pnpm release:check`, `pnpm release:beta`, `pnpm release:candidate` | Release scripts are maintainer workflows; read release docs before use. | | GitHub reads | `scripts/gh-read` | Uses a GitHub App read token when configured, leaving normal `gh` login for writes. | -| Commits | `scripts/committer "" ` | Preferred scoped commit helper for OpenClaw changes. | +| Commits | `git add ` then `git commit -m ""` | Stage only the intended files for each commit. | | Remote proof | `node scripts/crabbox-wrapper.mjs ...` | Agent default for tests and heavy work; pre-warm by source trust, sync each run, reuse the lease. | ## Script Families diff --git a/scripts/committer b/scripts/committer deleted file mode 100755 index d21942820d09..000000000000 --- a/scripts/committer +++ /dev/null @@ -1,289 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -# Disable glob expansion to handle brackets in file paths -set -f -usage() { - local exit_code=${1:-2} - if [ "$exit_code" -eq 0 ]; then - printf 'Usage: %s [--force] [--fast] [--no-verify-formatted] "commit message" "file" ["file" ...]\n' "$(basename "$0")" - else - printf 'Usage: %s [--force] [--fast] [--no-verify-formatted] "commit message" "file" ["file" ...]\n' "$(basename "$0")" >&2 - fi - exit "$exit_code" -} - -if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then - usage 0 -fi - -if [ "$#" -lt 2 ]; then - usage -fi - -force_delete_lock=false -fast_commit=false -no_verify_formatted=false -while [[ "${1:-}" == --* ]]; do - case "${1:-}" in - --force) - force_delete_lock=true - shift - ;; - --fast) - fast_commit=true - shift - ;; - --no-verify-formatted) - no_verify_formatted=true - shift - ;; - --help|-h) - usage 0 - ;; - *) - usage - ;; - esac -done - -if [ "$#" -lt 2 ]; then - usage -fi - -commit_message=$1 -shift - -if [[ "$commit_message" != *[![:space:]]* ]]; then - printf 'Error: commit message must not be empty\n' >&2 - exit 1 -fi - -if [ -e "$commit_message" ]; then - printf 'Error: first argument looks like a file path ("%s"); provide the commit message first\n' "$commit_message" >&2 - exit 1 -fi - -if [ "$#" -eq 0 ]; then - usage -fi - -path_exists_or_tracked() { - local candidate=$1 - [ -e "$candidate" ] || - git ls-files --error-unmatch -- "$candidate" >/dev/null 2>&1 || - git cat-file -e "HEAD:$candidate" >/dev/null 2>&1 -} - -append_normalized_file_arg() { - local raw=$1 - - if path_exists_or_tracked "$raw"; then - files+=("$raw") - return - fi - - if [[ "$raw" == *$'\n'* || "$raw" == *$'\r'* ]]; then - local normalized=${raw//$'\r'/} - while IFS= read -r line; do - if [[ "$line" == *[![:space:]]* ]]; then - files+=("$line") - fi - done <<< "$normalized" - return - fi - - if [[ "$raw" == *[[:space:]]* ]]; then - local split_paths=() - # Intentional IFS split for callers that pass a single shell-expanded path blob. - # shellcheck disable=SC2206 - split_paths=($raw) - if [ "${#split_paths[@]}" -gt 1 ]; then - files+=("${split_paths[@]}") - return - fi - fi - - files+=("$raw") -} - -files=() -for raw_arg in "$@"; do - append_normalized_file_arg "$raw_arg" -done - -# Disallow "." because it stages the entire repository and defeats the helper's safety guardrails. -for file in "${files[@]}"; do - if [ "$file" = "." ]; then - printf 'Error: "." is not allowed; list specific paths instead\n' >&2 - exit 1 - fi -done - -# Prevent staging node_modules even if a path is forced. -for file in "${files[@]}"; do - case "$file" in - *node_modules* | */node_modules | */node_modules/* | node_modules) - printf 'Error: node_modules paths are not allowed: %s\n' "$file" >&2 - exit 1 - ;; - esac -done - -last_commit_error='' - -run_git_command() { - local stderr_log - stderr_log=$(mktemp) - if "$@" 2>"$stderr_log"; then - if [ -s "$stderr_log" ]; then - cat "$stderr_log" >&2 - fi - rm -f "$stderr_log" - last_commit_error='' - return 0 - fi - - if [ -s "$stderr_log" ]; then - cat "$stderr_log" >&2 - fi - last_commit_error=$(cat "$stderr_log") - rm -f "$stderr_log" - return 1 -} - -is_git_lock_error() { - printf '%s\n' "$last_commit_error" | grep -Eq \ - "Another git process seems to be running|Unable to create '.*\\.git/[^']+\\.lock'" -} - -extract_git_lock_path() { - printf '%s\n' "$last_commit_error" | - sed -n "s/.*'\(.*\.git\/[^']*\.lock\)'.*/\1/p" | - head -n 1 -} - -run_git_with_lock_retry() { - local label=$1 - shift - - local deadline=$((SECONDS + 5)) - local announced_retry=false - - while true; do - if run_git_command "$@"; then - return 0 - fi - - if ! is_git_lock_error; then - return 1 - fi - - if [ "$SECONDS" -ge "$deadline" ]; then - break - fi - - if [ "$announced_retry" = false ]; then - printf 'Git lock during %s; retrying for up to 5 seconds...\n' "$label" >&2 - announced_retry=true - fi - - sleep 0.5 - done - - if [ "$force_delete_lock" = true ]; then - local lock_path - lock_path=$(extract_git_lock_path) - if [ -n "$lock_path" ] && [ -e "$lock_path" ]; then - rm -f "$lock_path" - printf 'Removed stale git lock: %s\n' "$lock_path" >&2 - run_git_command "$@" - return $? - fi - fi - - return 1 -} - -for file in "${files[@]}"; do - if ! path_exists_or_tracked "$file"; then - printf 'Error: file not found: %s\n' "$file" >&2 - exit 1 - fi -done - -committer_requires_repo_formatter() { - local root="$1" - local filter="$root/scripts/pre-commit/filter-staged-files.mjs" - [ -f "$filter" ] || return 1 - - local filter_output selected - filter_output=$(mktemp "${TMPDIR:-/tmp}/openclaw-committer-filter.XXXXXX") || { - echo "Unable to create temporary output for formatter applicability check." >&2 - return 2 - } - if ! node "$filter" format -- "${files[@]}" >"$filter_output"; then - rm -f "$filter_output" - echo "Unable to determine formatter applicability: filter-staged-files.mjs failed." >&2 - return 2 - fi - if IFS= read -r -d '' selected <"$filter_output"; then - rm -f "$filter_output" - return 0 - fi - rm -f "$filter_output" - return 1 -} - -repo_root=$(git rev-parse --show-toplevel) -if [ "$fast_commit" = false ] && [ "$no_verify_formatted" = false ] && \ - [ -f "$repo_root/pnpm-lock.yaml" ] && \ - command -v pnpm >/dev/null 2>&1 && \ - [ ! -e "$repo_root/node_modules" ] -then - if committer_requires_repo_formatter "$repo_root"; then - formatter_requirement=0 - else - formatter_requirement=$? - fi - if [ "$formatter_requirement" = 2 ]; then - exit 1 - fi - if [ "$formatter_requirement" = 0 ]; then - echo "Missing repo dependencies: cannot run oxfmt without node_modules." >&2 - echo "Run pnpm install in a normal checkout, or bypass the hook only after separate formatting proof." >&2 - echo "After that proof, pass --no-verify-formatted to commit explicitly with --no-verify." >&2 - exit 1 - fi -fi - -run_git_with_lock_retry "unstaging files" git restore --staged :/ -run_git_with_lock_retry "staging files" git add --all --force -- "${files[@]}" - -if git diff --staged --quiet; then - printf 'Warning: no staged changes detected for: %s\n' "${files[*]}" >&2 - exit 1 -fi - -committed=false -if [ "$fast_commit" = true ]; then - declare -a commit_env=(FAST_COMMIT=1) - if run_git_with_lock_retry "commit" env "${commit_env[@]}" git commit --no-verify -m "$commit_message"; then - committed=true - fi -elif [ "$no_verify_formatted" = true ]; then - echo "Notice: --no-verify-formatted asserts separate formatting proof; committing with --no-verify." - if run_git_with_lock_retry "commit" git commit --no-verify -m "$commit_message"; then - committed=true - fi -else - if run_git_with_lock_retry "commit" git commit -m "$commit_message"; then - committed=true - fi -fi - -if [ "$committed" = false ]; then - exit 1 -fi - -printf 'Committed "%s" with %d files\n' "$commit_message" "${#files[@]}" diff --git a/test/scripts/committer.test.ts b/test/scripts/committer.test.ts deleted file mode 100644 index 1d6c95811f86..000000000000 --- a/test/scripts/committer.test.ts +++ /dev/null @@ -1,279 +0,0 @@ -// Committer tests cover committer script behavior. -import { execFileSync, spawnSync } from "node:child_process"; -import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { createScriptTestHarness } from "./test-helpers.js"; - -const scriptPath = path.join(process.cwd(), "scripts", "committer"); -const { createTempDir } = createScriptTestHarness(); -let templateRepo: string; - -function run(cwd: string, command: string, args: string[]) { - return execFileSync(command, args, { - cwd, - encoding: "utf8", - }).trim(); -} - -function git(cwd: string, ...args: string[]) { - return run(cwd, "git", args); -} - -function createRepo() { - const repo = createTempDir("committer-test-"); - cpSync(templateRepo, repo, { recursive: true }); - return repo; -} - -function createTemplateRepo() { - const repo = mkdtempSync(path.join(tmpdir(), "committer-template-")); - git(repo, "init", "-q"); - git(repo, "config", "user.email", "test@example.com"); - git(repo, "config", "user.name", "Test User"); - writeFileSync(path.join(repo, "seed.txt"), "seed\n"); - git(repo, "add", "seed.txt"); - git(repo, "commit", "-qm", "seed"); - - return repo; -} - -function writeRepoFile(repo: string, relativePath: string, contents: string) { - const fullPath = path.join(repo, relativePath); - mkdirSync(path.dirname(fullPath), { recursive: true }); - writeFileSync(fullPath, contents); -} - -function installHook(repo: string, relativePath: string, contents: string) { - const fullPath = path.join(repo, relativePath); - mkdirSync(path.dirname(fullPath), { recursive: true }); - writeFileSync(fullPath, contents, { - encoding: "utf8", - mode: 0o755, - }); - git(repo, "config", "core.hooksPath", path.dirname(relativePath)); -} - -function commitWithHelper(repo: string, commitMessage: string, ...args: string[]) { - return run(repo, "bash", [scriptPath, commitMessage, ...args]); -} - -function commitWithHelperArgs(repo: string, ...args: string[]) { - return run(repo, "bash", [scriptPath, ...args]); -} - -function commitWithHelperFailure(repo: string, ...args: string[]) { - return spawnSync("bash", [scriptPath, ...args], { cwd: repo, encoding: "utf8" }); -} - -function committedPaths(repo: string) { - const output = git(repo, "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"); - const paths: string[] = []; - for (const line of output.split("\n")) { - if (line) { - paths.push(line); - } - } - return paths.toSorted(); -} - -function committedFileContents(repo: string, relativePath: string) { - return git(repo, "show", `HEAD:${relativePath}`); -} - -describe("scripts/committer", () => { - beforeAll(() => { - templateRepo = createTemplateRepo(); - }); - - afterAll(() => { - rmSync(templateRepo, { recursive: true, force: true }); - }); - - it("accepts supported path argument shapes", () => { - const cases = [ - { - commitMessage: "test: plain argv", - files: [ - ["alpha.txt", "alpha\n"], - ["nested/file with spaces.txt", "beta\n"], - ] as const, - args: ["alpha.txt", "nested/file with spaces.txt"], - expected: ["alpha.txt", "nested/file with spaces.txt"], - }, - { - commitMessage: "test: space blob", - files: [ - ["alpha.txt", "alpha\n"], - ["beta.txt", "beta\n"], - ] as const, - args: ["alpha.txt beta.txt"], - expected: ["alpha.txt", "beta.txt"], - }, - { - commitMessage: "test: newline blob", - files: [ - ["alpha.txt", "alpha\n"], - ["nested/file with spaces.txt", "beta\n"], - ] as const, - args: ["alpha.txt\nnested/file with spaces.txt"], - expected: ["alpha.txt", "nested/file with spaces.txt"], - }, - ] as const; - - for (const testCase of cases) { - const repo = createRepo(); - for (const [file, contents] of testCase.files) { - writeRepoFile(repo, file, contents); - } - - commitWithHelper(repo, testCase.commitMessage, ...testCase.args); - - expect(committedPaths(repo)).toEqual(testCase.expected); - } - }); - - it("commits changelog-only changes without pulling in unrelated dirty files", () => { - const repo = createRepo(); - writeRepoFile(repo, "CHANGELOG.md", "initial\n"); - writeRepoFile(repo, "unrelated.ts", "export const ok = true;\n"); - git(repo, "add", "CHANGELOG.md", "unrelated.ts"); - git(repo, "commit", "-qm", "seed extra files"); - - writeRepoFile(repo, "CHANGELOG.md", "breaking note\n"); - writeRepoFile(repo, "unrelated.ts", "<<<<<<< HEAD\nleft\n=======\nright\n>>>>>>> branch\n"); - - commitWithHelper(repo, "docs(changelog): note breaking change", "CHANGELOG.md"); - - expect(committedPaths(repo)).toEqual(["CHANGELOG.md"]); - expect(git(repo, "status", "--short")).toContain("M unrelated.ts"); - }); - - it("supports --fast before the commit message", () => { - const repo = createRepo(); - writeRepoFile(repo, "note.txt", "hello\n"); - - const output = commitWithHelperArgs(repo, "--fast", "test: fast helper", "note.txt"); - - expect(output).toContain('Committed "test: fast helper" with 1 files'); - expect(committedPaths(repo)).toEqual(["note.txt"]); - }); - - it("supports combining --force and --fast", () => { - const repo = createRepo(); - writeRepoFile(repo, "note.txt", "hello\n"); - - const output = commitWithHelperArgs( - repo, - "--force", - "--fast", - "test: fast forced helper", - "note.txt", - ); - - expect(output).toContain('Committed "test: fast forced helper" with 1 files'); - expect(committedPaths(repo)).toEqual(["note.txt"]); - }); - - it("fails before staging when formatting dependencies are missing", () => { - const repo = createRepo(); - writeRepoFile(repo, "pnpm-lock.yaml", "lockfileVersion: '9.0'\n"); - writeRepoFile( - repo, - "scripts/pre-commit/filter-staged-files.mjs", - "for (const file of process.argv.slice(4)) { if (file.endsWith('.ts')) process.stdout.write(file + '\\0'); }\n", - ); - writeRepoFile(repo, "note.ts", "export const note = true;\n"); - - const result = commitWithHelperFailure(repo, "test: missing formatter", "note.ts"); - - expect(result.status).toBe(1); - expect(result.stderr).toContain("cannot run oxfmt without node_modules"); - expect(result.stderr).toContain("--no-verify-formatted"); - expect(git(repo, "diff", "--cached", "--name-only")).toBe(""); - expect(git(repo, "log", "-1", "--pretty=%s")).toBe("seed"); - }); - - it("commits dependency-less formatted work only with the explicit assertion", () => { - const repo = createRepo(); - writeRepoFile(repo, "pnpm-lock.yaml", "lockfileVersion: '9.0'\n"); - writeRepoFile( - repo, - "scripts/pre-commit/filter-staged-files.mjs", - "for (const file of process.argv.slice(4)) { if (file.endsWith('.ts')) process.stdout.write(file + '\\0'); }\n", - ); - writeRepoFile(repo, "note.ts", "export const note = true;\n"); - - const output = commitWithHelperArgs( - repo, - "--no-verify-formatted", - "test: formatted assertion", - "note.ts", - ); - - expect(output).toContain("asserts separate formatting proof; committing with --no-verify"); - expect(committedPaths(repo)).toEqual(["note.ts"]); - }); - - it("fails before staging when formatter applicability cannot be determined", () => { - const repo = createRepo(); - writeRepoFile(repo, "pnpm-lock.yaml", "lockfileVersion: '9.0'\n"); - writeRepoFile( - repo, - "scripts/pre-commit/filter-staged-files.mjs", - "process.stderr.write('fixture filter failure\\n'); process.exit(7);\n", - ); - writeRepoFile(repo, "note.ts", "export const note = true;\n"); - - const result = commitWithHelperFailure(repo, "test: failed formatter filter", "note.ts"); - - expect(result.status).toBe(1); - expect(result.stderr).toContain("Unable to determine formatter applicability"); - expect(git(repo, "diff", "--cached", "--name-only")).toBe(""); - expect(git(repo, "log", "-1", "--pretty=%s")).toBe("seed"); - }); - - it("bypasses git hooks when using --fast", () => { - const repo = createRepo(); - installHook(repo, ".githooks/pre-commit", "#!/usr/bin/env bash\nset -euo pipefail\nexit 91\n"); - writeRepoFile(repo, "note.txt", "hello\n"); - - const output = commitWithHelperArgs(repo, "--fast", "test: fast no verify", "note.txt"); - - expect(output).toContain('Committed "test: fast no verify" with 1 files'); - expect(committedPaths(repo)).toEqual(["note.txt"]); - }); - - it("commits the hook-restaged file contents and leaves the tree clean", () => { - const repo = createRepo(); - installHook( - repo, - ".githooks/pre-commit", - [ - "#!/usr/bin/env bash", - "set -euo pipefail", - "printf 'formatted\\n' > note.txt", - "git add note.txt", - ].join("\n") + "\n", - ); - writeRepoFile(repo, "note.txt", "raw\n"); - - const output = commitWithHelperArgs(repo, "test: hook rewrite", "note.txt"); - - expect(output).toContain('Committed "test: hook rewrite" with 1 files'); - expect(committedPaths(repo)).toEqual(["note.txt"]); - expect(committedFileContents(repo, "note.txt")).toBe("formatted"); - expect(git(repo, "status", "--short", "--untracked-files=no")).toBe(""); - }); - - it("prints usage for --help", () => { - const repo = createRepo(); - - const output = commitWithHelperArgs(repo, "--help"); - - expect(output).toContain( - 'Usage: committer [--force] [--fast] [--no-verify-formatted] "commit message" "file" ["file" ...]', - ); - }); -}); diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index 7a2d75048bb5..1eafcce9d62b 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -1514,7 +1514,6 @@ describe("scripts/test-projects changed-target routing", () => { it("keeps extensionless helper script edits on owner tests", () => { const expectedTargets = Object.entries({ - "scripts/committer": ["test/scripts/committer.test.ts"], "scripts/gh-read": ["test/scripts/gh-read.test.ts"], "scripts/pr": [ "test/scripts/pr-merge.test.ts", @@ -3933,7 +3932,7 @@ describe("scripts/test-projects local heavy-check lock", () => { [ { config: "test/vitest/vitest.tooling.config.ts", - includePatterns: ["test/scripts/committer.test.ts"], + includePatterns: ["test/scripts/gh-read.test.ts"], watchMode: false, }, ], @@ -3981,7 +3980,7 @@ describe("scripts/test-projects local heavy-check lock", () => { [ { config: "test/vitest/vitest.tooling.config.ts", - includePatterns: ["test/scripts/committer.test.ts"], + includePatterns: ["test/scripts/gh-read.test.ts"], watchMode: false, }, ],