@homeflare/config 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/hooks/gates.d.ts.map +1 -1
- package/dist/hooks/oxfmt-config.d.ts +25 -0
- package/dist/hooks/oxfmt-config.d.ts.map +1 -0
- package/dist/hooks/report.d.ts +10 -0
- package/dist/hooks/report.d.ts.map +1 -1
- package/dist/hooks.js +70 -11
- package/dist/hooks.js.map +6 -5
- package/dist/repo-shape/yaml.d.ts +20 -3
- package/dist/repo-shape/yaml.d.ts.map +1 -1
- package/dist/repo-shape.js +8 -2
- package/dist/repo-shape.js.map +3 -3
- package/dist/versions.js +8 -2
- package/dist/versions.js.map +3 -3
- package/docs/hooks.md +19 -0
- package/package.json +1 -1
- package/src/hooks/gates.ts +70 -12
- package/src/hooks/oxfmt-config.ts +67 -0
- package/src/hooks/report.ts +15 -0
- package/src/repo-shape/yaml.ts +26 -4
package/README.md
CHANGED
|
@@ -83,7 +83,12 @@ cp node_modules/@homeflare/config/bunfig.toml bunfig.toml
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
⚠️ `.oxfmtrc.json` needs the **leading dot**. Without it oxfmt silently uses its defaults,
|
|
86
|
-
and the symptom is a formatter that rewrites your quotes.
|
|
86
|
+
and the symptom is a formatter that rewrites your quotes. The same silent-defaults trap
|
|
87
|
+
applies to `.oxfmtrc.mjs`/`.ts`/`.js`/`.cjs`/`.mts`/`.cts` in a bare `oxfmt`/`oxfmt .`
|
|
88
|
+
invocation — `oxfmt`'s own auto-discovery finds only `.json`/`.jsonc`, even though `-c`
|
|
89
|
+
accepts the rest. Prefer `.oxfmtrc.json`; the shared pre-commit hook resolves the other
|
|
90
|
+
extensions and passes `--config` itself (`docs/hooks.md`), but your own `bun run format`
|
|
91
|
+
script still needs to name the file explicitly if it isn't `.oxfmtrc.json`.
|
|
87
92
|
|
|
88
93
|
⛔ oxfmt has no `extends`. Extra `ignorePatterns` are a **merge**: generated OpenAPI,
|
|
89
94
|
`vendor/`, `**/generated/**`, `**/*.gen.ts` stay out of the formatter. ⛔ Identity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/hooks/gates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/hooks/gates.ts"],"names":[],"mappings":"AAqDA,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+F3D;AAWD;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDjG"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type OxfmtConfigResolution =
|
|
2
|
+
/** `.oxfmtrc.json` or `.oxfmtrc.jsonc` exists — bare oxfmt already finds it. */
|
|
3
|
+
{
|
|
4
|
+
readonly kind: 'auto';
|
|
5
|
+
}
|
|
6
|
+
/** No `.oxfmtrc.*` at all — let oxfmt use its built-in defaults, as today. */
|
|
7
|
+
| {
|
|
8
|
+
readonly kind: 'none';
|
|
9
|
+
}
|
|
10
|
+
/** Exactly one config oxfmt would not find unaided — pass it with `-c`. */
|
|
11
|
+
| {
|
|
12
|
+
readonly kind: 'explicit';
|
|
13
|
+
readonly path: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Two or more configs, none of them auto-discovered — bare oxfmt (and a repo's own
|
|
17
|
+
* bare `bun run format`) would silently fall back to defaults too, honouring neither.
|
|
18
|
+
* Guessing which one the repo meant would just move the silent-defaults bug here.
|
|
19
|
+
*/
|
|
20
|
+
| {
|
|
21
|
+
readonly kind: 'ambiguous';
|
|
22
|
+
readonly files: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
export declare function resolveOxfmtConfig(root: string): Promise<OxfmtConfigResolution>;
|
|
25
|
+
//# sourceMappingURL=oxfmt-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oxfmt-config.d.ts","sourceRoot":"","sources":["../../src/hooks/oxfmt-config.ts"],"names":[],"mappings":"AAsCA,MAAM,MAAM,qBAAqB;AAC/B,gFAAgF;AAC9E;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3B,8EAA8E;GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3B,2EAA2E;GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACtD;;;;GAIG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAQtE,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAQrF"}
|
package/dist/hooks/report.d.ts
CHANGED
|
@@ -29,6 +29,16 @@ export declare function withoutGitEnv(): Record<string, string | undefined>;
|
|
|
29
29
|
export declare function run(cmd: readonly string[], isolated?: boolean): Promise<number>;
|
|
30
30
|
/** Capture a command's stdout. Used for git plumbing only. */
|
|
31
31
|
export declare function capture(cmd: readonly string[]): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Run a command, relaying its stdout live-enough (after it exits) while also handing the
|
|
34
|
+
* caller the text — gates.ts reads oxfmt's own "on N files" summary from it. `stderr` stays
|
|
35
|
+
* `inherit`: diagnostics (a parse error, "no files matched") must show immediately, and nothing
|
|
36
|
+
* here needs to inspect them.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runCaptured(cmd: readonly string[]): Promise<{
|
|
39
|
+
readonly code: number;
|
|
40
|
+
readonly stdout: string;
|
|
41
|
+
}>;
|
|
32
42
|
/** Capture a command's stdout AND its exit code — git plumbing that answers by status. */
|
|
33
43
|
export declare function probe(cmd: readonly string[]): Promise<{
|
|
34
44
|
code: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/hooks/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sFAAsF;AACtF,MAAM,MAAM,IAAI,GAAG,YAAY,GAAG,UAAU,CAAC;AAO7C;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAIlE;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnF;AAED,8DAA8D;AAC9D,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE;AAED,0FAA0F;AAC1F,wBAAsB,KAAK,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAI7F;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAGlE;AAaD,wBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAErC;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvC;AAED,8FAA8F;AAC9F,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,CAKjE"}
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/hooks/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sFAAsF;AACtF,MAAM,MAAM,IAAI,GAAG,YAAY,GAAG,UAAU,CAAC;AAO7C;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAIlE;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnF;AAED,8DAA8D;AAC9D,wBAAsB,OAAO,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAK7D;AAED,0FAA0F;AAC1F,wBAAsB,KAAK,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAI7F;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAGlE;AAaD,wBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAErC;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvC;AAED,8FAA8F;AAC9F,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,CAKjE"}
|
package/dist/hooks.js
CHANGED
|
@@ -18,6 +18,12 @@ async function run(cmd, isolated = false) {
|
|
|
18
18
|
async function capture(cmd) {
|
|
19
19
|
return (await probe(cmd)).stdout;
|
|
20
20
|
}
|
|
21
|
+
async function runCaptured(cmd) {
|
|
22
|
+
const proc = Bun.spawn([...cmd], { stdout: "pipe", stderr: "inherit" });
|
|
23
|
+
const stdout = await new Response(proc.stdout).text();
|
|
24
|
+
process.stdout.write(stdout);
|
|
25
|
+
return { code: await proc.exited, stdout };
|
|
26
|
+
}
|
|
21
27
|
async function probe(cmd) {
|
|
22
28
|
const proc = Bun.spawn([...cmd], { stdout: "pipe", stderr: "ignore" });
|
|
23
29
|
const stdout = await new Response(proc.stdout).text();
|
|
@@ -89,6 +95,33 @@ async function activateHooks(root, env = process.env) {
|
|
|
89
95
|
// src/hooks/gates.ts
|
|
90
96
|
import { existsSync } from "fs";
|
|
91
97
|
|
|
98
|
+
// src/hooks/oxfmt-config.ts
|
|
99
|
+
import { readdir } from "fs/promises";
|
|
100
|
+
var AUTO_DISCOVERED = [".oxfmtrc.json", ".oxfmtrc.jsonc"];
|
|
101
|
+
var NEEDS_EXPLICIT_CONFIG = [
|
|
102
|
+
".oxfmtrc.ts",
|
|
103
|
+
".oxfmtrc.mts",
|
|
104
|
+
".oxfmtrc.cts",
|
|
105
|
+
".oxfmtrc.js",
|
|
106
|
+
".oxfmtrc.mjs",
|
|
107
|
+
".oxfmtrc.cjs"
|
|
108
|
+
];
|
|
109
|
+
async function present(root, names) {
|
|
110
|
+
const entries = new Set(await readdir(root).catch(() => []));
|
|
111
|
+
return names.filter((name) => entries.has(name));
|
|
112
|
+
}
|
|
113
|
+
async function resolveOxfmtConfig(root) {
|
|
114
|
+
if ((await present(root, AUTO_DISCOVERED)).length > 0)
|
|
115
|
+
return { kind: "auto" };
|
|
116
|
+
const explicit = await present(root, NEEDS_EXPLICIT_CONFIG);
|
|
117
|
+
const [only, ...rest] = explicit;
|
|
118
|
+
if (only === undefined)
|
|
119
|
+
return { kind: "none" };
|
|
120
|
+
if (rest.length === 0)
|
|
121
|
+
return { kind: "explicit", path: `${root}/${only}` };
|
|
122
|
+
return { kind: "ambiguous", files: explicit };
|
|
123
|
+
}
|
|
124
|
+
|
|
92
125
|
// src/hooks/push-plan.ts
|
|
93
126
|
var LEFT_TO_CI = /^(build|smoke)(:|$)/;
|
|
94
127
|
var MAX_DEPTH = 8;
|
|
@@ -317,6 +350,10 @@ async function fingerprints(files) {
|
|
|
317
350
|
}
|
|
318
351
|
|
|
319
352
|
// src/hooks/gates.ts
|
|
353
|
+
function matchedFileCount(stdout) {
|
|
354
|
+
const match = /\bon (\d+) files? using/.exec(stdout);
|
|
355
|
+
return match?.[1] === undefined ? null : Number(match[1]);
|
|
356
|
+
}
|
|
320
357
|
function installed(root, hook) {
|
|
321
358
|
if (existsSync(`${root}/node_modules`))
|
|
322
359
|
return true;
|
|
@@ -327,8 +364,21 @@ async function preCommit(root) {
|
|
|
327
364
|
await scanStagedSecrets();
|
|
328
365
|
if (!installed(root, "pre-commit"))
|
|
329
366
|
return;
|
|
330
|
-
const oxfmt = tool(root, "oxfmt");
|
|
331
367
|
const { formattable, code, partial } = await staged();
|
|
368
|
+
if (formattable.length === 0 && partial.length === 0) {
|
|
369
|
+
ok("pre-commit: nothing staged to format");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const config = await resolveOxfmtConfig(root);
|
|
373
|
+
if (config.kind === "ambiguous") {
|
|
374
|
+
fail("pre-commit", `${String(config.files.length)} oxfmt configs found (${config.files.join(", ")}) and oxfmt auto-discovers none of them`, "keep exactly one .oxfmtrc.* file at the repo root (.oxfmtrc.json is auto-discovered)");
|
|
375
|
+
}
|
|
376
|
+
const oxfmt = [
|
|
377
|
+
...tool(root, "oxfmt"),
|
|
378
|
+
...config.kind === "explicit" ? ["--config", config.path] : [],
|
|
379
|
+
"--no-error-on-unmatched-pattern"
|
|
380
|
+
];
|
|
381
|
+
const oxlint = [...tool(root, "oxlint"), "--no-error-on-unmatched-pattern"];
|
|
332
382
|
if (partial.length > 0) {
|
|
333
383
|
note(`${partial.length} staged file(s) also have unstaged edits; checking without rewriting`);
|
|
334
384
|
if (await run([...oxfmt, "--check", ...partial]) !== 0) {
|
|
@@ -340,21 +390,30 @@ async function preCommit(root) {
|
|
|
340
390
|
return;
|
|
341
391
|
}
|
|
342
392
|
const before = await fingerprints(formattable);
|
|
343
|
-
|
|
393
|
+
const { code: fmtExit, stdout: fmtOut } = await runCaptured([...oxfmt, ...formattable]);
|
|
394
|
+
if (fmtExit !== 0) {
|
|
344
395
|
fail("pre-commit", "oxfmt could not format the staged files", "bun run format");
|
|
345
396
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
397
|
+
const matched = matchedFileCount(fmtOut);
|
|
398
|
+
if (matched !== 0) {
|
|
399
|
+
const after = await fingerprints(formattable);
|
|
400
|
+
const rewritten = formattable.filter((file) => before.get(file) !== after.get(file));
|
|
401
|
+
if (rewritten.length > 0) {
|
|
402
|
+
note(`oxfmt rewrote and restaged ${rewritten.length} file(s): ${rewritten.join(", ")}`);
|
|
403
|
+
if (await run(["git", "add", "--", ...rewritten]) !== 0) {
|
|
404
|
+
fail("pre-commit", "could not restage the formatted files", "git add the listed files");
|
|
405
|
+
}
|
|
352
406
|
}
|
|
353
407
|
}
|
|
354
|
-
if (code.length > 0 && await run([...
|
|
408
|
+
if (code.length > 0 && await run([...oxlint, "--deny-warnings", ...code]) !== 0) {
|
|
355
409
|
fail("pre-commit", `oxlint found problems in ${code.length} staged file(s)`, "bun run lint:fix, then fix by hand what remains");
|
|
356
410
|
}
|
|
357
|
-
|
|
411
|
+
if (matched === 0) {
|
|
412
|
+
note(`${String(formattable.length)} staged file(s) matched a formattable extension, but all are excluded by ignore rules`);
|
|
413
|
+
ok("pre-commit: no formattable staged files");
|
|
414
|
+
} else {
|
|
415
|
+
ok(`pre-commit: ${formattable.length} staged file(s) formatted and linted`);
|
|
416
|
+
}
|
|
358
417
|
}
|
|
359
418
|
function describe(lane) {
|
|
360
419
|
if (lane.kind === "skip")
|
|
@@ -506,5 +565,5 @@ export {
|
|
|
506
565
|
runCommand
|
|
507
566
|
};
|
|
508
567
|
|
|
509
|
-
//# debugId=
|
|
568
|
+
//# debugId=F6FB90E426FFDF3C64756E2164756E21
|
|
510
569
|
//# sourceMappingURL=hooks.js.map
|
package/dist/hooks.js.map
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/hooks/report.ts", "../src/hooks/activate.ts", "../src/hooks/gates.ts", "../src/hooks/push-plan.ts", "../src/hooks/push-range.ts", "../src/hooks/secrets.ts", "../src/hooks/staged.ts", "../src/hooks/install.ts", "../src/hooks.ts"],
|
|
3
|
+
"sources": ["../src/hooks/report.ts", "../src/hooks/activate.ts", "../src/hooks/gates.ts", "../src/hooks/oxfmt-config.ts", "../src/hooks/push-plan.ts", "../src/hooks/push-range.ts", "../src/hooks/secrets.ts", "../src/hooks/staged.ts", "../src/hooks/install.ts", "../src/hooks.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * How a hook talks to whoever triggered it.\n *\n * ★ EVERY FAILURE PRINTS BOTH COMMANDS — the one that fixes it and the one that skips\n * it. A hook that exits non-zero and says nothing teaches `--no-verify` as a reflex,\n * and that switch turns off every check rather than the one that was wrong.\n *\n * ⚠️ Output goes to STDERR. Git hooks share stdout with porcelain in some flows, and a\n * hook that writes there can corrupt what a caller is parsing.\n */\n\n/** The two hooks this package implements. Named exactly as the git hook files are. */\nexport type Hook = 'pre-commit' | 'pre-push';\n\nconst BYPASS: Record<Hook, string> = {\n 'pre-commit': 'git commit --no-verify',\n 'pre-push': 'git push --no-verify',\n};\n\n/**\n * 🔴 A GIT HOOK EXPORTS `GIT_DIR` AND `GIT_INDEX_FILE`, AND EVERYTHING IT SPAWNS\n * INHERITS THEM. Measured 2026-09-22 at the cost of two junk files and a stray commit\n * on this repository's `main`: `pre-push` runs `bun run check`, `check` runs the test\n * suite, and a test that builds a throwaway git repository and commits in it commits\n * into THIS repository instead — `cwd` is ignored once `GIT_DIR` is set. The estate's\n * own `packages/site/tests/checkout.test.ts` already carried a comment warning about\n * exactly this, which is how much a convention is worth.\n * ⛔ So the hook strips them rather than trusting fourteen test suites to remember. The\n * gate must see the repository through `cwd`, the way CI does.\n */\nexport function withoutGitEnv(): Record<string, string | undefined> {\n return Object.fromEntries(\n Object.entries(process.env).filter(([name]) => !name.startsWith('GIT_')),\n );\n}\n\n/**\n * Run a command, streaming its output. Returns its exit code.\n * `isolated` drops the inherited `GIT_*` variables — see `withoutGitEnv`.\n */\nexport async function run(cmd: readonly string[], isolated = false): Promise<number> {\n const proc = Bun.spawn([...cmd], {\n stdout: 'inherit',\n stderr: 'inherit',\n ...(isolated ? { env: withoutGitEnv() } : {}),\n });\n return await proc.exited;\n}\n\n/** Capture a command's stdout. Used for git plumbing only. */\nexport async function capture(cmd: readonly string[]): Promise<string> {\n return (await probe(cmd)).stdout;\n}\n\n/** Capture a command's stdout AND its exit code — git plumbing that answers by status. */\nexport async function probe(cmd: readonly string[]): Promise<{ code: number; stdout: string }> {\n const proc = Bun.spawn([...cmd], { stdout: 'pipe', stderr: 'ignore' });\n const stdout = await new Response(proc.stdout).text();\n return { code: await proc.exited, stdout };\n}\n\n/**\n * Run one pre-push lane through `sh -c` at `root`, as `bun run` would run a script line.\n *\n * ⚠️ `node_modules/.bin` FIRST ON PATH, because a lane opened up from a script is no longer\n * run by `bun run`, which is what used to put it there — `vitest` or `oxfmt` in an expanded\n * script would otherwise be \"command not found\" on a machine without a global copy.\n * 🔴 AND WITHOUT THE HOOK'S `GIT_*` — see `withoutGitEnv`.\n */\nexport async function runLane(command: string, root: string): Promise<number> {\n const env = withoutGitEnv();\n env['PATH'] = `${root}/node_modules/.bin:${env['PATH'] ?? ''}`;\n const proc = Bun.spawn(['sh', '-c', command], {\n cwd: root,\n env,\n stdout: 'inherit',\n stderr: 'inherit',\n });\n return await proc.exited;\n}\n\n/**\n * Resolve a dev tool to the project's own copy.\n *\n * ⚠️ NOT `bunx` BY DEFAULT. On a cache miss `bunx` downloads from the registry, and a\n * git hook that reaches the network mid-commit is a hang waiting for a flaky link.\n * Git runs the hook with the caller's PATH, which has no `node_modules/.bin` (husky\n * used to add it; the hooks no longer run through husky), so the local binary is named\n * outright when it exists and `bunx` is only the fallback.\n */\nexport function tool(root: string, name: string): readonly string[] {\n const local = `${root}/node_modules/.bin/${name}`;\n return Bun.file(local).size > 0 ? [local] : ['bunx', name];\n}\n\n/**\n * ⚠️ `process.stderr.write`, NOT `console`. Two reasons, and the lint rule is the lesser\n * one: a hook shares stdout with git porcelain in some flows, and a synchronous write\n * is the only kind guaranteed to land before `process.exit` below throws the buffer\n * away. Using `console.error` here would also make every consumer of this package\n * need a `no-console` exemption for code they never call directly.\n */\nfunction line(text: string): void {\n process.stderr.write(`${text}\\n`);\n}\n\nexport function ok(what: string): void {\n line(`✓ ${what}`);\n}\n\nexport function note(what: string): void {\n line(` ${what}`);\n}\n\n/** ⛔ ALWAYS GIVE THE FIX. \"lint failed\" is a dead end; the command that repairs it is not. */\nexport function fail(hook: Hook, what: string, fix: string): never {\n line(`\\n✗ ${hook}: ${what}`);\n line(` fix: ${fix}`);\n line(` bypass: ${BYPASS[hook]} — CI still runs the real gate\\n`);\n process.exit(1);\n}\n",
|
|
5
|
+
"/**\n * How a hook talks to whoever triggered it.\n *\n * ★ EVERY FAILURE PRINTS BOTH COMMANDS — the one that fixes it and the one that skips\n * it. A hook that exits non-zero and says nothing teaches `--no-verify` as a reflex,\n * and that switch turns off every check rather than the one that was wrong.\n *\n * ⚠️ Output goes to STDERR. Git hooks share stdout with porcelain in some flows, and a\n * hook that writes there can corrupt what a caller is parsing.\n */\n\n/** The two hooks this package implements. Named exactly as the git hook files are. */\nexport type Hook = 'pre-commit' | 'pre-push';\n\nconst BYPASS: Record<Hook, string> = {\n 'pre-commit': 'git commit --no-verify',\n 'pre-push': 'git push --no-verify',\n};\n\n/**\n * 🔴 A GIT HOOK EXPORTS `GIT_DIR` AND `GIT_INDEX_FILE`, AND EVERYTHING IT SPAWNS\n * INHERITS THEM. Measured 2026-09-22 at the cost of two junk files and a stray commit\n * on this repository's `main`: `pre-push` runs `bun run check`, `check` runs the test\n * suite, and a test that builds a throwaway git repository and commits in it commits\n * into THIS repository instead — `cwd` is ignored once `GIT_DIR` is set. The estate's\n * own `packages/site/tests/checkout.test.ts` already carried a comment warning about\n * exactly this, which is how much a convention is worth.\n * ⛔ So the hook strips them rather than trusting fourteen test suites to remember. The\n * gate must see the repository through `cwd`, the way CI does.\n */\nexport function withoutGitEnv(): Record<string, string | undefined> {\n return Object.fromEntries(\n Object.entries(process.env).filter(([name]) => !name.startsWith('GIT_')),\n );\n}\n\n/**\n * Run a command, streaming its output. Returns its exit code.\n * `isolated` drops the inherited `GIT_*` variables — see `withoutGitEnv`.\n */\nexport async function run(cmd: readonly string[], isolated = false): Promise<number> {\n const proc = Bun.spawn([...cmd], {\n stdout: 'inherit',\n stderr: 'inherit',\n ...(isolated ? { env: withoutGitEnv() } : {}),\n });\n return await proc.exited;\n}\n\n/** Capture a command's stdout. Used for git plumbing only. */\nexport async function capture(cmd: readonly string[]): Promise<string> {\n return (await probe(cmd)).stdout;\n}\n\n/**\n * Run a command, relaying its stdout live-enough (after it exits) while also handing the\n * caller the text — gates.ts reads oxfmt's own \"on N files\" summary from it. `stderr` stays\n * `inherit`: diagnostics (a parse error, \"no files matched\") must show immediately, and nothing\n * here needs to inspect them.\n */\nexport async function runCaptured(\n cmd: readonly string[],\n): Promise<{ readonly code: number; readonly stdout: string }> {\n const proc = Bun.spawn([...cmd], { stdout: 'pipe', stderr: 'inherit' });\n const stdout = await new Response(proc.stdout).text();\n process.stdout.write(stdout);\n return { code: await proc.exited, stdout };\n}\n\n/** Capture a command's stdout AND its exit code — git plumbing that answers by status. */\nexport async function probe(cmd: readonly string[]): Promise<{ code: number; stdout: string }> {\n const proc = Bun.spawn([...cmd], { stdout: 'pipe', stderr: 'ignore' });\n const stdout = await new Response(proc.stdout).text();\n return { code: await proc.exited, stdout };\n}\n\n/**\n * Run one pre-push lane through `sh -c` at `root`, as `bun run` would run a script line.\n *\n * ⚠️ `node_modules/.bin` FIRST ON PATH, because a lane opened up from a script is no longer\n * run by `bun run`, which is what used to put it there — `vitest` or `oxfmt` in an expanded\n * script would otherwise be \"command not found\" on a machine without a global copy.\n * 🔴 AND WITHOUT THE HOOK'S `GIT_*` — see `withoutGitEnv`.\n */\nexport async function runLane(command: string, root: string): Promise<number> {\n const env = withoutGitEnv();\n env['PATH'] = `${root}/node_modules/.bin:${env['PATH'] ?? ''}`;\n const proc = Bun.spawn(['sh', '-c', command], {\n cwd: root,\n env,\n stdout: 'inherit',\n stderr: 'inherit',\n });\n return await proc.exited;\n}\n\n/**\n * Resolve a dev tool to the project's own copy.\n *\n * ⚠️ NOT `bunx` BY DEFAULT. On a cache miss `bunx` downloads from the registry, and a\n * git hook that reaches the network mid-commit is a hang waiting for a flaky link.\n * Git runs the hook with the caller's PATH, which has no `node_modules/.bin` (husky\n * used to add it; the hooks no longer run through husky), so the local binary is named\n * outright when it exists and `bunx` is only the fallback.\n */\nexport function tool(root: string, name: string): readonly string[] {\n const local = `${root}/node_modules/.bin/${name}`;\n return Bun.file(local).size > 0 ? [local] : ['bunx', name];\n}\n\n/**\n * ⚠️ `process.stderr.write`, NOT `console`. Two reasons, and the lint rule is the lesser\n * one: a hook shares stdout with git porcelain in some flows, and a synchronous write\n * is the only kind guaranteed to land before `process.exit` below throws the buffer\n * away. Using `console.error` here would also make every consumer of this package\n * need a `no-console` exemption for code they never call directly.\n */\nfunction line(text: string): void {\n process.stderr.write(`${text}\\n`);\n}\n\nexport function ok(what: string): void {\n line(`✓ ${what}`);\n}\n\nexport function note(what: string): void {\n line(` ${what}`);\n}\n\n/** ⛔ ALWAYS GIVE THE FIX. \"lint failed\" is a dead end; the command that repairs it is not. */\nexport function fail(hook: Hook, what: string, fix: string): never {\n line(`\\n✗ ${hook}: ${what}`);\n line(` fix: ${fix}`);\n line(` bypass: ${BYPASS[hook]} — CI still runs the real gate\\n`);\n process.exit(1);\n}\n",
|
|
6
6
|
"/**\n * Turn the hooks on for a clone and EVERY worktree of it, from `prepare`.\n *\n * 🔴 THE GAP THIS CLOSES, measured 2026-09-23. husky sets `core.hooksPath=.husky/_`, and\n * `.husky/_` is generated and gitignored: it exists only in the worktree where `bun\n * install` ran husky. `core.hooksPath` lives in the clone's SHARED config, so every other\n * worktree — every `git worktree add` an agent makes — points at a directory it does not\n * have, and git runs no hooks there, silently. In homeflare-kit that was every worktree\n * until `bun install` had run husky inside it.\n * ★ THE FIX IS A TRACKED PATH. `core.hooksPath=.husky` is relative, and git resolves a\n * relative hooks path against the worktree running the hook — so each worktree runs its\n * OWN checked-out `.husky/`, present from the moment the worktree exists. Measured on git\n * 2.55: a fresh `git worktree add` ran the tracked hook with no install at all. (The\n * wrapper then needs that worktree's `node_modules` to do its work — install.ts says so\n * out loud rather than failing.)\n * ⛔ IT NEVER FAILS. `prepare` runs inside `bun install`; a hook problem must not break an\n * install. Every outcome, including \"did nothing\", is a message and exit 0.\n */\nimport { probe } from './report.ts';\n\nexport type Activation = {\n /** The hooks path is set (now or already). */\n readonly active: boolean;\n readonly message: string;\n};\n\n/**\n * ⚠️ CI IS SKIPPED ON PURPOSE. Hooks are a local convenience and CI runs the real gate; a\n * runner with hooks on would run them on the commits it makes itself (a release's\n * \"Version Packages\" commit) on a machine that may have no gitleaks.\n */\nfunction inCi(env: Readonly<Record<string, string | undefined>>): boolean {\n const ci = env['CI'];\n return ci !== undefined && ci !== '' && ci !== '0' && ci.toLowerCase() !== 'false';\n}\n\nexport async function activateHooks(\n root: string,\n env: Readonly<Record<string, string | undefined>> = process.env,\n): Promise<Activation> {\n if (inCi(env)) return { active: false, message: 'CI is set — hooks stay off; CI runs the gate' };\n\n // ★ `--show-prefix` answers two questions at once: whether this is a work tree at all\n // (it fails outside one — a tarball install), and where `root` sits inside it.\n const where = await probe(['git', '-C', root, 'rev-parse', '--show-prefix']);\n if (where.code !== 0) return { active: false, message: 'not a git work tree — nothing to do' };\n\n const tracked = await Promise.all(\n ['pre-commit', 'pre-push'].map((name) => Bun.file(`${root}/.husky/${name}`).exists()),\n );\n if (!tracked.some(Boolean)) {\n return { active: false, message: 'no .husky/pre-commit or .husky/pre-push to point git at' };\n }\n\n // ⚠️ RELATIVE TO THE TOP OF THE WORK TREE, which is where git resolves it — a package\n // below the root (`<prefix>.husky`) still points at its own directory.\n const want = `${where.stdout.trim()}.husky`;\n const current = (\n await probe(['git', '-C', root, 'config', '--get', 'core.hooksPath'])\n ).stdout.trim();\n if (current === want) return { active: true, message: `core.hooksPath is already ${want}` };\n\n // ★ NO `--worktree`: the clone's shared config, so ONE install covers every worktree.\n const set = await probe(['git', '-C', root, 'config', 'core.hooksPath', want]);\n if (set.code !== 0) return { active: false, message: 'git config core.hooksPath failed' };\n const was = current === '' ? 'unset' : current;\n return {\n active: true,\n message: `core.hooksPath ${was} → ${want}, for every worktree of this clone`,\n };\n}\n",
|
|
7
|
-
"/**\n * The two gates every HomeFlare repo gets, from one place.\n *\n * ★ THE SPLIT IS BY COST. `pre-commit` touches only the staged files — a secret scan, then\n * format and lint — and is measured in hundreds of milliseconds, so it can run on every\n * commit without anyone resenting it. `pre-push` runs the repository's own `check`\n * narrowed to what the push can affect (push-plan.ts): seconds, once per push.\n * ⚠️ NEITHER IS A GATE. Both are skippable with `--no-verify`, and a worktree has them only\n * once `bun install` has run there. The required checks on `main` stay the gate — CI\n * runs the whole `check`, every test, the build and the smoke test on every pull request.\n */\nimport { existsSync } from 'node:fs';\nimport { type Lane, planLanes } from './push-plan.ts';\nimport { changesEverything, parsePushRefs, pushScope } from './push-range.ts';\nimport { fail, note, ok, run, runLane, tool } from './report.ts';\nimport { scanStagedSecrets } from './secrets.ts';\nimport { fingerprints, staged } from './staged.ts';\n\n/**\n * Format and lint what is staged, restaging only what the formatter rewrote.\n *\n * ⚠️ THIS HOOK REWRITES FILES DURING A COMMIT, and it says so on the line where it\n * happens. Without the restage, oxfmt would fix the worktree while the commit kept\n * the unformatted bytes — CI then fails on a file that reads as correct locally.\n */\n/**\n * Has `bun install` run in this worktree?\n *\n * ⚠️ WITHOUT IT, SKIP — LOUDLY — RATHER THAN IMPROVISE. A fresh worktree now runs its hooks\n * (activate.ts), and in the repo that HOSTS this package they are reached by workspace\n * path, not through node_modules. There `tool()` would fall back to `bunx`, fetching an\n * unpinned oxfmt mid-commit, and a pre-push lane would die on \"command not found\". The\n * wrapper every other repo commits makes the same call one step earlier (install.ts).\n */\nfunction installed(root: string, hook: 'pre-commit' | 'pre-push'): boolean {\n if (existsSync(`${root}/node_modules`)) return true;\n note(`${hook}: no node_modules in this worktree — run 'bun install'; skipping the rest`);\n return false;\n}\n\nexport async function preCommit(root: string): Promise<void> {\n // ⛔ SECRETS FIRST, before anything can rewrite or pass — see secrets.ts. It needs only the\n // gitleaks binary, so it runs even in a worktree nobody has installed yet.\n await scanStagedSecrets();\n if (!installed(root, 'pre-commit')) return;\n const oxfmt = tool(root, 'oxfmt');\n const { formattable, code, partial } = await staged();\n\n // ⛔ Checked, never rewritten — see staged.ts for why `git add` here would be theft.\n if (partial.length > 0) {\n note(`${partial.length} staged file(s) also have unstaged edits; checking without rewriting`);\n if ((await run([...oxfmt, '--check', ...partial])) !== 0) {\n fail(\n 'pre-commit',\n 'those partially staged files are unformatted, and rewriting them would stage your unstaged work',\n 'bun run format, then stage the files you meant to commit',\n );\n }\n }\n\n if (formattable.length === 0) {\n ok('pre-commit: nothing staged to format');\n return;\n }\n\n const before = await fingerprints(formattable);\n if ((await run([...oxfmt, ...formattable])) !== 0) {\n fail('pre-commit', 'oxfmt could not format the staged files', 'bun run format');\n }\n const after = await fingerprints(formattable);\n const rewritten = formattable.filter((file) => before.get(file) !== after.get(file));\n\n if (rewritten.length > 0) {\n note(`oxfmt rewrote and restaged ${rewritten.length} file(s): ${rewritten.join(', ')}`);\n if ((await run(['git', 'add', '--', ...rewritten])) !== 0) {\n fail('pre-commit', 'could not restage the formatted files', 'git add the listed files');\n }\n }\n\n // ⚠️ `--deny-warnings` matches what CI runs. A hook that is laxer than CI is worse\n // than no hook: it certifies a change CI will reject.\n if (code.length > 0 && (await run([...tool(root, 'oxlint'), '--deny-warnings', ...code])) !== 0) {\n fail(\n 'pre-commit',\n `oxlint found problems in ${code.length} staged file(s)`,\n 'bun run lint:fix, then fix by hand what remains',\n );\n }\n\n ok(`pre-commit: ${formattable.length} staged file(s) formatted and linted`);\n}\n\n/** The one-line reason a lane is in the run, printed before it starts. */\nfunction describe(lane: Lane): string {\n if (lane.kind === 'skip') return `skip ${lane.label} — ${lane.why}`;\n if (lane.kind === 'test' && lane.scoped)\n return `run ${lane.command} (only the tests the push can reach)`;\n if (lane.kind === 'test') return `run ${lane.command} (IN FULL)`;\n return `run ${lane.command}`;\n}\n\n/**\n * Run the repository's own `check`, narrowed to what the push can affect.\n *\n * ★ `args` ARE GIT'S: the remote name and URL. `stdin` is git's ref list — see\n * push-range.ts for how the base is chosen and why it never narrows to nothing.\n * ⛔ IT DOES NOT CERTIFY WHAT CI WILL SAY. It certifies that `check`'s own lint and type\n * lanes pass and that every test the pushed files can reach passes. The build, the smoke\n * test and the unreachable tests are CI's, and the success line says so.\n */\nexport async function prePush(root: string, args: readonly string[], stdin: string): Promise<void> {\n const manifest = Bun.file(`${root}/package.json`);\n const pkg = (await manifest.exists())\n ? ((await manifest.json()) as { scripts?: Record<string, string> })\n : {};\n const scripts = pkg.scripts ?? {};\n if (scripts['check'] === undefined) {\n note('pre-push: no `check` script declared in package.json; nothing to run');\n return;\n }\n if (!installed(root, 'pre-push')) return;\n\n const scope = await pushScope(root, args[0] ?? 'origin', parsePushRefs(stdin));\n if (scope.kind === 'empty') {\n ok(`pre-push: ${scope.why} — nothing to check`);\n return;\n }\n if (scope.kind === 'elsewhere') {\n // ⛔ NOT A PASS, AND IT DOES NOT SAY ONE. The working tree is another commit; running the\n // lanes would certify content nobody checked. Failing would teach `--no-verify` for an\n // ordinary push, so it says what it did not do, and CI checks the ref.\n note(`pre-push: ${scope.why} — the working tree is not what is being pushed`);\n note(\n ' NOT CHECKED here; CI checks it. To check it locally, check it out and push from there.',\n );\n return;\n }\n let base: string | undefined;\n if (scope.kind === 'unscoped') {\n note(`pre-push: ${scope.why} — every lane runs, tests in full`);\n } else {\n const global = scope.changed.filter(changesEverything);\n note(`pre-push: ${String(scope.changed.length)} file(s) changed ${scope.why}`);\n if (global.length > 0)\n note(` ${global.join(', ')} changes what every test runs on — tests in full`);\n else base = scope.base;\n }\n\n const lanes = planLanes(scripts, base);\n const started = Bun.nanoseconds();\n let ran = 0;\n for (const lane of lanes) {\n note(describe(lane));\n if (lane.kind === 'skip') continue;\n // 🔴 `runLane` strips the GIT_* this hook inherited — see report.ts.\n if ((await runLane(lane.command, root)) !== 0) {\n fail('pre-push', `\\`${lane.label}\\` failed`, `${lane.command} — until it is green`);\n }\n ran += 1;\n }\n const seconds = ((Bun.nanoseconds() - started) / 1e9).toFixed(1);\n ok(`pre-push: ${String(ran)} lane(s) of \\`check\\` passed in ${seconds}s — CI runs the full gate`);\n}\n",
|
|
7
|
+
"/**\n * The two gates every HomeFlare repo gets, from one place.\n *\n * ★ THE SPLIT IS BY COST. `pre-commit` touches only the staged files — a secret scan, then\n * format and lint — and is measured in hundreds of milliseconds, so it can run on every\n * commit without anyone resenting it. `pre-push` runs the repository's own `check`\n * narrowed to what the push can affect (push-plan.ts): seconds, once per push.\n * ⚠️ NEITHER IS A GATE. Both are skippable with `--no-verify`, and a worktree has them only\n * once `bun install` has run there. The required checks on `main` stay the gate — CI\n * runs the whole `check`, every test, the build and the smoke test on every pull request.\n */\nimport { existsSync } from 'node:fs';\nimport { resolveOxfmtConfig } from './oxfmt-config.ts';\nimport { type Lane, planLanes } from './push-plan.ts';\nimport { changesEverything, parsePushRefs, pushScope } from './push-range.ts';\nimport { fail, note, ok, run, runCaptured, runLane, tool } from './report.ts';\nimport { scanStagedSecrets } from './secrets.ts';\nimport { fingerprints, staged } from './staged.ts';\n\n/**\n * oxfmt's own summary line (\"Finished in 2ms on 0 files using 10 threads.\") is how the hook\n * learns whether `--no-error-on-unmatched-pattern` turned a real run into a no-op — oxfmt has\n * no flag that answers this more directly. `null` (unparsable — a future oxfmt wording change)\n * is treated as \"something ran\": that is the safe direction, since it only ever suppresses the\n * new \"no formattable staged files\" message, never a real failure.\n */\nfunction matchedFileCount(stdout: string): number | null {\n const match = /\\bon (\\d+) files? using/.exec(stdout);\n return match?.[1] === undefined ? null : Number(match[1]);\n}\n\n/**\n * Format and lint what is staged, restaging only what the formatter rewrote.\n *\n * ⚠️ THIS HOOK REWRITES FILES DURING A COMMIT, and it says so on the line where it\n * happens. Without the restage, oxfmt would fix the worktree while the commit kept\n * the unformatted bytes — CI then fails on a file that reads as correct locally.\n */\n/**\n * Has `bun install` run in this worktree?\n *\n * ⚠️ WITHOUT IT, SKIP — LOUDLY — RATHER THAN IMPROVISE. A fresh worktree now runs its hooks\n * (activate.ts), and in the repo that HOSTS this package they are reached by workspace\n * path, not through node_modules. There `tool()` would fall back to `bunx`, fetching an\n * unpinned oxfmt mid-commit, and a pre-push lane would die on \"command not found\". The\n * wrapper every other repo commits makes the same call one step earlier (install.ts).\n */\nfunction installed(root: string, hook: 'pre-commit' | 'pre-push'): boolean {\n if (existsSync(`${root}/node_modules`)) return true;\n note(`${hook}: no node_modules in this worktree — run 'bun install'; skipping the rest`);\n return false;\n}\n\nexport async function preCommit(root: string): Promise<void> {\n // ⛔ SECRETS FIRST, before anything can rewrite or pass — see secrets.ts. It needs only the\n // gitleaks binary, so it runs even in a worktree nobody has installed yet.\n await scanStagedSecrets();\n if (!installed(root, 'pre-commit')) return;\n\n const { formattable, code, partial } = await staged();\n\n // ⚠️ BEFORE RESOLVING A CONFIG: if oxfmt would never run (nothing formattable staged, not\n // even a partially-staged one to `--check`), a config problem — even an unresolved\n // ambiguous one — must not block the commit. That is the exact shape of BUG 1: don't\n // fail on \"there is nothing to do here\".\n if (formattable.length === 0 && partial.length === 0) {\n ok('pre-commit: nothing staged to format');\n return;\n }\n\n const config = await resolveOxfmtConfig(root);\n if (config.kind === 'ambiguous') {\n fail(\n 'pre-commit',\n `${String(config.files.length)} oxfmt configs found (${config.files.join(', ')}) and oxfmt auto-discovers none of them`,\n 'keep exactly one .oxfmtrc.* file at the repo root (.oxfmtrc.json is auto-discovered)',\n );\n }\n // ⛔ BUG, MEASURED 2026-09-23: bare `oxfmt` only finds `.oxfmtrc.json`/`.oxfmtrc.jsonc` on\n // its own — see oxfmt-config.ts. `--config` is added only when the repo's config needs it.\n const oxfmt = [\n ...tool(root, 'oxfmt'),\n ...(config.kind === 'explicit' ? ['--config', config.path] : []),\n // ⛔ BUG, MEASURED 2026-09-23: a staged file wholly excluded by oxfmt's OWN\n // `ignorePatterns` (house `packages/distilled-*/src/**`, say) made oxfmt exit non-zero\n // with \"Expected at least one target file\" — `staged()` classifies by extension only,\n // blind to the repo's ignore rules, so `formattable`/`code` can be 100% ignored files.\n // This flag is oxfmt's own documented answer (`--help`, oxfmt 0.68.0): still fail on a\n // real formatting problem, but not on \"everything here was excluded\".\n '--no-error-on-unmatched-pattern',\n ];\n const oxlint = [...tool(root, 'oxlint'), '--no-error-on-unmatched-pattern'];\n\n // ⛔ Checked, never rewritten — see staged.ts for why `git add` here would be theft.\n if (partial.length > 0) {\n note(`${partial.length} staged file(s) also have unstaged edits; checking without rewriting`);\n if ((await run([...oxfmt, '--check', ...partial])) !== 0) {\n fail(\n 'pre-commit',\n 'those partially staged files are unformatted, and rewriting them would stage your unstaged work',\n 'bun run format, then stage the files you meant to commit',\n );\n }\n }\n\n if (formattable.length === 0) {\n ok('pre-commit: nothing staged to format');\n return;\n }\n\n const before = await fingerprints(formattable);\n const { code: fmtExit, stdout: fmtOut } = await runCaptured([...oxfmt, ...formattable]);\n if (fmtExit !== 0) {\n fail('pre-commit', 'oxfmt could not format the staged files', 'bun run format');\n }\n const matched = matchedFileCount(fmtOut);\n\n if (matched !== 0) {\n const after = await fingerprints(formattable);\n const rewritten = formattable.filter((file) => before.get(file) !== after.get(file));\n\n if (rewritten.length > 0) {\n note(`oxfmt rewrote and restaged ${rewritten.length} file(s): ${rewritten.join(', ')}`);\n if ((await run(['git', 'add', '--', ...rewritten])) !== 0) {\n fail('pre-commit', 'could not restage the formatted files', 'git add the listed files');\n }\n }\n }\n\n // ⚠️ `--deny-warnings` matches what CI runs. A hook that is laxer than CI is worse\n // than no hook: it certifies a change CI will reject. Same ignore-rules shape as oxfmt\n // above: `code` can be entirely excluded by oxlint's own `ignorePatterns`.\n if (code.length > 0 && (await run([...oxlint, '--deny-warnings', ...code])) !== 0) {\n fail(\n 'pre-commit',\n `oxlint found problems in ${code.length} staged file(s)`,\n 'bun run lint:fix, then fix by hand what remains',\n );\n }\n\n if (matched === 0) {\n note(\n `${String(formattable.length)} staged file(s) matched a formattable extension, but all are excluded by ignore rules`,\n );\n ok('pre-commit: no formattable staged files');\n } else {\n ok(`pre-commit: ${formattable.length} staged file(s) formatted and linted`);\n }\n}\n\n/** The one-line reason a lane is in the run, printed before it starts. */\nfunction describe(lane: Lane): string {\n if (lane.kind === 'skip') return `skip ${lane.label} — ${lane.why}`;\n if (lane.kind === 'test' && lane.scoped)\n return `run ${lane.command} (only the tests the push can reach)`;\n if (lane.kind === 'test') return `run ${lane.command} (IN FULL)`;\n return `run ${lane.command}`;\n}\n\n/**\n * Run the repository's own `check`, narrowed to what the push can affect.\n *\n * ★ `args` ARE GIT'S: the remote name and URL. `stdin` is git's ref list — see\n * push-range.ts for how the base is chosen and why it never narrows to nothing.\n * ⛔ IT DOES NOT CERTIFY WHAT CI WILL SAY. It certifies that `check`'s own lint and type\n * lanes pass and that every test the pushed files can reach passes. The build, the smoke\n * test and the unreachable tests are CI's, and the success line says so.\n */\nexport async function prePush(root: string, args: readonly string[], stdin: string): Promise<void> {\n const manifest = Bun.file(`${root}/package.json`);\n const pkg = (await manifest.exists())\n ? ((await manifest.json()) as { scripts?: Record<string, string> })\n : {};\n const scripts = pkg.scripts ?? {};\n if (scripts['check'] === undefined) {\n note('pre-push: no `check` script declared in package.json; nothing to run');\n return;\n }\n if (!installed(root, 'pre-push')) return;\n\n const scope = await pushScope(root, args[0] ?? 'origin', parsePushRefs(stdin));\n if (scope.kind === 'empty') {\n ok(`pre-push: ${scope.why} — nothing to check`);\n return;\n }\n if (scope.kind === 'elsewhere') {\n // ⛔ NOT A PASS, AND IT DOES NOT SAY ONE. The working tree is another commit; running the\n // lanes would certify content nobody checked. Failing would teach `--no-verify` for an\n // ordinary push, so it says what it did not do, and CI checks the ref.\n note(`pre-push: ${scope.why} — the working tree is not what is being pushed`);\n note(\n ' NOT CHECKED here; CI checks it. To check it locally, check it out and push from there.',\n );\n return;\n }\n let base: string | undefined;\n if (scope.kind === 'unscoped') {\n note(`pre-push: ${scope.why} — every lane runs, tests in full`);\n } else {\n const global = scope.changed.filter(changesEverything);\n note(`pre-push: ${String(scope.changed.length)} file(s) changed ${scope.why}`);\n if (global.length > 0)\n note(` ${global.join(', ')} changes what every test runs on — tests in full`);\n else base = scope.base;\n }\n\n const lanes = planLanes(scripts, base);\n const started = Bun.nanoseconds();\n let ran = 0;\n for (const lane of lanes) {\n note(describe(lane));\n if (lane.kind === 'skip') continue;\n // 🔴 `runLane` strips the GIT_* this hook inherited — see report.ts.\n if ((await runLane(lane.command, root)) !== 0) {\n fail('pre-push', `\\`${lane.label}\\` failed`, `${lane.command} — until it is green`);\n }\n ran += 1;\n }\n const seconds = ((Bun.nanoseconds() - started) / 1e9).toFixed(1);\n ok(`pre-push: ${String(ran)} lane(s) of \\`check\\` passed in ${seconds}s — CI runs the full gate`);\n}\n",
|
|
8
|
+
"/**\n * Which `.oxfmtrc.*` file governs this repo — so the hook can name it explicitly.\n *\n * ⛔ BUG, MEASURED 2026-09-23 (homeflare-desktop docs agent): the shared pre-commit ran\n * bare `oxfmt`, and bare `oxfmt` auto-discovers only `.oxfmtrc.json` and `.oxfmtrc.jsonc`.\n * Checked against the pinned oxfmt 0.68.0 directly: a repo with only `.oxfmtrc.mjs` and\n * no `-c` prints \"No config found, using defaults\" and formats with oxfmt's built-in\n * style, silently — even though `oxfmt --help` lists `.ts/.mts/.cts/.js/.mjs/.cjs` as\n * valid `--config` targets. Desktop's fix was renaming to `.oxfmtrc.json`; this fixes the\n * hook so a repo does not have to.\n * ★ WHY A FILE SCAN, NOT A PACKAGE.JSON READ. The repo's own `format`/`check` script names\n * the config it wants (or relies on auto-discovery finding `.oxfmtrc.json`), but its exact\n * invocation shape varies per repo. Finding the config file on disk and handing it to\n * oxfmt directly gets the same result without parsing an arbitrary shell command.\n * ⛔ EVERY `.oxfmtrc.*` LIVES AT THE REPO ROOT TODAY (checked: every repo in the estate has\n * exactly one, at root — no nested per-package overrides). Explicit `-c` disables oxfmt's\n * own nested-config search (measured: a nested `.oxfmtrc.json` was ignored once `-c` named\n * the root one), so this only scans `root` itself — it would misfire on a repo that added a\n * package-level override, which none currently do.\n */\nimport { readdir } from 'node:fs/promises';\n\n/** Auto-discovered by bare `oxfmt` — nothing for the hook to pass. */\nconst AUTO_DISCOVERED = ['.oxfmtrc.json', '.oxfmtrc.jsonc'];\n\n/**\n * Accepted by `oxfmt -c/--config` (per `oxfmt --help`, oxfmt 0.68.0) but never found on its\n * own — checked empirically for each extension. Listed in the order `--help` documents them.\n */\nconst NEEDS_EXPLICIT_CONFIG = [\n '.oxfmtrc.ts',\n '.oxfmtrc.mts',\n '.oxfmtrc.cts',\n '.oxfmtrc.js',\n '.oxfmtrc.mjs',\n '.oxfmtrc.cjs',\n];\n\nexport type OxfmtConfigResolution =\n /** `.oxfmtrc.json` or `.oxfmtrc.jsonc` exists — bare oxfmt already finds it. */\n | { readonly kind: 'auto' }\n /** No `.oxfmtrc.*` at all — let oxfmt use its built-in defaults, as today. */\n | { readonly kind: 'none' }\n /** Exactly one config oxfmt would not find unaided — pass it with `-c`. */\n | { readonly kind: 'explicit'; readonly path: string }\n /**\n * Two or more configs, none of them auto-discovered — bare oxfmt (and a repo's own\n * bare `bun run format`) would silently fall back to defaults too, honouring neither.\n * Guessing which one the repo meant would just move the silent-defaults bug here.\n */\n | { readonly kind: 'ambiguous'; readonly files: readonly string[] };\n\n/** Every `.oxfmtrc.*` file actually present at `root`, in a fixed, deterministic order. */\nasync function present(root: string, names: readonly string[]): Promise<readonly string[]> {\n const entries = new Set(await readdir(root).catch(() => []));\n return names.filter((name) => entries.has(name));\n}\n\nexport async function resolveOxfmtConfig(root: string): Promise<OxfmtConfigResolution> {\n if ((await present(root, AUTO_DISCOVERED)).length > 0) return { kind: 'auto' };\n\n const explicit = await present(root, NEEDS_EXPLICIT_CONFIG);\n const [only, ...rest] = explicit;\n if (only === undefined) return { kind: 'none' };\n if (rest.length === 0) return { kind: 'explicit', path: `${root}/${only}` };\n return { kind: 'ambiguous', files: explicit };\n}\n",
|
|
8
9
|
"/**\n * What `pre-push` runs: the repository's own `check`, with the expensive lanes narrowed to\n * the push or left to CI.\n *\n * ★ IT READS `check` RATHER THAN NAMING TOOLS. `check` is the command CI runs, so the lanes\n * here are that command's lanes — a repository that adds a step to `check` gets it in the\n * hook with no change to this package. Hard-coding `tsc` and `bun test` would drift from\n * whichever repository added a step first, and certify a push CI rejects.\n * ★ THREE THINGS CHANGE, AND ONLY THREE:\n * 1. every `bun test …` becomes `bun test … --changed=<base>` — Bun's own import-graph\n * answer to \"which test files can these changed files reach\" (bun 1.4, measured\n * 2026-09-23: 11 changed files ran 2 of 246 test files in homeflare-kit);\n * 2. `build` and `smoke` scripts are skipped — minutes in a big workspace, and CI runs\n * both on every pull request;\n * 3. everything else (lint, types, a `--check` script) runs exactly as `check` spells it.\n * They are seconds, whole-program by nature, and deterministic.\n * ⛔ PURE. No git, no filesystem, no process: the whole contract is a function of the\n * scripts table and the base, so the estate's real `check` shapes are pinned by a table\n * test (tests/hooks-push-plan.test.ts) instead of by a live push.\n */\n\n/** One step of the pre-push run. */\nexport type Lane =\n /** Run `command` through `sh -c` at the repository root. */\n | { readonly kind: 'run'; readonly label: string; readonly command: string }\n /** A test runner; `scoped` says whether it was narrowed to the push. */\n | {\n readonly kind: 'test';\n readonly label: string;\n readonly command: string;\n readonly scoped: boolean;\n }\n /** Not run here, and why. */\n | { readonly kind: 'skip'; readonly label: string; readonly why: string };\n\n/**\n * ⚠️ BY NAME, AND ONLY THESE. A build in homeflare-kit is every package; a smoke test packs\n * and installs tarballs. Both are CI jobs on every pull request, and neither says anything\n * a type check and the reachable tests did not already say about a typical push.\n */\nconst LEFT_TO_CI = /^(build|smoke)(:|$)/;\n\n/** Recursion guard: a script that names itself, directly or through another, stops here. */\nconst MAX_DEPTH = 8;\n\n/**\n * Split a script on top-level `&&`, or return undefined when it is anything else.\n *\n * ⛔ UNDEFINED MEANS \"RUN IT WHOLE\". `||`, `;`, a pipe, a redirect, a background `&` or a\n * substitution each change what the pieces mean together, and a hook that re-plumbed them\n * would run something the repository never wrote. Quoted text is left alone, so\n * `--path-ignore-patterns=\"homeflare-*\\/**\"` survives intact.\n */\nexport function andChain(script: string): readonly string[] | undefined {\n const parts: string[] = [];\n let quote: string | undefined;\n let current = '';\n for (let i = 0; i < script.length; i++) {\n const char = script[i] ?? '';\n if (quote !== undefined) {\n if (char === quote) quote = undefined;\n current += char;\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '&' && script[i + 1] === '&') {\n parts.push(current.trim());\n current = '';\n i += 1;\n continue;\n }\n if ('|;&<>`'.includes(char) || (char === '$' && script[i + 1] === '(')) return undefined;\n current += char;\n }\n if (quote !== undefined) return undefined;\n parts.push(current.trim());\n return parts.some((part) => part === '') ? undefined : parts;\n}\n\n/** The script a segment names: `bun run x`, `npm run x`, or `npm test`. Else undefined. */\nfunction scriptRef(segment: string, scripts: Readonly<Record<string, string>>): string | undefined {\n const words = segment.split(/\\s+/);\n if (words.length === 2 && words[0] === 'npm' && words[1] === 'test') return 'test';\n const [runner, verb, name] = words;\n if (words.length !== 3 || verb !== 'run' || name === undefined) return undefined;\n if (runner !== 'bun' && runner !== 'npm') return undefined;\n return Object.hasOwn(scripts, name) ? name : undefined;\n}\n\n/** `bun test …` — the runner, not a script called `test`. */\nconst isBunTest = (segment: string): boolean => /^bun\\s+test(\\s|$)/.test(segment);\n\nfunction testLane(segment: string, base: string | undefined): Lane {\n return base === undefined\n ? { kind: 'test', label: segment, command: segment, scoped: false }\n : { kind: 'test', label: segment, command: `${segment} --changed=${base}`, scoped: true };\n}\n\nfunction expand(\n script: string,\n scripts: Readonly<Record<string, string>>,\n base: string | undefined,\n depth: number,\n): readonly Lane[] | undefined {\n const segments = andChain(script);\n if (segments === undefined || depth > MAX_DEPTH) return undefined;\n const lanes: Lane[] = [];\n for (const segment of segments) {\n if (isBunTest(segment)) {\n lanes.push(testLane(segment, base));\n continue;\n }\n const name = scriptRef(segment, scripts);\n if (name === undefined) {\n lanes.push({ kind: 'run', label: segment, command: segment });\n continue;\n }\n if (LEFT_TO_CI.test(name)) {\n lanes.push({ kind: 'skip', label: segment, why: 'CI runs it on every pull request' });\n continue;\n }\n const inner = expand(scripts[name] ?? '', scripts, base, depth + 1);\n if (inner !== undefined && inner.some((lane) => lane.kind !== 'run')) {\n // Something inside needs narrowing or skipping: open the script up.\n lanes.push(...inner);\n } else if (/^test(:|$)/.test(name)) {\n // ⚠️ A TEST SCRIPT WITH NO `bun test` IN IT RUNS IN FULL, AND SAYS SO — vitest with\n // coverage thresholds (a narrowed run would fail them), `node --test`, anything piped.\n // Narrowing it would mean rewriting a command this package does not understand.\n lanes.push({ kind: 'test', label: segment, command: segment, scoped: false });\n } else {\n // ★ A SCRIPT WITH NOTHING TO NARROW RUNS UNDER ITS OWN NAME. `bun run lint` keeps\n // Bun's PATH handling and reads the way the repository wrote it.\n lanes.push({ kind: 'run', label: segment, command: segment });\n }\n }\n return lanes;\n}\n\n/**\n * The lanes `pre-push` runs for this `scripts` table.\n *\n * `base` is the commit the push is measured from; `undefined` runs every test lane in full\n * (an unknown base, or a push that changes what every test runs on).\n * ⚠️ AN EMPTY LIST MEANS NO `check` SCRIPT — the caller reports that; it is not a pass.\n */\nexport function planLanes(\n scripts: Readonly<Record<string, string>>,\n base: string | undefined,\n): readonly Lane[] {\n const check = scripts['check'];\n if (check === undefined) return [];\n // ⚠️ A `check` THAT IS NOT A PLAIN `&&` CHAIN IS RUN WHOLE, tests and all, and reported\n // as unscoped. No estate repository has one (surveyed 2026-09-23); the fallback exists so\n // an unusual one is checked rather than skipped.\n const whole: Lane = {\n kind: 'test',\n label: 'bun run check',\n command: 'bun run check',\n scoped: false,\n };\n return expand(check, scripts, base, 0) ?? [whole];\n}\n",
|
|
9
10
|
"/**\n * What a push changes: the base it is measured from, and the files between that base and\n * the commit being pushed.\n *\n * ★ THE BASE COMES FROM GIT, NOT FROM A GUESS. A pre-push hook reads\n * `<local ref> <local sha> <remote ref> <remote sha>` on stdin. The remote sha is exactly\n * \"what the remote has now\", so a second push to a branch checks only what is new.\n * 🔴 A BRANCH-NAME BASE IS VACUOUS ON THE BASE BRANCH. Measured in the house monorepo\n * 2026-09-08: `turbo --affected` compares against `main`, so on `main` itself it selected\n * zero packages and a real two-commit push ran zero tests. Taking the base from stdin\n * cannot do that — the remote sha is never the commit being pushed.\n * ⛔ AN UNKNOWN BASE WIDENS, IT NEVER NARROWS TO NOTHING. No merge base (a shallow clone, a\n * rewritten history), no remote-tracking branch: every lane runs, tests in full. A gate\n * whose base is unusable must do MORE work, never quietly run nothing.\n */\nimport { probe } from './report.ts';\n\n/** One line of git's pre-push stdin. */\nexport type PushRef = {\n readonly localRef: string;\n readonly localSha: string;\n readonly remoteRef: string;\n readonly remoteSha: string;\n};\n\nexport type PushScope =\n /** The push changes no file (a deletion, or a branch at its base): nothing to check. */\n | { readonly kind: 'empty'; readonly why: string }\n /** Measured from `base`: `changed` is every path that differs between it and the tip. */\n | {\n readonly kind: 'scoped';\n readonly base: string;\n readonly changed: readonly string[];\n readonly why: string;\n }\n /** No usable base: run every lane, tests in full. */\n | { readonly kind: 'unscoped'; readonly why: string }\n /** The pushed commit is not what is checked out, so the working tree cannot vouch for it. */\n | { readonly kind: 'elsewhere'; readonly why: string };\n\n/** git's \"no such ref\" sentinel — 40 zeros (64 under SHA-256). */\nconst ZERO = /^0+$/;\n\nexport function parsePushRefs(stdin: string): readonly PushRef[] {\n const refs: PushRef[] = [];\n for (const line of stdin.split('\\n')) {\n const [localRef, localSha, remoteRef, remoteSha] = line.trim().split(/\\s+/);\n if (localRef && localSha && remoteRef && remoteSha) {\n refs.push({ localRef, localSha, remoteRef, remoteSha });\n }\n }\n return refs;\n}\n\nconst short = (sha: string): string => sha.slice(0, 9);\n\nasync function ok(root: string, args: readonly string[]): Promise<boolean> {\n return (await probe(['git', '-C', root, ...args])).code === 0;\n}\n\nasync function out(root: string, args: readonly string[]): Promise<string | undefined> {\n const result = await probe(['git', '-C', root, ...args]);\n return result.code === 0 ? result.stdout.trim() : undefined;\n}\n\n/**\n * The remote's default branch as a local ref: `<remote>/HEAD` when the clone recorded it,\n * else `<remote>/main`, else `<remote>/master`.\n * ⚠️ `refs/remotes/<remote>/HEAD` IS OFTEN ABSENT — `git clone` sets it, `git init` plus\n * `git remote add` does not — so the fallbacks are the common path, not the rare one.\n */\nasync function defaultBranch(root: string, remote: string): Promise<string | undefined> {\n const head = await out(root, ['symbolic-ref', '--quiet', `refs/remotes/${remote}/HEAD`]);\n if (head !== undefined && head !== '') return head;\n for (const name of ['main', 'master']) {\n const ref = `refs/remotes/${remote}/${name}`;\n if (await ok(root, ['rev-parse', '--verify', '--quiet', ref])) return ref;\n }\n return undefined;\n}\n\n/**\n * Where the pushed commit is measured from.\n *\n * ★ A FAST-FORWARD IS MEASURED FROM THE REMOTE SHA — only what this push adds.\n * ⚠️ ANYTHING ELSE IS MEASURED FROM THE MERGE BASE WITH THE DEFAULT BRANCH: a new branch\n * (zero remote sha), a remote sha this clone has not fetched, or a force-push after a\n * rebase — where diffing against the old tip would drag in everything `main` gained since.\n */\nasync function baseFor(\n root: string,\n remote: string,\n ref: PushRef,\n): Promise<{ base: string; why: string } | { why: string }> {\n const { localSha, remoteSha } = ref;\n if (\n !ZERO.test(remoteSha) &&\n (await ok(root, ['cat-file', '-e', `${remoteSha}^{commit}`])) &&\n (await ok(root, ['merge-base', '--is-ancestor', remoteSha, localSha]))\n ) {\n return { base: remoteSha, why: `since ${short(remoteSha)}, what ${remote} has now` };\n }\n const branch = await defaultBranch(root, remote);\n if (branch === undefined) return { why: `no ${remote}/main or ${remote}/master to measure from` };\n const mergeBase = await out(root, ['merge-base', localSha, branch]);\n if (mergeBase === undefined || mergeBase === '') {\n return { why: `no merge base with ${branch} (a shallow clone, or unrelated history)` };\n }\n const name = branch.replace(/^refs\\/remotes\\//, '');\n return { base: mergeBase, why: `since ${short(mergeBase)}, where this branch left ${name}` };\n}\n\n/**\n * Resolve the push git described on stdin. With no stdin (a manual run) the push is\n * `HEAD` to a new branch — measured from the merge base with the default branch.\n * 🔴 THE LANES RUN ON THE WORKING TREE, SO ONLY A PUSH OF `HEAD` CAN BE CHECKED HERE. Found\n * in review 2026-09-23 and reproduced: `git push origin broken` from a clean `main`\n * measured the right files, then ran `bun test --changed` against `main`'s tree, found\n * nothing, and printed \"passed\". A ref that is not checked out now comes back\n * `elsewhere`, which the caller reports as NOT CHECKED — never as a pass. (The old\n * whole-`check` hook had the same blind spot; it just ran unrelated tests while in it.)\n * ⚠️ WITH SEVERAL REFS, THE ONE AT `HEAD` IS MEASURED and the rest are named as unchecked.\n */\nexport async function pushScope(\n root: string,\n remote: string,\n refs: readonly PushRef[],\n): Promise<PushScope> {\n const pushed = refs.filter((ref) => !ZERO.test(ref.localSha));\n if (refs.length > 0 && pushed.length === 0) {\n return { kind: 'empty', why: 'this push only deletes refs' };\n }\n const head = await out(root, ['rev-parse', 'HEAD']);\n const atHead = pushed.find((ref) => ref.localSha === head);\n const others = pushed.filter((ref) => ref !== atHead).map((ref) => ref.localRef);\n if (pushed.length > 0 && atHead === undefined) {\n return {\n kind: 'elsewhere',\n why: `pushing ${others.join(', ')}, but the checkout is at ${short(head ?? '?')}`,\n };\n }\n const ref = atHead ?? {\n localRef: 'HEAD',\n localSha: head ?? 'HEAD',\n remoteRef: '',\n remoteSha: '0'.repeat(40),\n };\n const found = await baseFor(root, remote, ref);\n const also = others.length > 0 ? `; ${others.join(', ')} not checked here` : '';\n if (!('base' in found)) return { kind: 'unscoped', why: `${found.why}${also}` };\n\n const diff = await probe([\n 'git',\n '-C',\n root,\n 'diff',\n '--name-only',\n '-z',\n found.base,\n ref.localSha,\n ]);\n if (diff.code !== 0) return { kind: 'unscoped', why: `git diff ${short(found.base)} failed` };\n const changed = diff.stdout.split('\\0').filter((path) => path !== '');\n if (changed.length === 0) return { kind: 'empty', why: `no file differs ${found.why}${also}` };\n return { kind: 'scoped', base: found.base, changed, why: `${found.why}${also}` };\n}\n\n/**\n * A path that changes what EVERY test runs on: a manifest, a lockfile, Bun's config, a\n * tsconfig. Bun's `--changed` follows imports and none of these is imported — measured\n * 2026-09-23, editing package.json selected 0 of 246 test files — so a push touching one\n * runs the tests in full rather than none of them.\n */\nexport function changesEverything(path: string): boolean {\n const name = path.split('/').pop() ?? '';\n return /^(package\\.json|bun\\.lockb?|bunfig\\.toml|tsconfig.*\\.json)$/.test(name);\n}\n",
|
|
10
11
|
"/**\n * Refuse to commit anything that looks like a credential.\n *\n * ⛔ FIRST GATE, ALWAYS. A secret that reaches a public repository is compromised the moment\n * it is pushed — rotating it is the only remedy, and rewriting history does not help\n * because the object is already fetched and mirrored. Every other check can be fixed\n * after the fact; this one cannot. Moved here from homeflare-kit's own\n * scripts/hooks/secrets.ts (2026-09-23) so every repository commits under it, not one.\n * ★ gitleaks IS THE TOOL, not a hand-rolled regex list. It ships hundreds of maintained rules\n * and an entropy engine, and it reads the repository's own `.gitleaks.toml` when there is\n * one; a homegrown pattern set covers the token shapes its author thought of.\n * ⚠️ IT IS A GO BINARY, NOT AN npm PACKAGE — nothing in package.json can install it, so a\n * missing binary is explained rather than surfacing as \"command not found\".\n * ⚠️ `git --staged`, NOT `protect`. Measured 2026-09-15 on gitleaks 8.30.1: `protect` still\n * runs, but the documented surface is `gitleaks git` / `dir` / `stdin`.\n */\nimport { fail, ok, run } from './report.ts';\n\nconst INSTALL = 'brew install gitleaks — Linux: https://github.com/gitleaks/gitleaks/releases';\n\nexport async function scanStagedSecrets(): Promise<void> {\n // ⛔ NOT A SILENT SKIP. A secret scan that quietly does nothing is worse than none: it\n // reads, in a log and in a reviewer's head, as coverage that does not exist.\n if (Bun.which('gitleaks') === null) {\n fail(\n 'pre-commit',\n 'gitleaks is not installed, so the staged changes were NOT scanned',\n INSTALL,\n );\n }\n // `--redact`, so a real finding never prints the secret into a scrollback, a CI log, or an\n // agent's context.\n const code = await run(['gitleaks', 'git', '--staged', '--redact', '--no-banner', '.']);\n if (code !== 0) {\n fail(\n 'pre-commit',\n 'gitleaks found a secret in the staged changes',\n 'remove it, then ROTATE it — assume anything committed is already compromised',\n );\n }\n ok('pre-commit: gitleaks found no secret in the staged changes');\n}\n",
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
"/**\n * Adoption: the one wrapper every repo commits, and the check that it has not drifted.\n *\n * ★ WHY A WRAPPER AT ALL. git can only run a file that is present in the worktree, so\n * something must be committed per repo. This keeps that something to a delegation\n * whose text is owned HERE — the behaviour lives in one package, and a repo that\n * edits its copy is reported as drift rather than quietly diverging.\n * ★ ONE FILE, TWO NAMES. The wrapper reads the hook name from `$0`, so `pre-commit`\n * and `pre-push` are byte-identical and there is a single text to keep in step.\n * ★ THE DIRECTORY KEEPS HUSKY'S NAME, NOT HUSKY. `.husky/` is where the estate's hook files\n * already live (homeflare-kit, homeflare-alerts, the house monorepo); since 2026-09-23 git\n * runs them directly through `core.hooksPath` — see activate.ts for why husky's own\n * `.husky/_` left every fresh worktree without hooks.\n */\nimport { chmod, mkdir, stat } from 'node:fs/promises';\n\n/** The hook files this package installs, in the order a contributor meets them. */\nexport const HOOK_NAMES = ['pre-commit', 'pre-push'] as const;\n\n/** Where the runner lives once `bun install` has run. Relative: git runs hooks at the root. */\nconst RUNNER = 'node_modules/@homeflare/config/bin/hooks.ts';\n\n/** What a consumer's `prepare` script runs, so every `bun install` activates the hooks. */\nexport const PREPARE: string = `bun ${RUNNER} activate`;\n\n/**\n * ⚠️ IT EXITS 0 WHEN THE RUNNER IS ABSENT. A worktree with no `node_modules` would\n * otherwise fail every commit with a module-resolution error, and the first thing\n * anyone would do is delete the hook. Failing open is the right trade for a\n * convenience; the required checks on `main` are what must fail closed.\n * ★ `\"$@\"` AND STDIN PASS THROUGH. `pre-push` reads the remote name from its first argument\n * and the pushed refs from stdin (push-range.ts); `exec` keeps both.\n * ⚠️ NO SHEBANG, AND THAT IS MEASURED, NOT FORGOTTEN: git 2.55 runs an executable hook that\n * has none through `sh` (2026-09-23), and the husky-era files in the estate have none.\n */\nexport const HUSKY_HOOK: string = `# HomeFlare shared git hook. The behaviour lives in @homeflare/config, not in this file,\n# and the same bytes are installed as .husky/pre-commit and .husky/pre-push — the hook\n# name comes from $0, and git's arguments and stdin pass straight through.\n#\n# ⚠️ A hook is a local convenience, not a gate: it is skippable with --no-verify, and a\n# worktree runs it only once \\`bun install\\` has run there. The required checks on main\n# stay the gate.\n#\n# Regenerate this file with: bun ${RUNNER} install\nhook=\"${RUNNER}\"\nif [ ! -f \"$hook\" ]; then\n echo \"homeflare hooks: $hook is missing — run 'bun install' in this worktree; skipping\" >&2\n exit 0\nfi\nexec bun \"$hook\" \"$(basename \"$0\")\" \"$@\"\n`;\n\n/** Write the wrapper into `.husky/`. Returns the paths written, relative to the project. */\nexport async function installHooks(projectDir: string): Promise<readonly string[]> {\n await mkdir(`${projectDir}/.husky`, { recursive: true });\n const written: string[] = [];\n for (const name of HOOK_NAMES) {\n const path = `${projectDir}/.husky/${name}`;\n await Bun.write(path, HUSKY_HOOK);\n // ⛔ THE EXECUTE BIT IS REQUIRED. git runs a `core.hooksPath` file directly and IGNORES\n // a non-executable hook, with nothing but an advice line to say so.\n await chmod(path, 0o755);\n written.push(`.husky/${name}`);\n }\n return written;\n}\n\n/**\n * Report what stops this project's hooks from working. Empty means adopted.\n *\n * ⛔ DELIBERATELY NOT PART OF `checkProject`. Every repo in the estate runs that checker\n * from a test; folding hook conformance into it would turn every repo that has not\n * adopted yet red on `main` in the same commit. A repo opts in by calling this.\n */\nexport async function problemsInHooks(projectDir: string): Promise<readonly string[]> {\n const problems: string[] = [];\n const manifest = Bun.file(`${projectDir}/package.json`);\n\n if (!(await manifest.exists())) return ['package.json: missing'];\n const pkg = (await manifest.json()) as { scripts?: Record<string, string> };\n\n const prepare = pkg.scripts?.['prepare'] ?? '';\n if (!/bin\\/hooks\\.ts activate/.test(prepare)) {\n problems.push(\n `package.json: \"prepare\" does not run \\`${PREPARE}\\` — no clone or worktree gets hooks`,\n );\n }\n // ⚠️ husky POINTS core.hooksPath AT AN UNTRACKED `.husky/_` that exists only where it ran —\n // the gap activate.ts closes. Left in `prepare`, it undoes the activation.\n if (/\\bhusky\\b/.test(prepare)) {\n problems.push(\n 'package.json: \"prepare\" still runs husky, which re-points core.hooksPath at .husky/_',\n );\n }\n\n for (const name of HOOK_NAMES) {\n const path = `${projectDir}/.husky/${name}`;\n const file = Bun.file(path);\n if (!(await file.exists())) {\n problems.push(`.husky/${name}: missing — run \\`bun ${RUNNER} install\\``);\n continue;\n }\n if ((await file.text()) !== HUSKY_HOOK) {\n problems.push(\n `.husky/${name}: differs from the @homeflare/config wrapper — run \\`bun ${RUNNER} install\\`, or change it in the package`,\n );\n }\n if (((await stat(path)).mode & 0o111) === 0) {\n problems.push(`.husky/${name}: not executable, so git ignores it — chmod +x it and commit`);\n }\n }\n\n return problems;\n}\n",
|
|
13
14
|
"/**\n * The HomeFlare git hooks, as a package.\n *\n * ★ WHY THIS IS NOT A SCRIPT IN EVERY REPO. Fourteen copies of a hook script is the\n * exact drift `@homeflare/config` exists to prevent: the copies diverge, nobody\n * notices, and two repos disagree about what a commit must satisfy. Here the repo\n * commits a delegating wrapper and the behaviour ships with the package, so changing\n * the rule is one release and a version bump rather than fourteen edits.\n * ★ TRACKED `.husky/`, RUN BY GIT THROUGH `core.hooksPath` — NOT husky's `.husky/_`. Since\n * 2026-09-23 the mechanism is the one that reaches every worktree; activate.ts has the\n * measurement. The files keep the directory name the estate already uses.\n * ★ PRE-PUSH IS SCOPED TO THE PUSH (push-range.ts, push-plan.ts): the repository's own\n * `check`, with `bun test` narrowed to the tests the pushed files can reach and the build\n * and smoke test left to CI. Many agents push to many repositories at once; a pre-push\n * that re-ran every suite was the one people learned to skip.\n *\n * Usage from a hook file — see `HUSKY_HOOK`:\n *\n * bun node_modules/@homeflare/config/bin/hooks.ts pre-commit\n */\nimport { activateHooks } from './hooks/activate.ts';\nimport { preCommit, prePush } from './hooks/gates.ts';\nimport { HOOK_NAMES, HUSKY_HOOK, PREPARE, installHooks, problemsInHooks } from './hooks/install.ts';\nimport { type Lane, planLanes } from './hooks/push-plan.ts';\nimport { type Hook, note, ok } from './hooks/report.ts';\n\nexport { HOOK_NAMES, HUSKY_HOOK, PREPARE, activateHooks, installHooks, planLanes, problemsInHooks };\nexport type { Hook, Lane };\n\n/** The commands `bin/hooks.ts` accepts. */\nexport type Command = Hook | 'install' | 'activate';\n\nexport function isCommand(value: string): value is Command {\n return ['pre-commit', 'pre-push', 'install', 'activate'].includes(value);\n}\n\n/**\n * Run one hook, write the wrappers, or activate them.\n *\n * `args` are git's own hook arguments (for `pre-push`: the remote name and URL) and\n * `stdin` is what git wrote to the hook (for `pre-push`: the refs being pushed).\n * ⛔ Never exits non-zero for a reason the caller cannot act on: an unknown command is a\n * programming error in the wrapper and is reported as such, not as a failed commit.\n */\nexport async function runCommand(\n command: Command,\n root: string,\n args: readonly string[] = [],\n stdin = '',\n): Promise<void> {\n if (command === 'install') {\n const written = await installHooks(root);\n ok(`wrote ${written.join(', ')} — commit them`);\n note(`then set \"prepare\": \"${PREPARE}\" so every install activates them`);\n return;\n }\n if (command === 'activate') {\n const result = await activateHooks(root);\n (result.active ? ok : note)(`homeflare hooks: ${result.message}`);\n return;\n }\n if (command === 'pre-commit') return await preCommit(root);\n return await prePush(root, args, stdin);\n}\n"
|
|
14
15
|
],
|
|
15
|
-
"mappings": ";;AAcA,IAAM,SAA+B;AAAA,EACnC,cAAc;AAAA,EACd,YAAY;AACd;AAaO,SAAS,aAAa,GAAuC;AAAA,EAClE,OAAO,OAAO,YACZ,OAAO,QAAQ,QAAQ,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,WAAW,MAAM,CAAC,CACzE;AAAA;AAOF,eAAsB,GAAG,CAAC,KAAwB,WAAW,OAAwB;AAAA,EACnF,MAAM,OAAO,IAAI,MAAM,CAAC,GAAG,GAAG,GAAG;AAAA,IAC/B,QAAQ;AAAA,IACR,QAAQ;AAAA,OACJ,WAAW,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EAC7C,CAAC;AAAA,EACD,OAAO,MAAM,KAAK;AAAA;AAIpB,eAAsB,OAAO,CAAC,KAAyC;AAAA,EACrE,QAAQ,MAAM,MAAM,GAAG,GAAG;AAAA;AAI5B,eAAsB,KAAK,CAAC,KAAmE;AAAA,EAC7F,MAAM,OAAO,IAAI,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AAAA,EACrE,MAAM,SAAS,MAAM,IAAI,SAAS,KAAK,MAAM,EAAE,KAAK;AAAA,EACpD,OAAO,EAAE,MAAM,MAAM,KAAK,QAAQ,OAAO;AAAA;AAW3C,eAAsB,OAAO,CAAC,SAAiB,MAA+B;AAAA,EAC5E,MAAM,MAAM,cAAc;AAAA,EAC1B,IAAI,UAAU,GAAG,0BAA0B,IAAI,WAAW;AAAA,EAC1D,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,MAAM,OAAO,GAAG;AAAA,IAC5C,KAAK;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,OAAO,MAAM,KAAK;AAAA;AAYb,SAAS,IAAI,CAAC,MAAc,MAAiC;AAAA,EAClE,MAAM,QAAQ,GAAG,0BAA0B;AAAA,EAC3C,OAAO,IAAI,KAAK,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,IAAI;AAAA;AAU3D,SAAS,IAAI,CAAC,MAAoB;AAAA,EAChC,QAAQ,OAAO,MAAM,GAAG;AAAA,CAAQ;AAAA;AAG3B,SAAS,EAAE,CAAC,MAAoB;AAAA,EACrC,KAAK,UAAK,MAAM;AAAA;AAGX,SAAS,IAAI,CAAC,MAAoB;AAAA,EACvC,KAAK,KAAK,MAAM;AAAA;AAIX,SAAS,IAAI,CAAC,MAAY,MAAc,KAAoB;AAAA,EACjE,KAAK;AAAA,SAAO,SAAS,MAAM;AAAA,EAC3B,KAAK,aAAa,KAAK;AAAA,EACvB,KAAK,aAAa,OAAO;AAAA,CAAwC;AAAA,EACjE,QAAQ,KAAK,CAAC;AAAA;;;ACxFhB,SAAS,IAAI,CAAC,KAA4D;AAAA,EACxE,MAAM,KAAK,IAAI;AAAA,EACf,OAAO,OAAO,aAAa,OAAO,MAAM,OAAO,OAAO,GAAG,YAAY,MAAM;AAAA;AAG7E,eAAsB,aAAa,CACjC,MACA,MAAoD,QAAQ,KACvC;AAAA,EACrB,IAAI,KAAK,GAAG;AAAA,IAAG,OAAO,EAAE,QAAQ,OAAO,SAAS,oDAA+C;AAAA,EAI/F,MAAM,QAAQ,MAAM,MAAM,CAAC,OAAO,MAAM,MAAM,aAAa,eAAe,CAAC;AAAA,EAC3E,IAAI,MAAM,SAAS;AAAA,IAAG,OAAO,EAAE,QAAQ,OAAO,SAAS,2CAAsC;AAAA,EAE7F,MAAM,UAAU,MAAM,QAAQ,IAC5B,CAAC,cAAc,UAAU,EAAE,IAAI,CAAC,SAAS,IAAI,KAAK,GAAG,eAAe,MAAM,EAAE,OAAO,CAAC,CACtF;AAAA,EACA,IAAI,CAAC,QAAQ,KAAK,OAAO,GAAG;AAAA,IAC1B,OAAO,EAAE,QAAQ,OAAO,SAAS,0DAA0D;AAAA,EAC7F;AAAA,EAIA,MAAM,OAAO,GAAG,MAAM,OAAO,KAAK;AAAA,EAClC,MAAM,WACJ,MAAM,MAAM,CAAC,OAAO,MAAM,MAAM,UAAU,SAAS,gBAAgB,CAAC,GACpE,OAAO,KAAK;AAAA,EACd,IAAI,YAAY;AAAA,IAAM,OAAO,EAAE,QAAQ,MAAM,SAAS,6BAA6B,OAAO;AAAA,EAG1F,MAAM,MAAM,MAAM,MAAM,CAAC,OAAO,MAAM,MAAM,UAAU,kBAAkB,IAAI,CAAC;AAAA,EAC7E,IAAI,IAAI,SAAS;AAAA,IAAG,OAAO,EAAE,QAAQ,OAAO,SAAS,mCAAmC;AAAA,EACxF,MAAM,MAAM,YAAY,KAAK,UAAU;AAAA,EACvC,OAAO;AAAA,IACL,QAAQ;AAAA,IACR,SAAS,kBAAkB,cAAS;AAAA,EACtC;AAAA;;;AC1DF;;;AC6BA,IAAM,aAAa;AAGnB,IAAM,YAAY;AAUX,SAAS,QAAQ,CAAC,QAA+C;AAAA,EACtE,MAAM,QAAkB,CAAC;AAAA,EACzB,IAAI;AAAA,EACJ,IAAI,UAAU;AAAA,EACd,SAAS,IAAI,EAAG,IAAI,OAAO,QAAQ,KAAK;AAAA,IACtC,MAAM,OAAO,OAAO,MAAM;AAAA,IAC1B,IAAI,UAAU,WAAW;AAAA,MACvB,IAAI,SAAS;AAAA,QAAO,QAAQ;AAAA,MAC5B,WAAW;AAAA,MACX;AAAA,IACF;AAAA,IACA,IAAI,SAAS,OAAO,SAAS,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,WAAW;AAAA,MACX;AAAA,IACF;AAAA,IACA,IAAI,SAAS,OAAO,OAAO,IAAI,OAAO,KAAK;AAAA,MACzC,MAAM,KAAK,QAAQ,KAAK,CAAC;AAAA,MACzB,UAAU;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IACF;AAAA,IACA,IAAI,SAAS,SAAS,IAAI,KAAM,SAAS,OAAO,OAAO,IAAI,OAAO;AAAA,MAAM;AAAA,IACxE,WAAW;AAAA,EACb;AAAA,EACA,IAAI,UAAU;AAAA,IAAW;AAAA,EACzB,MAAM,KAAK,QAAQ,KAAK,CAAC;AAAA,EACzB,OAAO,MAAM,KAAK,CAAC,SAAS,SAAS,EAAE,IAAI,YAAY;AAAA;AAIzD,SAAS,SAAS,CAAC,SAAiB,SAA+D;AAAA,EACjG,MAAM,QAAQ,QAAQ,MAAM,KAAK;AAAA,EACjC,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,SAAS,MAAM,OAAO;AAAA,IAAQ,OAAO;AAAA,EAC5E,OAAO,QAAQ,MAAM,QAAQ;AAAA,EAC7B,IAAI,MAAM,WAAW,KAAK,SAAS,SAAS,SAAS;AAAA,IAAW;AAAA,EAChE,IAAI,WAAW,SAAS,WAAW;AAAA,IAAO;AAAA,EAC1C,OAAO,OAAO,OAAO,SAAS,IAAI,IAAI,OAAO;AAAA;AAI/C,IAAM,YAAY,CAAC,YAA6B,oBAAoB,KAAK,OAAO;AAEhF,SAAS,QAAQ,CAAC,SAAiB,MAAgC;AAAA,EACjE,OAAO,SAAS,YACZ,EAAE,MAAM,QAAQ,OAAO,SAAS,SAAS,SAAS,QAAQ,MAAM,IAChE,EAAE,MAAM,QAAQ,OAAO,SAAS,SAAS,GAAG,qBAAqB,QAAQ,QAAQ,KAAK;AAAA;AAG5F,SAAS,MAAM,CACb,QACA,SACA,MACA,OAC6B;AAAA,EAC7B,MAAM,WAAW,SAAS,MAAM;AAAA,EAChC,IAAI,aAAa,aAAa,QAAQ;AAAA,IAAW;AAAA,EACjD,MAAM,QAAgB,CAAC;AAAA,EACvB,WAAW,WAAW,UAAU;AAAA,IAC9B,IAAI,UAAU,OAAO,GAAG;AAAA,MACtB,MAAM,KAAK,SAAS,SAAS,IAAI,CAAC;AAAA,MAClC;AAAA,IACF;AAAA,IACA,MAAM,OAAO,UAAU,SAAS,OAAO;AAAA,IACvC,IAAI,SAAS,WAAW;AAAA,MACtB,MAAM,KAAK,EAAE,MAAM,OAAO,OAAO,SAAS,SAAS,QAAQ,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,IAAI,WAAW,KAAK,IAAI,GAAG;AAAA,MACzB,MAAM,KAAK,EAAE,MAAM,QAAQ,OAAO,SAAS,KAAK,mCAAmC,CAAC;AAAA,MACpF;AAAA,IACF;AAAA,IACA,MAAM,QAAQ,OAAO,QAAQ,SAAS,IAAI,SAAS,MAAM,QAAQ,CAAC;AAAA,IAClE,IAAI,UAAU,aAAa,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,KAAK,GAAG;AAAA,MAEpE,MAAM,KAAK,GAAG,KAAK;AAAA,IACrB,EAAO,SAAI,aAAa,KAAK,IAAI,GAAG;AAAA,MAIlC,MAAM,KAAK,EAAE,MAAM,QAAQ,OAAO,SAAS,SAAS,SAAS,QAAQ,MAAM,CAAC;AAAA,IAC9E,EAAO;AAAA,MAGL,MAAM,KAAK,EAAE,MAAM,OAAO,OAAO,SAAS,SAAS,QAAQ,CAAC;AAAA;AAAA,EAEhE;AAAA,EACA,OAAO;AAAA;AAUF,SAAS,SAAS,CACvB,SACA,MACiB;AAAA,EACjB,MAAM,QAAQ,QAAQ;AAAA,EACtB,IAAI,UAAU;AAAA,IAAW,OAAO,CAAC;AAAA,EAIjC,MAAM,QAAc;AAAA,IAClB,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EACA,OAAO,OAAO,OAAO,SAAS,MAAM,CAAC,KAAK,CAAC,KAAK;AAAA;;;AC5HlD,IAAM,OAAO;AAEN,SAAS,aAAa,CAAC,OAAmC;AAAA,EAC/D,MAAM,OAAkB,CAAC;AAAA,EACzB,WAAW,SAAQ,MAAM,MAAM;AAAA,CAAI,GAAG;AAAA,IACpC,OAAO,UAAU,UAAU,WAAW,aAAa,MAAK,KAAK,EAAE,MAAM,KAAK;AAAA,IAC1E,IAAI,YAAY,YAAY,aAAa,WAAW;AAAA,MAClD,KAAK,KAAK,EAAE,UAAU,UAAU,WAAW,UAAU,CAAC;AAAA,IACxD;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,QAAQ,CAAC,QAAwB,IAAI,MAAM,GAAG,CAAC;AAErD,eAAe,GAAE,CAAC,MAAc,MAA2C;AAAA,EACzE,QAAQ,MAAM,MAAM,CAAC,OAAO,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS;AAAA;AAG9D,eAAe,GAAG,CAAC,MAAc,MAAsD;AAAA,EACrF,MAAM,SAAS,MAAM,MAAM,CAAC,OAAO,MAAM,MAAM,GAAG,IAAI,CAAC;AAAA,EACvD,OAAO,OAAO,SAAS,IAAI,OAAO,OAAO,KAAK,IAAI;AAAA;AASpD,eAAe,aAAa,CAAC,MAAc,QAA6C;AAAA,EACtF,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,gBAAgB,WAAW,gBAAgB,aAAa,CAAC;AAAA,EACvF,IAAI,SAAS,aAAa,SAAS;AAAA,IAAI,OAAO;AAAA,EAC9C,WAAW,QAAQ,CAAC,QAAQ,QAAQ,GAAG;AAAA,IACrC,MAAM,MAAM,gBAAgB,UAAU;AAAA,IACtC,IAAI,MAAM,IAAG,MAAM,CAAC,aAAa,YAAY,WAAW,GAAG,CAAC;AAAA,MAAG,OAAO;AAAA,EACxE;AAAA,EACA;AAAA;AAWF,eAAe,OAAO,CACpB,MACA,QACA,KAC0D;AAAA,EAC1D,QAAQ,UAAU,cAAc;AAAA,EAChC,IACE,CAAC,KAAK,KAAK,SAAS,KACnB,MAAM,IAAG,MAAM,CAAC,YAAY,MAAM,GAAG,oBAAoB,CAAC,KAC1D,MAAM,IAAG,MAAM,CAAC,cAAc,iBAAiB,WAAW,QAAQ,CAAC,GACpE;AAAA,IACA,OAAO,EAAE,MAAM,WAAW,KAAK,SAAS,MAAM,SAAS,WAAW,iBAAiB;AAAA,EACrF;AAAA,EACA,MAAM,SAAS,MAAM,cAAc,MAAM,MAAM;AAAA,EAC/C,IAAI,WAAW;AAAA,IAAW,OAAO,EAAE,KAAK,MAAM,kBAAkB,gCAAgC;AAAA,EAChG,MAAM,YAAY,MAAM,IAAI,MAAM,CAAC,cAAc,UAAU,MAAM,CAAC;AAAA,EAClE,IAAI,cAAc,aAAa,cAAc,IAAI;AAAA,IAC/C,OAAO,EAAE,KAAK,sBAAsB,iDAAiD;AAAA,EACvF;AAAA,EACA,MAAM,OAAO,OAAO,QAAQ,oBAAoB,EAAE;AAAA,EAClD,OAAO,EAAE,MAAM,WAAW,KAAK,SAAS,MAAM,SAAS,6BAA6B,OAAO;AAAA;AAc7F,eAAsB,SAAS,CAC7B,MACA,QACA,MACoB;AAAA,EACpB,MAAM,SAAS,KAAK,OAAO,CAAC,SAAQ,CAAC,KAAK,KAAK,KAAI,QAAQ,CAAC;AAAA,EAC5D,IAAI,KAAK,SAAS,KAAK,OAAO,WAAW,GAAG;AAAA,IAC1C,OAAO,EAAE,MAAM,SAAS,KAAK,8BAA8B;AAAA,EAC7D;AAAA,EACA,MAAM,OAAO,MAAM,IAAI,MAAM,CAAC,aAAa,MAAM,CAAC;AAAA,EAClD,MAAM,SAAS,OAAO,KAAK,CAAC,SAAQ,KAAI,aAAa,IAAI;AAAA,EACzD,MAAM,SAAS,OAAO,OAAO,CAAC,SAAQ,SAAQ,MAAM,EAAE,IAAI,CAAC,SAAQ,KAAI,QAAQ;AAAA,EAC/E,IAAI,OAAO,SAAS,KAAK,WAAW,WAAW;AAAA,IAC7C,OAAO;AAAA,MACL,MAAM;AAAA,MACN,KAAK,WAAW,OAAO,KAAK,IAAI,6BAA6B,MAAM,QAAQ,GAAG;AAAA,IAChF;AAAA,EACF;AAAA,EACA,MAAM,MAAM,UAAU;AAAA,IACpB,UAAU;AAAA,IACV,UAAU,QAAQ;AAAA,IAClB,WAAW;AAAA,IACX,WAAW,IAAI,OAAO,EAAE;AAAA,EAC1B;AAAA,EACA,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ,GAAG;AAAA,EAC7C,MAAM,OAAO,OAAO,SAAS,IAAI,KAAK,OAAO,KAAK,IAAI,uBAAuB;AAAA,EAC7E,IAAI,EAAE,UAAU;AAAA,IAAQ,OAAO,EAAE,MAAM,YAAY,KAAK,GAAG,MAAM,MAAM,OAAO;AAAA,EAE9E,MAAM,OAAO,MAAM,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN,IAAI;AAAA,EACN,CAAC;AAAA,EACD,IAAI,KAAK,SAAS;AAAA,IAAG,OAAO,EAAE,MAAM,YAAY,KAAK,YAAY,MAAM,MAAM,IAAI,WAAW;AAAA,EAC5F,MAAM,UAAU,KAAK,OAAO,MAAM,MAAI,EAAE,OAAO,CAAC,SAAS,SAAS,EAAE;AAAA,EACpE,IAAI,QAAQ,WAAW;AAAA,IAAG,OAAO,EAAE,MAAM,SAAS,KAAK,mBAAmB,MAAM,MAAM,OAAO;AAAA,EAC7F,OAAO,EAAE,MAAM,UAAU,MAAM,MAAM,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,OAAO;AAAA;AAS1E,SAAS,iBAAiB,CAAC,MAAuB;AAAA,EACvD,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AAAA,EACtC,OAAO,8DAA8D,KAAK,IAAI;AAAA;;;AC7JhF,IAAM,UAAU;AAEhB,eAAsB,iBAAiB,GAAkB;AAAA,EAGvD,IAAI,IAAI,MAAM,UAAU,MAAM,MAAM;AAAA,IAClC,KACE,cACA,qEACA,OACF;AAAA,EACF;AAAA,EAGA,MAAM,OAAO,MAAM,IAAI,CAAC,YAAY,OAAO,YAAY,YAAY,eAAe,GAAG,CAAC;AAAA,EACtF,IAAI,SAAS,GAAG;AAAA,IACd,KACE,cACA,iDACA,mFACF;AAAA,EACF;AAAA,EACA,GAAG,4DAA4D;AAAA;;;ACzBjE,IAAM,cAAc;AACpB,IAAM,OAAO;AAkBb,eAAe,KAAK,CAAC,MAAqD;AAAA,EACxE,MAAM,OAAM,MAAM,QAAQ,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC;AAAA,EAChD,OAAO,KAAI,MAAM,MAAI,EAAE,OAAO,CAAC,UAAS,MAAK,SAAS,CAAC;AAAA;AAIzD,eAAsB,MAAM,GAAoB;AAAA,EAC9C,MAAM,UAAU,MAAM,MAAM,CAAC,QAAQ,YAAY,eAAe,oBAAoB,CAAC;AAAA,EACrF,MAAM,QAAQ,IAAI,IAAI,MAAM,MAAM,CAAC,QAAQ,eAAe,oBAAoB,CAAC,CAAC;AAAA,EAChF,MAAM,aAAa,QAAQ,OAAO,CAAC,SAAS,YAAY,KAAK,IAAI,CAAC;AAAA,EAElE,OAAO;AAAA,IACL,aAAa,WAAW,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC;AAAA,IACzD,MAAM,WAAW,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC;AAAA,IACrE,SAAS,WAAW,OAAO,CAAC,SAAS,MAAM,IAAI,IAAI,CAAC;AAAA,EACtD;AAAA;AAUF,eAAsB,YAAY,CAAC,OAAgE;AAAA,EACjG,MAAM,OAAM,IAAI;AAAA,EAChB,WAAW,QAAQ,OAAO;AAAA,IACxB,MAAM,SAAS,IAAI,KAAK,IAAI;AAAA,IAC5B,IAAI,CAAE,MAAM,OAAO,OAAO;AAAA,MAAI;AAAA,IAC9B,KAAI,IAAI,MAAM,OAAO,IAAI,KAAK,MAAM,OAAO,YAAY,CAAC,CAAC,CAAC;AAAA,EAC5D;AAAA,EACA,OAAO;AAAA;;;AJhCT,SAAS,SAAS,CAAC,MAAc,MAA0C;AAAA,EACzE,IAAI,WAAW,GAAG,mBAAmB;AAAA,IAAG,OAAO;AAAA,EAC/C,KAAK,GAAG,oFAA+E;AAAA,EACvF,OAAO;AAAA;AAGT,eAAsB,SAAS,CAAC,MAA6B;AAAA,EAG3D,MAAM,kBAAkB;AAAA,EACxB,IAAI,CAAC,UAAU,MAAM,YAAY;AAAA,IAAG;AAAA,EACpC,MAAM,QAAQ,KAAK,MAAM,OAAO;AAAA,EAChC,QAAQ,aAAa,MAAM,YAAY,MAAM,OAAO;AAAA,EAGpD,IAAI,QAAQ,SAAS,GAAG;AAAA,IACtB,KAAK,GAAG,QAAQ,4EAA4E;AAAA,IAC5F,IAAK,MAAM,IAAI,CAAC,GAAG,OAAO,WAAW,GAAG,OAAO,CAAC,MAAO,GAAG;AAAA,MACxD,KACE,cACA,mGACA,0DACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,YAAY,WAAW,GAAG;AAAA,IAC5B,GAAG,sCAAsC;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,MAAM,aAAa,WAAW;AAAA,EAC7C,IAAK,MAAM,IAAI,CAAC,GAAG,OAAO,GAAG,WAAW,CAAC,MAAO,GAAG;AAAA,IACjD,KAAK,cAAc,2CAA2C,gBAAgB;AAAA,EAChF;AAAA,EACA,MAAM,QAAQ,MAAM,aAAa,WAAW;AAAA,EAC5C,MAAM,YAAY,YAAY,OAAO,CAAC,SAAS,OAAO,IAAI,IAAI,MAAM,MAAM,IAAI,IAAI,CAAC;AAAA,EAEnF,IAAI,UAAU,SAAS,GAAG;AAAA,IACxB,KAAK,8BAA8B,UAAU,mBAAmB,UAAU,KAAK,IAAI,GAAG;AAAA,IACtF,IAAK,MAAM,IAAI,CAAC,OAAO,OAAO,MAAM,GAAG,SAAS,CAAC,MAAO,GAAG;AAAA,MACzD,KAAK,cAAc,yCAAyC,0BAA0B;AAAA,IACxF;AAAA,EACF;AAAA,EAIA,IAAI,KAAK,SAAS,KAAM,MAAM,IAAI,CAAC,GAAG,KAAK,MAAM,QAAQ,GAAG,mBAAmB,GAAG,IAAI,CAAC,MAAO,GAAG;AAAA,IAC/F,KACE,cACA,4BAA4B,KAAK,yBACjC,iDACF;AAAA,EACF;AAAA,EAEA,GAAG,eAAe,YAAY,4CAA4C;AAAA;AAI5E,SAAS,QAAQ,CAAC,MAAoB;AAAA,EACpC,IAAI,KAAK,SAAS;AAAA,IAAQ,OAAO,QAAQ,KAAK,gBAAW,KAAK;AAAA,EAC9D,IAAI,KAAK,SAAS,UAAU,KAAK;AAAA,IAC/B,OAAO,QAAQ,KAAK;AAAA,EACtB,IAAI,KAAK,SAAS;AAAA,IAAQ,OAAO,QAAQ,KAAK;AAAA,EAC9C,OAAO,QAAQ,KAAK;AAAA;AAYtB,eAAsB,OAAO,CAAC,MAAc,MAAyB,OAA8B;AAAA,EACjG,MAAM,WAAW,IAAI,KAAK,GAAG,mBAAmB;AAAA,EAChD,MAAM,MAAO,MAAM,SAAS,OAAO,IAC7B,MAAM,SAAS,KAAK,IACtB,CAAC;AAAA,EACL,MAAM,UAAU,IAAI,WAAW,CAAC;AAAA,EAChC,IAAI,QAAQ,aAAa,WAAW;AAAA,IAClC,KAAK,sEAAsE;AAAA,IAC3E;AAAA,EACF;AAAA,EACA,IAAI,CAAC,UAAU,MAAM,UAAU;AAAA,IAAG;AAAA,EAElC,MAAM,QAAQ,MAAM,UAAU,MAAM,KAAK,MAAM,UAAU,cAAc,KAAK,CAAC;AAAA,EAC7E,IAAI,MAAM,SAAS,SAAS;AAAA,IAC1B,GAAG,aAAa,MAAM,6BAAwB;AAAA,IAC9C;AAAA,EACF;AAAA,EACA,IAAI,MAAM,SAAS,aAAa;AAAA,IAI9B,KAAK,aAAa,MAAM,yDAAoD;AAAA,IAC5E,KACE,0FACF;AAAA,IACA;AAAA,EACF;AAAA,EACA,IAAI;AAAA,EACJ,IAAI,MAAM,SAAS,YAAY;AAAA,IAC7B,KAAK,aAAa,MAAM,2CAAsC;AAAA,EAChE,EAAO;AAAA,IACL,MAAM,SAAS,MAAM,QAAQ,OAAO,iBAAiB;AAAA,IACrD,KAAK,aAAa,OAAO,MAAM,QAAQ,MAAM,qBAAqB,MAAM,KAAK;AAAA,IAC7E,IAAI,OAAO,SAAS;AAAA,MAClB,KAAK,KAAK,OAAO,KAAK,IAAI,wDAAmD;AAAA,IAC1E;AAAA,aAAO,MAAM;AAAA;AAAA,EAGpB,MAAM,QAAQ,UAAU,SAAS,IAAI;AAAA,EACrC,MAAM,UAAU,IAAI,YAAY;AAAA,EAChC,IAAI,MAAM;AAAA,EACV,WAAW,QAAQ,OAAO;AAAA,IACxB,KAAK,SAAS,IAAI,CAAC;AAAA,IACnB,IAAI,KAAK,SAAS;AAAA,MAAQ;AAAA,IAE1B,IAAK,MAAM,QAAQ,KAAK,SAAS,IAAI,MAAO,GAAG;AAAA,MAC7C,KAAK,YAAY,KAAK,KAAK,kBAAkB,GAAG,KAAK,kCAA6B;AAAA,IACpF;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EACA,MAAM,YAAY,IAAI,YAAY,IAAI,WAAW,KAAK,QAAQ,CAAC;AAAA,EAC/D,GAAG,aAAa,OAAO,GAAG,oCAAoC,uCAAkC;AAAA;;;AKnJlG;AAGO,IAAM,aAAa,CAAC,cAAc,UAAU;AAGnD,IAAM,SAAS;AAGR,IAAM,UAAkB,OAAO;AAY/B,IAAM,aAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAQC;AAAA,QAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASR,eAAsB,YAAY,CAAC,YAAgD;AAAA,EACjF,MAAM,MAAM,GAAG,qBAAqB,EAAE,WAAW,KAAK,CAAC;AAAA,EACvD,MAAM,UAAoB,CAAC;AAAA,EAC3B,WAAW,QAAQ,YAAY;AAAA,IAC7B,MAAM,OAAO,GAAG,qBAAqB;AAAA,IACrC,MAAM,IAAI,MAAM,MAAM,UAAU;AAAA,IAGhC,MAAM,MAAM,MAAM,GAAK;AAAA,IACvB,QAAQ,KAAK,UAAU,MAAM;AAAA,EAC/B;AAAA,EACA,OAAO;AAAA;AAUT,eAAsB,eAAe,CAAC,YAAgD;AAAA,EACpF,MAAM,WAAqB,CAAC;AAAA,EAC5B,MAAM,WAAW,IAAI,KAAK,GAAG,yBAAyB;AAAA,EAEtD,IAAI,CAAE,MAAM,SAAS,OAAO;AAAA,IAAI,OAAO,CAAC,uBAAuB;AAAA,EAC/D,MAAM,MAAO,MAAM,SAAS,KAAK;AAAA,EAEjC,MAAM,UAAU,IAAI,UAAU,cAAc;AAAA,EAC5C,IAAI,CAAC,0BAA0B,KAAK,OAAO,GAAG;AAAA,IAC5C,SAAS,KACP,0CAA0C,kDAC5C;AAAA,EACF;AAAA,EAGA,IAAI,YAAY,KAAK,OAAO,GAAG;AAAA,IAC7B,SAAS,KACP,sFACF;AAAA,EACF;AAAA,EAEA,WAAW,QAAQ,YAAY;AAAA,IAC7B,MAAM,OAAO,GAAG,qBAAqB;AAAA,IACrC,MAAM,OAAO,IAAI,KAAK,IAAI;AAAA,IAC1B,IAAI,CAAE,MAAM,KAAK,OAAO,GAAI;AAAA,MAC1B,SAAS,KAAK,UAAU,kCAA6B,kBAAkB;AAAA,MACvE;AAAA,IACF;AAAA,IACA,IAAK,MAAM,KAAK,KAAK,MAAO,YAAY;AAAA,MACtC,SAAS,KACP,UAAU,qEAAgE,+CAC5E;AAAA,IACF;AAAA,IACA,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO,QAAW,GAAG;AAAA,MAC3C,SAAS,KAAK,UAAU,uEAAkE;AAAA,IAC5F;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;;;AChFF,SAAS,SAAS,CAAC,OAAiC;AAAA,EACzD,OAAO,CAAC,cAAc,YAAY,WAAW,UAAU,EAAE,SAAS,KAAK;AAAA;AAWzE,eAAsB,UAAU,CAC9B,SACA,MACA,OAA0B,CAAC,GAC3B,QAAQ,IACO;AAAA,EACf,IAAI,YAAY,WAAW;AAAA,IACzB,MAAM,UAAU,MAAM,aAAa,IAAI;AAAA,IACvC,GAAG,SAAS,QAAQ,KAAK,IAAI,sBAAiB;AAAA,IAC9C,KAAK,wBAAwB,0CAA0C;AAAA,IACvE;AAAA,EACF;AAAA,EACA,IAAI,YAAY,YAAY;AAAA,IAC1B,MAAM,SAAS,MAAM,cAAc,IAAI;AAAA,KACtC,OAAO,SAAS,KAAK,MAAM,oBAAoB,OAAO,SAAS;AAAA,IAChE;AAAA,EACF;AAAA,EACA,IAAI,YAAY;AAAA,IAAc,OAAO,MAAM,UAAU,IAAI;AAAA,EACzD,OAAO,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA;",
|
|
16
|
-
"debugId": "
|
|
16
|
+
"mappings": "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",
|
|
17
|
+
"debugId": "F6FB90E426FFDF3C64756E2164756E21",
|
|
17
18
|
"names": []
|
|
18
19
|
}
|
|
@@ -6,11 +6,28 @@
|
|
|
6
6
|
* this house are the product. The rendered workflows are written as text with holes;
|
|
7
7
|
* only the step lists, whose shape varies per repository, go through here.
|
|
8
8
|
*
|
|
9
|
-
* ⚠️ Bun
|
|
10
|
-
* against Bun 1.4.0 on 2026-09-
|
|
11
|
-
*
|
|
9
|
+
* ⚠️ Bun.YAML.stringify EXISTS (`Object.keys(Bun.YAML)` is `["parse", "stringify"]`,
|
|
10
|
+
* measured against Bun 1.4.0 on 2026-09-23) but its output does not fit these rules:
|
|
11
|
+
* (a) a multi-line `run:` comes out as a double-quoted string with `\n` escapes, e.g.
|
|
12
|
+
* `run: "echo a\necho b\n"`, not a `|` block scalar; (b) `09:00` comes out UNQUOTED,
|
|
13
|
+
* e.g. `cron: 09:00`, the YAML 1.1 sexagesimal trap the `scalar()` comment below guards
|
|
14
|
+
* against; (c) a mapping key is followed by a trailing space, e.g. `"steps: \n - ..."`;
|
|
15
|
+
* and (d) a plain JS object has nowhere to attach a comment, so it cannot carry one. The
|
|
16
|
+
* tests parse what this writes with `Bun.YAML.parse` and compare structures, so a
|
|
17
|
+
* malformed emission fails rather than shipping.
|
|
12
18
|
*/
|
|
13
19
|
import type { JobStep } from './shape.ts';
|
|
20
|
+
/**
|
|
21
|
+
* Trim trailing `\n` characters the way `command.replace(/\n+$/, '')` used to, but
|
|
22
|
+
* linear instead of backtracking — the same pattern as `normalizeBaseUrl` in
|
|
23
|
+
* `packages/distilled-netbox/src/credentials.ts`. Exported so a test can compare it
|
|
24
|
+
* against the old regex directly.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ Linear on purpose: a `/\n+$/` regex backtracks polynomially on a long run of
|
|
27
|
+
* "\n" that is not at the end (CodeQL js/polynomial-redos), and `command` here is
|
|
28
|
+
* repository-configured step text.
|
|
29
|
+
*/
|
|
30
|
+
export declare function trimTrailingNewlines(value: string): string;
|
|
14
31
|
/** One step, as the lines of a `steps:` list item at `depth`. */
|
|
15
32
|
export declare function renderStep(step: JobStep, depth: number): string[];
|
|
16
33
|
/** A whole `steps:` list, already indented for a job at `depth`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../../src/repo-shape/yaml.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../../src/repo-shape/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AA2C1C;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI1D;AAaD,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BjE;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5E"}
|
package/dist/repo-shape.js
CHANGED
|
@@ -191,8 +191,14 @@ function scalar(value) {
|
|
|
191
191
|
function renderMapping(entries, depth) {
|
|
192
192
|
return Object.entries(entries).map(([key, value]) => `${indent(depth)}${key}: ${scalar(value)}`);
|
|
193
193
|
}
|
|
194
|
+
function trimTrailingNewlines(value) {
|
|
195
|
+
let end = value.length;
|
|
196
|
+
while (end > 0 && value.charCodeAt(end - 1) === 10)
|
|
197
|
+
end--;
|
|
198
|
+
return value.slice(0, end);
|
|
199
|
+
}
|
|
194
200
|
function renderRun(command, depth) {
|
|
195
|
-
const lines = command
|
|
201
|
+
const lines = trimTrailingNewlines(command).split(`
|
|
196
202
|
`);
|
|
197
203
|
if (lines.length === 1)
|
|
198
204
|
return [`${indent(depth)}run: ${scalar(lines[0] ?? "")}`];
|
|
@@ -775,5 +781,5 @@ export {
|
|
|
775
781
|
runsOn
|
|
776
782
|
};
|
|
777
783
|
|
|
778
|
-
//# debugId=
|
|
784
|
+
//# debugId=C9A5F54A65373F0A64756E2164756E21
|
|
779
785
|
//# sourceMappingURL=repo-shape.js.map
|