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:
160
.claude/scripts/pending-deploys.mjs
Executable file
160
.claude/scripts/pending-deploys.mjs
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env node
|
||||
// pending-deploys: decide whether the session has deploy-relevant changes not
|
||||
// yet covered by the migrations dir.
|
||||
//
|
||||
// pending-deploys --app <APP_DIR> --session <SESSION_SHORT>
|
||||
//
|
||||
// Prints a non-empty marker (the changed deploy-relevant paths) when a deploy is
|
||||
// worth offering; prints nothing otherwise.
|
||||
//
|
||||
// Exit codes:
|
||||
// 0 - decision made (stdout empty = nothing to deploy, non-empty = paths).
|
||||
// 3 - could NOT decide: --session missing, or the passed <SESSION_SHORT>
|
||||
// has session-base siblings but no refs of its own (likely a mismatch).
|
||||
import { execFileSync } from "node:child_process";
|
||||
import {
|
||||
loadConfig,
|
||||
isDeployEnabled,
|
||||
deployGlobs,
|
||||
} from "../hooks/lib/config.mjs";
|
||||
|
||||
// Convert a deploy-relevant glob (config.deploy.relevantGlobs) into an anchored
|
||||
// regex source. `**/` -> optional dir prefix, `**` -> any, `*` -> non-slash. The
|
||||
// git diff paths are repo-relative, so anchor at the start.
|
||||
export function globToRegexSource(glob) {
|
||||
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
||||
// Placeholder the glob operators FIRST, expand `*` last, then swap the
|
||||
// placeholders in. Otherwise the `.*` inserted for `**` would be re-mangled by
|
||||
// the single-`*` -> `[^/]*` pass.
|
||||
const body = escaped
|
||||
.replace(/\*\*\//g, "\0DS\0")
|
||||
.replace(/\*\*/g, "\0D\0")
|
||||
.replace(/\*/g, "[^/]*")
|
||||
.replace(/\0DS\0/g, "(?:.*/)?")
|
||||
.replace(/\0D\0/g, ".*");
|
||||
return `^${body}`;
|
||||
}
|
||||
|
||||
// The single deploy-relevance matcher, built from config.deploy.relevantGlobs
|
||||
// (one definition, shared with the orchestrator's SIMPLE-review gate and
|
||||
// the planner's schema wording). Empty globs -> matches nothing.
|
||||
export function relevanceRegex(globs) {
|
||||
if (!globs.length) return /a^/; // never matches
|
||||
return new RegExp(globs.map(globToRegexSource).join("|"));
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const get = (flag, def) => {
|
||||
const i = args.indexOf(flag);
|
||||
return i >= 0 ? args[i + 1] : def;
|
||||
};
|
||||
const APP = get("--app", process.env.CLAUDE_PROJECT_DIR || "/app");
|
||||
const SESSION = get("--session", "");
|
||||
|
||||
// Pluggable deploy phase: the migration round exists ONLY if this
|
||||
// project configures a deploy adapter. With no config.deploy, there is nothing
|
||||
// to deploy, so exit 0 (empty) and the orchestrator terminates at promotion
|
||||
// with no PD state.
|
||||
let cfg;
|
||||
try {
|
||||
cfg = loadConfig(APP);
|
||||
} catch (e) {
|
||||
process.stderr.write(`pending-deploys: ${e.message}\n`);
|
||||
process.exit(3);
|
||||
}
|
||||
if (!isDeployEnabled(cfg)) process.exit(0);
|
||||
|
||||
if (!SESSION) {
|
||||
// Couldn't decide - exit 3, never 0 (0 + empty stdout reads as "nothing to
|
||||
// deploy", which would silently skip a real pending deploy).
|
||||
process.stderr.write("--session <SESSION_SHORT> required\n");
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
const refExists = (ref) => {
|
||||
try {
|
||||
execFileSync("git", ["-C", APP, "show-ref", "--verify", "--quiet", ref], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Guard against the SESSION_SHORT_ID mismatch. The session topology
|
||||
// (session-base/<id> + session/<id>) is created at session start, so missing
|
||||
// refs for the requested id are suspicious. Distinguish two cases:
|
||||
// - NO session-base/* branch exists at all -> no topology (e.g. hooks never
|
||||
// ran, or a non-session context) -> genuinely nothing to deploy -> exit 0.
|
||||
// - session-base/* branches exist but NOT for <SESSION> -> the caller passed
|
||||
// the wrong short id -> fail loudly so the deploy isn't silently skipped.
|
||||
if (
|
||||
!refExists(`refs/heads/session-base/${SESSION}`) ||
|
||||
!refExists(`refs/heads/session/${SESSION}`)
|
||||
) {
|
||||
let bases = "";
|
||||
try {
|
||||
bases = execFileSync(
|
||||
"git",
|
||||
[
|
||||
"-C",
|
||||
APP,
|
||||
"for-each-ref",
|
||||
"--format=%(refname:short)",
|
||||
"refs/heads/session-base/",
|
||||
],
|
||||
{ stdio: ["pipe", "pipe", "pipe"] },
|
||||
)
|
||||
.toString()
|
||||
.trim();
|
||||
} catch {
|
||||
/* not a git repo / no refs - treated as no topology below */
|
||||
}
|
||||
if (bases) {
|
||||
process.stderr.write(
|
||||
`pending-deploys: no session refs for '${SESSION}', but found: ${bases.split("\n").join(", ")}. ` +
|
||||
`Likely a SESSION_SHORT_ID mismatch - refusing to report "nothing to deploy".\n`,
|
||||
);
|
||||
process.exit(3);
|
||||
}
|
||||
process.exit(0); // no session topology anywhere -> nothing to deploy
|
||||
}
|
||||
|
||||
// Deploy-relevance from config.deploy.relevantGlobs: one definition of
|
||||
// "deploy-relevant paths", not a hardcoded regex. A real migration requires a
|
||||
// change under these globs (the DDL source of truth), so this is both
|
||||
// necessary and sufficient to decide whether to offer the migration round.
|
||||
const SCHEMA_RE = relevanceRegex(deployGlobs(cfg));
|
||||
|
||||
let changed = "";
|
||||
try {
|
||||
changed = execFileSync(
|
||||
"git",
|
||||
[
|
||||
"-C",
|
||||
APP,
|
||||
"diff",
|
||||
"--name-only",
|
||||
`session-base/${SESSION}..session/${SESSION}`,
|
||||
],
|
||||
{ stdio: ["pipe", "pipe", "pipe"] },
|
||||
).toString();
|
||||
} catch (e) {
|
||||
process.stderr.write(
|
||||
`pending-deploys: git diff failed for '${SESSION}': ${e.message}\n`,
|
||||
);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
const relevant = changed
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.filter((p) => SCHEMA_RE.test(p));
|
||||
// (Idempotency against already-applied schema is enforced later by the
|
||||
// migration round, which cross-checks the migrations dir and may no-op.)
|
||||
if (relevant.length) process.stdout.write(`${relevant.join("\n")}\n`);
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) main();
|
||||
Reference in New Issue
Block a user