snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
This commit is contained in:
73
.claude/scripts/test/harness-revert.test.mjs
Normal file
73
.claude/scripts/test/harness-revert.test.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Tests for harness-revert.mjs --hard collateral guard: `reset --hard` to the
|
||||
// session fork point must NOT silently drop commits that are not part of the
|
||||
// session (e.g. the user's own commits made on the base branch after the fork).
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const SCRIPT = join(HERE, "..", "harness-revert.mjs");
|
||||
const SHORT = "beef1234";
|
||||
|
||||
let TMP = null;
|
||||
afterEach(() => {
|
||||
if (TMP) rmSync(TMP, { recursive: true, force: true });
|
||||
TMP = null;
|
||||
});
|
||||
|
||||
// A repo forked at `base`, a session branch with one change, and one UNRELATED
|
||||
// commit on main made AFTER the fork (the collateral --hard would drop).
|
||||
const setup = () => {
|
||||
TMP = mkdtempSync(join(tmpdir(), "harness-revert-test-"));
|
||||
const app = join(TMP, "app");
|
||||
const g = (...a) => spawnSync("git", ["-C", app, ...a], { encoding: "utf8" });
|
||||
spawnSync("git", ["init", "-q", "-b", "main", app]);
|
||||
g("config", "user.email", "t@t.t");
|
||||
g("config", "user.name", "t");
|
||||
writeFileSync(join(app, "file.txt"), "base\n");
|
||||
g("add", ".");
|
||||
g("commit", "-qm", "base");
|
||||
const forkSha = g("rev-parse", "HEAD").stdout.trim();
|
||||
g("branch", `session-base/${SHORT}`);
|
||||
g("branch", `session/${SHORT}`);
|
||||
// session work lands on the session branch (never merged to main here)
|
||||
g("checkout", "-q", `session/${SHORT}`);
|
||||
writeFileSync(join(app, "file.txt"), "session change\n");
|
||||
g("add", ".");
|
||||
g("commit", "-qm", "session work");
|
||||
g("checkout", "-q", "main");
|
||||
// the user's unrelated commit on main, AFTER the fork
|
||||
writeFileSync(join(app, "unrelated.txt"), "my own work\n");
|
||||
g("add", ".");
|
||||
g("commit", "-qm", "unrelated commit");
|
||||
g("config", "--local", `sessionbase.${SHORT}.branch`, "main");
|
||||
|
||||
const head = () => g("rev-parse", "HEAD").stdout.trim();
|
||||
const run = (...args) =>
|
||||
spawnSync("node", [SCRIPT, SHORT, ...args], { cwd: app, encoding: "utf8" });
|
||||
return { app, forkSha, head, run };
|
||||
};
|
||||
|
||||
describe("harness-revert --hard collateral guard", () => {
|
||||
test("refuses --hard when it would drop non-session commits", () => {
|
||||
const { head, run } = setup();
|
||||
const before = head();
|
||||
const r = run("--hard");
|
||||
expect(r.status).toBe(1);
|
||||
expect(r.stderr).toMatch(/NOT part of session/);
|
||||
expect(r.stderr).toContain("unrelated commit");
|
||||
// HEAD untouched — nothing was reset.
|
||||
expect(head()).toBe(before);
|
||||
});
|
||||
|
||||
test("--force lets --hard reset to the fork point", () => {
|
||||
const { forkSha, head, run } = setup();
|
||||
const r = run("--hard", "--force");
|
||||
expect(r.status).toBe(0);
|
||||
expect(head()).toBe(forkSha);
|
||||
});
|
||||
});
|
||||
171
.claude/scripts/test/pending-deploys.test.mjs
Normal file
171
.claude/scripts/test/pending-deploys.test.mjs
Normal file
@@ -0,0 +1,171 @@
|
||||
// Tests for .claude/scripts/pending-deploys.mjs: decides whether a session has
|
||||
// deploy-relevant changes worth a migration round. Config-driven:
|
||||
// the round exists iff config.deploy is present, and relevance comes from
|
||||
// config.deploy.relevantGlobs (one definition, unified with the orchestrator's
|
||||
// SIMPLE-review gate).
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { globToRegexSource, relevanceRegex } from "../pending-deploys.mjs";
|
||||
|
||||
const SCRIPT = new URL("../pending-deploys.mjs", import.meta.url).pathname;
|
||||
|
||||
function git(cwd, ...args) {
|
||||
return execFileSync("git", args, { cwd }).toString();
|
||||
}
|
||||
|
||||
// A repo with a Supabase deploy adapter configured (so the deploy gate is open),
|
||||
// a declarative schema file, and the session topology branches.
|
||||
function setupRepo({ deploy = true } = {}) {
|
||||
const dir = mkdtempSync(join(tmpdir(), "pd-"));
|
||||
git(dir, "init", "-q", "-b", "main");
|
||||
git(dir, "config", "user.email", "t@t.t");
|
||||
git(dir, "config", "user.name", "t");
|
||||
mkdirSync(join(dir, "src"), { recursive: true });
|
||||
mkdirSync(join(dir, "supabase/schemas"), { recursive: true });
|
||||
mkdirSync(join(dir, "supabase/migrations"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(dir, "src/types.ts"),
|
||||
"export type Contact = { id: string };\n",
|
||||
);
|
||||
writeFileSync(
|
||||
join(dir, "supabase/schemas/01_tables.sql"),
|
||||
"create table contacts (id uuid primary key);\n",
|
||||
);
|
||||
const config = { validation: { steps: [] }, roles: {} };
|
||||
if (deploy)
|
||||
config.deploy = { adapter: "supabase", relevantGlobs: ["supabase/**"] };
|
||||
writeFileSync(join(dir, "harness.config.json"), JSON.stringify(config));
|
||||
git(dir, "add", ".");
|
||||
git(dir, "commit", "-qm", "seed");
|
||||
git(dir, "branch", "session-base/ab12cd34", "main");
|
||||
git(dir, "branch", "session/ab12cd34", "main");
|
||||
return dir;
|
||||
}
|
||||
|
||||
const run = (dir, ...args) =>
|
||||
execFileSync("node", [SCRIPT, "--app", dir, ...args])
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
describe("pending-deploys relevance", () => {
|
||||
test("globToRegexSource anchors and expands ** / *", () => {
|
||||
expect(globToRegexSource("supabase/**")).toBe("^supabase/.*");
|
||||
expect(globToRegexSource("**/types.ts")).toBe("^(?:.*/)?types\\.ts");
|
||||
});
|
||||
|
||||
test("relevanceRegex matches configured paths, not others", () => {
|
||||
const re = relevanceRegex(["supabase/**"]);
|
||||
expect(re.test("supabase/schemas/01_tables.sql")).toBe(true);
|
||||
expect(re.test("src/types.ts")).toBe(false);
|
||||
expect(relevanceRegex([]).test("supabase/schemas/x.sql")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pending-deploys", () => {
|
||||
test("empty when session branch made no deploy-relevant change", () => {
|
||||
const dir = setupRepo();
|
||||
expect(run(dir, "--session", "ab12cd34")).toBe("");
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("non-empty when the session branch changes the declarative schema", () => {
|
||||
const dir = setupRepo();
|
||||
git(dir, "checkout", "-q", "session/ab12cd34");
|
||||
writeFileSync(
|
||||
join(dir, "supabase/schemas/01_tables.sql"),
|
||||
"create table contacts (id uuid primary key, priority int);\n",
|
||||
);
|
||||
git(dir, "add", ".");
|
||||
git(dir, "commit", "-qm", "add priority column");
|
||||
git(dir, "checkout", "-q", "main");
|
||||
expect(run(dir, "--session", "ab12cd34")).not.toBe("");
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("a TS-only change (no schema file) is NOT deploy-relevant (unified definition)", () => {
|
||||
const dir = setupRepo();
|
||||
git(dir, "checkout", "-q", "session/ab12cd34");
|
||||
writeFileSync(
|
||||
join(dir, "src/types.ts"),
|
||||
"export type Contact = { id: string; priority: number };\n",
|
||||
);
|
||||
git(dir, "add", ".");
|
||||
git(dir, "commit", "-qm", "add priority to type only");
|
||||
git(dir, "checkout", "-q", "main");
|
||||
expect(run(dir, "--session", "ab12cd34")).toBe("");
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("no config.deploy -> exit 0 empty (deploy phase not configured)", () => {
|
||||
const dir = setupRepo({ deploy: false });
|
||||
git(dir, "checkout", "-q", "session/ab12cd34");
|
||||
writeFileSync(
|
||||
join(dir, "supabase/schemas/01_tables.sql"),
|
||||
"create table contacts (id uuid primary key, x int);\n",
|
||||
);
|
||||
git(dir, "add", ".");
|
||||
git(dir, "commit", "-qm", "schema change but no deploy adapter");
|
||||
git(dir, "checkout", "-q", "main");
|
||||
expect(run(dir, "--session", "ab12cd34")).toBe("");
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("empty and exit 0 when NO session topology exists at all (hooks never ran)", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "pd-"));
|
||||
git(dir, "init", "-q", "-b", "main");
|
||||
git(dir, "config", "user.email", "t@t.t");
|
||||
git(dir, "config", "user.name", "t");
|
||||
writeFileSync(join(dir, "x.ts"), "export const x = 1;\n");
|
||||
writeFileSync(
|
||||
join(dir, "harness.config.json"),
|
||||
JSON.stringify({
|
||||
validation: { steps: [] },
|
||||
roles: {},
|
||||
deploy: { adapter: "supabase", relevantGlobs: ["supabase/**"] },
|
||||
}),
|
||||
);
|
||||
git(dir, "add", ".");
|
||||
git(dir, "commit", "-qm", "seed");
|
||||
expect(run(dir, "--session", "xx99yy00")).toBe("");
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("exits 3 (could not decide) when --session is missing", () => {
|
||||
const dir = setupRepo();
|
||||
let code = 0,
|
||||
stderr = "";
|
||||
try {
|
||||
execFileSync("node", [SCRIPT, "--app", dir], {
|
||||
encoding: "utf8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
} catch (e) {
|
||||
code = e.status;
|
||||
stderr = (e.stderr || "").toString();
|
||||
}
|
||||
expect(code).toBe(3);
|
||||
expect(stderr).toMatch(/session/i);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("fails loudly (exit 3 + stderr) on SESSION_SHORT_ID mismatch", () => {
|
||||
const dir = setupRepo();
|
||||
let code = 0,
|
||||
stderr = "";
|
||||
try {
|
||||
execFileSync("node", [SCRIPT, "--app", dir, "--session", "xx99yy00"], {
|
||||
encoding: "utf8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
} catch (e) {
|
||||
code = e.status;
|
||||
stderr = (e.stderr || "").toString();
|
||||
}
|
||||
expect(code).toBe(3);
|
||||
expect(stderr).toMatch(/mismatch/i);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user