@reddoorla/maintenance 0.75.1 → 0.78.0
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 +34 -7
- package/dist/{announce-V6EYPPUR.js → announce-RUT247HS.js} +9 -6
- package/dist/{announce-V6EYPPUR.js.map → announce-RUT247HS.js.map} +1 -1
- package/dist/{blux-C24OVDDW.js → blux-2P4FGCUL.js} +1867 -277
- package/dist/blux-2P4FGCUL.js.map +1 -0
- package/dist/{bump-deps-OQR3IXLN.js → bump-deps-XB2LJTVN.js} +3 -3
- package/dist/{chunk-RHPDQW6D.js → chunk-2SLED3NB.js} +1 -1
- package/dist/{chunk-RHPDQW6D.js.map → chunk-2SLED3NB.js.map} +1 -1
- package/dist/chunk-37S6L6G4.js +170 -0
- package/dist/chunk-37S6L6G4.js.map +1 -0
- package/dist/{chunk-HIN3P4ZN.js → chunk-4SOCPK7A.js} +10 -1
- package/dist/chunk-4SOCPK7A.js.map +1 -0
- package/dist/{run-migration-LJNNZWIV.js → chunk-6DOFNDXU.js} +5 -3
- package/dist/chunk-6DOFNDXU.js.map +1 -0
- package/dist/{chunk-NUIBQPB3.js → chunk-6SZBNALD.js} +62 -9
- package/dist/chunk-6SZBNALD.js.map +1 -0
- package/dist/{chunk-CASCWQQX.js → chunk-73UFRZKY.js} +34 -19
- package/dist/chunk-73UFRZKY.js.map +1 -0
- package/dist/{chunk-D3DQFC32.js → chunk-ATJ6ZA2V.js} +2 -2
- package/dist/{chunk-ZO7WNY57.js → chunk-B5ZUQZFL.js} +2 -2
- package/dist/{chunk-PH3AGF4Z.js → chunk-DC5H7KEQ.js} +2 -2
- package/dist/chunk-DRDAEELJ.js +197 -0
- package/dist/chunk-DRDAEELJ.js.map +1 -0
- package/dist/{chunk-IDBMYDVH.js → chunk-FVVGDIGX.js} +6 -4
- package/dist/{chunk-IDBMYDVH.js.map → chunk-FVVGDIGX.js.map} +1 -1
- package/dist/{chunk-7MJBTV2R.js → chunk-FX7RAMGC.js} +2 -2
- package/dist/{chunk-2AUUAJ5X.js → chunk-GWAOX3CU.js} +9 -9
- package/dist/{chunk-PKVZMFEB.js → chunk-K5JZ5KLO.js} +2 -2
- package/dist/{chunk-6OYICQSJ.js → chunk-KPQXB5CR.js} +9 -4
- package/dist/chunk-KPQXB5CR.js.map +1 -0
- package/dist/{chunk-BTEBRTW4.js → chunk-LS6QPANO.js} +2 -2
- package/dist/{chunk-2X322ZES.js → chunk-MGJ72BA4.js} +2 -2
- package/dist/chunk-MNDIED4M.js +137 -0
- package/dist/chunk-MNDIED4M.js.map +1 -0
- package/dist/{chunk-JCITWZQR.js → chunk-MNE374CZ.js} +2 -50
- package/dist/chunk-MNE374CZ.js.map +1 -0
- package/dist/chunk-MPBXOJ3N.js +51 -0
- package/dist/chunk-MPBXOJ3N.js.map +1 -0
- package/dist/{chunk-ZYBOT5PM.js → chunk-NT4WIP7I.js} +2 -2
- package/dist/{chunk-JNXCDUYB.js → chunk-Q276MZ7E.js} +3 -4
- package/dist/chunk-Q276MZ7E.js.map +1 -0
- package/dist/{chunk-GYNRZCM6.js → chunk-QG6OSL4M.js} +2 -2
- package/dist/{chunk-IRRZG66U.js → chunk-QMXQUL5Z.js} +34 -18
- package/dist/chunk-QMXQUL5Z.js.map +1 -0
- package/dist/{chunk-I55YWV6T.js → chunk-QWFHC3VH.js} +3 -3
- package/dist/{chunk-KLVMJYZZ.js → chunk-SEQA7MC3.js} +2 -2
- package/dist/chunk-UWJZXL2D.js +9 -0
- package/dist/chunk-UWJZXL2D.js.map +1 -0
- package/dist/{chunk-YEGHUODU.js → chunk-VSHC4HOI.js} +96 -1
- package/dist/chunk-VSHC4HOI.js.map +1 -0
- package/dist/{chunk-HBFMTM2E.js → chunk-YIQ7IWE3.js} +3 -3
- package/dist/{chunk-TDG5Z5OT.js → chunk-YSPA5AUT.js} +86 -13
- package/dist/chunk-YSPA5AUT.js.map +1 -0
- package/dist/{chunk-XKOZUVK2.js → chunk-ZTPPCHDB.js} +1 -1
- package/dist/chunk-ZTPPCHDB.js.map +1 -0
- package/dist/cli/bin.js +62 -24
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +1 -1
- package/dist/cli/commands/audit.js +5 -5
- package/dist/client-X5WUJWDE.js +11 -0
- package/dist/configs/eslint.js +10 -0
- package/dist/configs/eslint.js.map +1 -1
- package/dist/{convert-to-pnpm-OVMASATZ.js → convert-to-pnpm-FZHSLUQB.js} +3 -3
- package/dist/{db-3QW2XEXA.js → db-JSIZYT23.js} +3 -3
- package/dist/{digest-LEMZS5ZK.js → digest-NQAQI34P.js} +16 -13
- package/dist/{digest-LEMZS5ZK.js.map → digest-NQAQI34P.js.map} +1 -1
- package/dist/favicon-SRHSJZ62.js +42 -0
- package/dist/favicon-SRHSJZ62.js.map +1 -0
- package/dist/forms/index.js.map +1 -1
- package/dist/freeze-Y5OJFOST.js +442 -0
- package/dist/freeze-Y5OJFOST.js.map +1 -0
- package/dist/{github-signals-TF3VSNIN.js → github-signals-4LWQGD5V.js} +5 -5
- package/dist/header-image-H4N54DHN.js +95 -0
- package/dist/header-image-H4N54DHN.js.map +1 -0
- package/dist/{health-endpoint-JZGYCMTV.js → health-endpoint-LFVE26QS.js} +3 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +86 -31
- package/dist/index.js.map +1 -1
- package/dist/{init-Y2DMKKE7.js → init-KF4QHMWQ.js} +12 -12
- package/dist/{launch-ZDX7UA3Y.js → launch-CVCIP5NU.js} +11 -8
- package/dist/launch-CVCIP5NU.js.map +1 -0
- package/dist/migrate-LJUFGXQZ.js +7 -0
- package/dist/{onboard-PX36YDZA.js → onboard-RDGHVCNG.js} +3 -3
- package/dist/{orchestrate-NYRYEKQM.js → orchestrate-QOQXOJLI.js} +7 -6
- package/dist/{preflight-JCQFKB6M.js → preflight-2PWVVIRR.js} +8 -7
- package/dist/{preflight-JCQFKB6M.js.map → preflight-2PWVVIRR.js.map} +1 -1
- package/dist/protection-audit-X3BHT2MB.js +140 -0
- package/dist/protection-audit-X3BHT2MB.js.map +1 -0
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/recipes/sync-configs.js +3 -3
- package/dist/{renovate-dispatch-6II2JVOX.js → renovate-dispatch-2VYCEH42.js} +2 -2
- package/dist/renovate-dispatch-2VYCEH42.js.map +1 -0
- package/dist/{report-POHQEV6F.js → report-IU4CLJS5.js} +8 -6
- package/dist/{report-POHQEV6F.js.map → report-IU4CLJS5.js.map} +1 -1
- package/dist/reports/header-image/assets/plate.png +0 -0
- package/dist/run-migration-WP57U5XO.js +9 -0
- package/dist/run-migration-WP57U5XO.js.map +1 -0
- package/dist/{self-updating-2YSVNMPP.js → self-updating-ALMUWJNX.js} +5 -4
- package/dist/{self-updating-2YSVNMPP.js.map → self-updating-ALMUWJNX.js.map} +1 -1
- package/dist/{selftest-OO2WDSDM.js → selftest-PJFABZDM.js} +11 -9
- package/dist/{selftest-OO2WDSDM.js.map → selftest-PJFABZDM.js.map} +1 -1
- package/dist/{smoke-suite-CSP23NZM.js → smoke-suite-SUEJFBYP.js} +3 -3
- package/dist/{submissions-MDKWJYMM.js → submissions-3I3GAQAX.js} +13 -5
- package/dist/submissions-3I3GAQAX.js.map +1 -0
- package/dist/{submissions-BNRGY3JQ.js → submissions-SURN7BV5.js} +3 -3
- package/dist/{svelte-codemods-V54KOBWU.js → svelte-codemods-IWO7X3F3.js} +3 -3
- package/dist/{sync-configs-UBCJRASZ.js → sync-configs-7XGHFC6P.js} +4 -4
- package/dist/{types-0jreMCF5.d.ts → types-DnEL3NRn.d.ts} +1 -1
- package/dist/{upgrade-WR6ICPAG.js → upgrade-AM4S7JF3.js} +3 -3
- package/dist/webflow-624FEBFD.js +548 -0
- package/dist/webflow-624FEBFD.js.map +1 -0
- package/package.json +3 -3
- package/dist/blux-C24OVDDW.js.map +0 -1
- package/dist/chunk-6OYICQSJ.js.map +0 -1
- package/dist/chunk-CASCWQQX.js.map +0 -1
- package/dist/chunk-HIN3P4ZN.js.map +0 -1
- package/dist/chunk-IRRZG66U.js.map +0 -1
- package/dist/chunk-JCITWZQR.js.map +0 -1
- package/dist/chunk-JNXCDUYB.js.map +0 -1
- package/dist/chunk-NUIBQPB3.js.map +0 -1
- package/dist/chunk-TDG5Z5OT.js.map +0 -1
- package/dist/chunk-XKOZUVK2.js.map +0 -1
- package/dist/chunk-YEGHUODU.js.map +0 -1
- package/dist/client-NVVQK3HH.js +0 -11
- package/dist/launch-ZDX7UA3Y.js.map +0 -1
- package/dist/migrate-35DNNF3H.js +0 -7
- package/dist/renovate-dispatch-6II2JVOX.js.map +0 -1
- package/dist/run-migration-LJNNZWIV.js.map +0 -1
- package/dist/submissions-MDKWJYMM.js.map +0 -1
- /package/dist/{bump-deps-OQR3IXLN.js.map → bump-deps-XB2LJTVN.js.map} +0 -0
- /package/dist/{chunk-D3DQFC32.js.map → chunk-ATJ6ZA2V.js.map} +0 -0
- /package/dist/{chunk-ZO7WNY57.js.map → chunk-B5ZUQZFL.js.map} +0 -0
- /package/dist/{chunk-PH3AGF4Z.js.map → chunk-DC5H7KEQ.js.map} +0 -0
- /package/dist/{chunk-7MJBTV2R.js.map → chunk-FX7RAMGC.js.map} +0 -0
- /package/dist/{chunk-2AUUAJ5X.js.map → chunk-GWAOX3CU.js.map} +0 -0
- /package/dist/{chunk-PKVZMFEB.js.map → chunk-K5JZ5KLO.js.map} +0 -0
- /package/dist/{chunk-BTEBRTW4.js.map → chunk-LS6QPANO.js.map} +0 -0
- /package/dist/{chunk-2X322ZES.js.map → chunk-MGJ72BA4.js.map} +0 -0
- /package/dist/{chunk-ZYBOT5PM.js.map → chunk-NT4WIP7I.js.map} +0 -0
- /package/dist/{chunk-GYNRZCM6.js.map → chunk-QG6OSL4M.js.map} +0 -0
- /package/dist/{chunk-I55YWV6T.js.map → chunk-QWFHC3VH.js.map} +0 -0
- /package/dist/{chunk-KLVMJYZZ.js.map → chunk-SEQA7MC3.js.map} +0 -0
- /package/dist/{chunk-HBFMTM2E.js.map → chunk-YIQ7IWE3.js.map} +0 -0
- /package/dist/{client-NVVQK3HH.js.map → client-X5WUJWDE.js.map} +0 -0
- /package/dist/{convert-to-pnpm-OVMASATZ.js.map → convert-to-pnpm-FZHSLUQB.js.map} +0 -0
- /package/dist/{db-3QW2XEXA.js.map → db-JSIZYT23.js.map} +0 -0
- /package/dist/{github-signals-TF3VSNIN.js.map → github-signals-4LWQGD5V.js.map} +0 -0
- /package/dist/{health-endpoint-JZGYCMTV.js.map → health-endpoint-LFVE26QS.js.map} +0 -0
- /package/dist/{init-Y2DMKKE7.js.map → init-KF4QHMWQ.js.map} +0 -0
- /package/dist/{migrate-35DNNF3H.js.map → migrate-LJUFGXQZ.js.map} +0 -0
- /package/dist/{onboard-PX36YDZA.js.map → onboard-RDGHVCNG.js.map} +0 -0
- /package/dist/{orchestrate-NYRYEKQM.js.map → orchestrate-QOQXOJLI.js.map} +0 -0
- /package/dist/{smoke-suite-CSP23NZM.js.map → smoke-suite-SUEJFBYP.js.map} +0 -0
- /package/dist/{submissions-BNRGY3JQ.js.map → submissions-SURN7BV5.js.map} +0 -0
- /package/dist/{svelte-codemods-V54KOBWU.js.map → svelte-codemods-IWO7X3F3.js.map} +0 -0
- /package/dist/{sync-configs-UBCJRASZ.js.map → sync-configs-7XGHFC6P.js.map} +0 -0
- /package/dist/{upgrade-WR6ICPAG.js.map → upgrade-AM4S7JF3.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blux/emit/plan.ts"],"sourcesContent":["import type { Diagnostic, SectionIR } from \"../ir.js\";\n\nexport type RichTextMarker = { __richtext_html: string };\nexport type AssetMarker = { __asset_id: string };\nexport const richText = (html: string): RichTextMarker => ({ __richtext_html: html });\nexport const assetRef = (id: string): AssetMarker => ({ __asset_id: id });\n\nexport type PlanSlice = {\n slice_type: string;\n variation: string;\n primary: Record<string, unknown>;\n items: Record<string, unknown>[];\n};\nexport type PlanDocument = { type: string; uid: string; data: Record<string, unknown> };\nexport type PlanCustomType = { id: string; label: string; repeatable: true; json: unknown };\nexport type PlanAsset = { id: string; url: string; alt: string };\n/** Presentation hints for one emitted slice; `index` is its position in the\n * document's slice zone (after empty-slice filtering). `items` aligns with a\n * kept section_grid's items. */\nexport type SliceStyleEntry = {\n index: number;\n sliceType: string;\n presentation?: NonNullable<SectionIR[\"presentation\"]>;\n items?: (NonNullable<SectionIR[\"presentation\"]> | null)[];\n};\n\nexport type MigrationPlan = {\n customTypes: PlanCustomType[];\n documents: PlanDocument[];\n assets: PlanAsset[];\n /** Per-page presentation hints (block styles + text roles) — design-pass\n * reference only, never pushed to Prismic. */\n stylesManifest: { pageUid: string; slices: SliceStyleEntry[] }[];\n /** Plan-time findings (skipped empty pages, dropped non-image assets, …). */\n diagnostics: Diagnostic[];\n};\n"],"mappings":";AAIO,IAAM,WAAW,CAAC,UAAkC,EAAE,iBAAiB,KAAK;AAC5E,IAAM,WAAW,CAAC,QAA6B,EAAE,YAAY,GAAG;","names":[]}
|
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
} from "./chunk-HVOOCK6L.js";
|
|
4
4
|
|
|
5
5
|
// src/github/gh.ts
|
|
6
|
+
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
7
|
+
import { tmpdir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
6
9
|
function assertUrlSegment(kind, value) {
|
|
7
10
|
const structural = /[\s?#%\\]|\.\./;
|
|
8
11
|
if (value.length === 0 || value.startsWith("/") || structural.test(value)) {
|
|
@@ -36,6 +39,16 @@ function makeGitHub(deps) {
|
|
|
36
39
|
if (r.code !== 0) throw new Error(`gh ${args[0]} failed (code ${r.code}): ${r.stderr.trim()}`);
|
|
37
40
|
return r.stdout;
|
|
38
41
|
}
|
|
42
|
+
async function ghJson(method, path, body) {
|
|
43
|
+
const dir = await mkdtemp(join(tmpdir(), "gh-json-"));
|
|
44
|
+
const file = join(dir, "body.json");
|
|
45
|
+
try {
|
|
46
|
+
await writeFile(file, JSON.stringify(body), "utf-8");
|
|
47
|
+
return await gh(["api", "-X", method, path, "--input", file]);
|
|
48
|
+
} finally {
|
|
49
|
+
await rm(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
39
52
|
return {
|
|
40
53
|
async openPullRequest(repo, pr) {
|
|
41
54
|
const out = await gh([
|
|
@@ -57,6 +70,9 @@ function makeGitHub(deps) {
|
|
|
57
70
|
async enableRepoAutoMerge(repo) {
|
|
58
71
|
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-F", "allow_auto_merge=true"]);
|
|
59
72
|
},
|
|
73
|
+
async disableRepoAutoMerge(repo) {
|
|
74
|
+
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-F", "allow_auto_merge=false"]);
|
|
75
|
+
},
|
|
60
76
|
async protectBranch(repo, branch, requiredChecks) {
|
|
61
77
|
assertUrlSegment("branch", branch);
|
|
62
78
|
const args = [
|
|
@@ -247,6 +263,85 @@ function makeGitHub(deps) {
|
|
|
247
263
|
"-f",
|
|
248
264
|
`ref=${ref}`
|
|
249
265
|
]);
|
|
266
|
+
},
|
|
267
|
+
async listRepoRulesets(repo) {
|
|
268
|
+
const out = await gh([
|
|
269
|
+
"api",
|
|
270
|
+
`repos/${repo}/rulesets?per_page=100&includes_parents=false`,
|
|
271
|
+
"--jq",
|
|
272
|
+
'.[] | "\\(.id)\\t\\(.name)"'
|
|
273
|
+
]);
|
|
274
|
+
return out.split("\n").map((l) => l.trim()).filter((l) => l.includes(" ")).map((l) => {
|
|
275
|
+
const tab = l.indexOf(" ");
|
|
276
|
+
return { id: Number(l.slice(0, tab)), name: l.slice(tab + 1) };
|
|
277
|
+
}).filter((r) => Number.isSafeInteger(r.id));
|
|
278
|
+
},
|
|
279
|
+
async getRuleset(repo, id) {
|
|
280
|
+
const out = await gh(["api", `repos/${repo}/rulesets/${Math.trunc(id)}`]);
|
|
281
|
+
return JSON.parse(out);
|
|
282
|
+
},
|
|
283
|
+
async createRuleset(repo, payload) {
|
|
284
|
+
await ghJson("POST", `repos/${repo}/rulesets`, payload);
|
|
285
|
+
},
|
|
286
|
+
async updateRuleset(repo, id, payload) {
|
|
287
|
+
await ghJson("PUT", `repos/${repo}/rulesets/${Math.trunc(id)}`, payload);
|
|
288
|
+
},
|
|
289
|
+
async checkContextObserved(repo, ref, context) {
|
|
290
|
+
assertUrlSegment("branch", ref);
|
|
291
|
+
const r = await spawn(
|
|
292
|
+
"gh",
|
|
293
|
+
[
|
|
294
|
+
"api",
|
|
295
|
+
`repos/${repo}/commits/${ref}/check-runs?per_page=100`,
|
|
296
|
+
"--jq",
|
|
297
|
+
".check_runs[].name"
|
|
298
|
+
],
|
|
299
|
+
{ env, timeoutMs: 6e4 }
|
|
300
|
+
);
|
|
301
|
+
if (r.code !== 0) return false;
|
|
302
|
+
return r.stdout.split("\n").map((l) => l.trim()).includes(context);
|
|
303
|
+
},
|
|
304
|
+
async repoVisibility(repo) {
|
|
305
|
+
const out = await gh(["api", `repos/${repo}`, "--jq", ".visibility"]);
|
|
306
|
+
return out.trim();
|
|
307
|
+
},
|
|
308
|
+
async listOrgRepos(org) {
|
|
309
|
+
assertUrlSegment("path", org);
|
|
310
|
+
const out = await gh([
|
|
311
|
+
"api",
|
|
312
|
+
"--paginate",
|
|
313
|
+
`orgs/${org}/repos?per_page=100`,
|
|
314
|
+
"--jq",
|
|
315
|
+
'.[] | "\\(.name)\\t\\(.visibility)\\t\\(.archived)\\t\\(.security_and_analysis.secret_scanning.status // "unavailable")\\t\\(.security_and_analysis.secret_scanning_push_protection.status // "unavailable")"'
|
|
316
|
+
]);
|
|
317
|
+
return out.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((l) => {
|
|
318
|
+
const [name = "", visibility = "", archived = "", ss = "", pp = ""] = l.split(" ");
|
|
319
|
+
return {
|
|
320
|
+
name,
|
|
321
|
+
visibility,
|
|
322
|
+
archived: archived === "true",
|
|
323
|
+
secretScanning: ss || "unavailable",
|
|
324
|
+
pushProtection: pp || "unavailable"
|
|
325
|
+
};
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
async workflowHealth(repo, filename) {
|
|
329
|
+
const wf = await spawn(
|
|
330
|
+
"gh",
|
|
331
|
+
["api", `repos/${repo}/actions/workflows/${filename}`, "--jq", ".state"],
|
|
332
|
+
{ env, timeoutMs: 6e4 }
|
|
333
|
+
);
|
|
334
|
+
if (wf.code !== 0) {
|
|
335
|
+
if (/HTTP 404/.test(wf.stderr)) return { present: false };
|
|
336
|
+
throw new Error(`workflowHealth(${repo}/${filename}) failed: ${wf.stderr.trim()}`);
|
|
337
|
+
}
|
|
338
|
+
const runs = await gh([
|
|
339
|
+
"api",
|
|
340
|
+
`repos/${repo}/actions/workflows/${filename}/runs?status=success&per_page=1`,
|
|
341
|
+
"--jq",
|
|
342
|
+
'.workflow_runs[0].created_at // ""'
|
|
343
|
+
]);
|
|
344
|
+
return { present: true, state: wf.stdout.trim(), lastSuccessAt: runs.trim() || null };
|
|
250
345
|
}
|
|
251
346
|
};
|
|
252
347
|
}
|
|
@@ -255,4 +350,4 @@ export {
|
|
|
255
350
|
assertUrlSegment,
|
|
256
351
|
makeGitHub
|
|
257
352
|
};
|
|
258
|
-
//# sourceMappingURL=chunk-
|
|
353
|
+
//# sourceMappingURL=chunk-VSHC4HOI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/github/gh.ts"],"sourcesContent":["import { mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { defaultSpawn, type SpawnFn } from \"../audits/util/spawn.js\";\nimport type { ExistingRuleset, RulesetPayload } from \"./rulesets.js\";\n\n/** Aggregate CI state of a PR's head commit, normalized from GitHub's rollup. */\nexport type CiState = \"passing\" | \"failing\" | \"pending\" | \"none\";\n\n/** GitHub's computed mergeability of a PR. `UNKNOWN` is transient — GitHub is\n * still computing it (e.g. right after a push) — so it should be read as \"not\n * known to conflict\", never as conflicting. */\nexport type PrMergeable = \"MERGEABLE\" | \"CONFLICTING\" | \"UNKNOWN\";\n\n/** A minimal open-PR summary with its head-commit CI rollup state + mergeability. */\nexport type PullRequestSummary = {\n number: number;\n title: string;\n url: string;\n headRef: string;\n ciState: CiState;\n mergeable: PrMergeable;\n};\n\n/**\n * Reject a value before it's interpolated into a `gh api` URL path. The\n * `owner/repo` split methods already validate shape; this is the defense-in-depth\n * guard for the `branch` and file-`path` segments. An unexpected value (`..`, a\n * leading `/`, whitespace, or a URL-structural char like `?#%` or a backslash)\n * could otherwise retarget the endpoint (escape the intended path, smuggle a\n * query string, or traverse). Conservative by design — legit branch names like\n * `maint/self-updating-x` and paths like `.github/workflows/ci.yml` pass.\n *\n * Both branch refs (`maint/self-updating-x`) and file paths\n * (`.github/workflows/ci.yml`) legitimately contain `/`, so a single slash is\n * allowed; what's rejected is `..`, a leading `/`, whitespace, or a\n * URL-structural char (`?`, `#`, `%`, backslash) that could escape or retarget\n * the endpoint.\n */\nexport function assertUrlSegment(kind: \"branch\" | \"path\", value: string): void {\n const structural = /[\\s?#%\\\\]|\\.\\./;\n if (value.length === 0 || value.startsWith(\"/\") || structural.test(value)) {\n throw new Error(\n `unsafe ${kind} for gh api path (illegal characters or traversal): ${JSON.stringify(value)}`,\n );\n }\n}\n\n/** Map GitHub's `statusCheckRollup.state` enum to our normalized CiState. */\nfunction mapRollupState(state: string | null | undefined): CiState {\n switch (state) {\n case \"SUCCESS\":\n return \"passing\";\n case \"FAILURE\":\n case \"ERROR\":\n return \"failing\";\n case \"PENDING\":\n case \"EXPECTED\":\n return \"pending\";\n default:\n return \"none\"; // null/undefined = no checks reported\n }\n}\n\n/** Coerce GitHub's `mergeable` enum to our PrMergeable. Anything unexpected\n * (including the literal `UNKNOWN` GitHub returns while still computing) maps to\n * `UNKNOWN` — i.e. \"not known to conflict\". Only an explicit `CONFLICTING` is. */\nfunction mapMergeable(state: string | null | undefined): PrMergeable {\n return state === \"MERGEABLE\" || state === \"CONFLICTING\" ? state : \"UNKNOWN\";\n}\n\nexport type GitHub = {\n openPullRequest: (\n repo: string,\n pr: { head: string; base: string; title: string; body: string },\n ) => Promise<{ url: string }>;\n /** Turn GitHub's platform auto-merge ON for a repo. Retained as the documented\n * ROLLBACK path — the fleet drives repos to auto-merge OFF (see\n * {@link disableRepoAutoMerge} and `recipes/self-updating`). */\n enableRepoAutoMerge: (repo: string) => Promise<void>;\n /** Turn GitHub's platform auto-merge OFF for a repo. This is the fleet default:\n * platform auto-merge is a per-PR flag anyone with write access can arm, and it\n * merges unattended outside Renovate's `packageRules`. Renovate merges from\n * inside its own run instead. */\n disableRepoAutoMerge: (repo: string) => Promise<void>;\n protectBranch: (repo: string, branch: string, requiredChecks: string[]) => Promise<void>;\n setRepoSecret: (repo: string, name: string, value: string) => Promise<void>;\n repoExists: (repo: string) => Promise<boolean>;\n defaultBranch: (repo: string) => Promise<string>;\n filesOnBranch: (repo: string, branch: string, paths: string[]) => Promise<string[]>;\n /** The raw contents of `path` on `branch`, or null when the file is absent (404).\n * Lets a caller detect a present-but-STALE file (content drift), where\n * `filesOnBranch` only sees existence. */\n fileContentsOnBranch: (repo: string, branch: string, path: string) => Promise<string | null>;\n branchProtectionContexts: (repo: string, branch: string) => Promise<string[]>;\n secretExists: (repo: string, name: string) => Promise<boolean>;\n autoMergeEnabled: (repo: string) => Promise<boolean>;\n findOpenSelfUpdatingPR: (repo: string) => Promise<string | null>;\n /** All open PRs on a repo with each head commit's normalized CI rollup state. */\n openPullRequests: (repo: string) => Promise<PullRequestSummary[]>;\n /** The default branch's latest-commit date + normalized CI rollup, one query. */\n defaultBranchStatus: (repo: string) => Promise<{ ciState: CiState; lastCommitAt: string | null }>;\n /** Renovate PRs (head `renovate/*`) merged at/after `sinceIso`. Used to record\n * `pr_automerged` fleet events. gh-shell (Actions only). */\n mergedRenovatePullRequests: (\n repo: string,\n sinceIso: string,\n ) => Promise<Array<{ number: number; title: string; url: string; mergedAt: string }>>;\n /** Fire a `workflow_dispatch` for `<workflow>` (a filename like `renovate.yml`)\n * on `ref`. Requires the token's `actions:write` scope; a 404 (no such\n * workflow) or 403 (missing scope) surfaces as a thrown error. */\n dispatchWorkflow: (repo: string, workflow: string, ref: string) => Promise<void>;\n /** REPO-sourced rulesets only (`includes_parents=false`): org-level rulesets\n * aren't ours to manage (and don't exist on the Free plan). */\n listRepoRulesets: (repo: string) => Promise<Array<{ id: number; name: string }>>;\n getRuleset: (repo: string, id: number) => Promise<ExistingRuleset>;\n createRuleset: (repo: string, payload: RulesetPayload) => Promise<void>;\n /** The update verb is PUT — `PATCH repos/{repo}/rulesets/{id}` returns 404\n * (verified live 2026-08-01), so a PATCH-based update would silently no-op\n * on every drifted repo while the recipe reported success. */\n updateRuleset: (repo: string, id: number, payload: RulesetPayload) => Promise<void>;\n /** True iff a check-run named `context` exists on `ref`'s HEAD commit. The\n * evidence gate for requiring a status check: with an empty bypass list, a\n * required context that never fires makes the repo permanently unmergeable\n * by everyone — so a context must be OBSERVED before it is required.\n * False-negatives (fresh repo, API hiccup) safely degrade to \"don't require\n * yet\"; false-positives are impossible (we name-match real runs). */\n checkContextObserved: (repo: string, ref: string, context: string) => Promise<boolean>;\n /** \"public\" | \"private\" (| \"internal\" on GHEC). Rulesets on private repos\n * need a paid plan, so callers gate on this rather than attempting. */\n repoVisibility: (repo: string) => Promise<string>;\n /** Every repo in the org (paginated) — the anti-hand-typed-list enumerator.\n * Sweeps driven by a hand-maintained or Airtable-scoped list have already\n * produced two false \"all clear\"s; enumerate from the API instead.\n * secretScanning/pushProtection come from the same listing (no extra call);\n * \"unavailable\" means the token couldn't read security_and_analysis (needs\n * admin/security read) — callers must treat that as unverified, not fine. */\n listOrgRepos: (org: string) => Promise<\n Array<{\n name: string;\n visibility: string;\n archived: boolean;\n secretScanning: string;\n pushProtection: string;\n }>\n >;\n /** Liveness of one workflow file: registered? state? when did it last\n * SUCCEED? Success, not mere existence of a run: a dead credential or\n * broken config still CREATES a run on every cron tick that fails in\n * seconds, keeping created_at perpetually fresh — counting those as alive\n * is exactly the silent-stop blindness this probe exists to catch.\n * A clean 404 is the answer `{present: false}` — any OTHER failure throws,\n * because \"couldn't check\" must never read as \"healthy\" downstream. */\n workflowHealth: (repo: string, filename: string) => Promise<WorkflowHealth>;\n};\n\nexport type WorkflowHealth =\n { present: false } | { present: true; state: string; lastSuccessAt: string | null };\n\nexport function makeGitHub(deps: { token: string; spawn?: SpawnFn }): GitHub {\n const spawn = deps.spawn ?? defaultSpawn;\n const env = { ...process.env, GH_TOKEN: deps.token };\n\n async function gh(args: string[]): Promise<string> {\n const r = await spawn(\"gh\", args, { env, timeoutMs: 60_000 });\n if (r.code !== 0) throw new Error(`gh ${args[0]} failed (code ${r.code}): ${r.stderr.trim()}`);\n return r.stdout;\n }\n\n /** `gh api` with a JSON request body. Ruleset payloads have nested arrays/\n * objects that `-f`/`-F` field flags cannot express, and the spawn wrapper\n * has no stdin — so the body rides a private temp file via `--input`. */\n async function ghJson(method: \"POST\" | \"PUT\", path: string, body: unknown): Promise<string> {\n const dir = await mkdtemp(join(tmpdir(), \"gh-json-\"));\n const file = join(dir, \"body.json\");\n try {\n await writeFile(file, JSON.stringify(body), \"utf-8\");\n return await gh([\"api\", \"-X\", method, path, \"--input\", file]);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n return {\n async openPullRequest(repo, pr) {\n const out = await gh([\n \"pr\",\n \"create\",\n \"--repo\",\n repo,\n \"--head\",\n pr.head,\n \"--base\",\n pr.base,\n \"--title\",\n pr.title,\n \"--body\",\n pr.body,\n ]);\n return { url: out.trim() };\n },\n async enableRepoAutoMerge(repo) {\n await gh([\"api\", \"-X\", \"PATCH\", `repos/${repo}`, \"-F\", \"allow_auto_merge=true\"]);\n },\n async disableRepoAutoMerge(repo) {\n await gh([\"api\", \"-X\", \"PATCH\", `repos/${repo}`, \"-F\", \"allow_auto_merge=false\"]);\n },\n async protectBranch(repo, branch, requiredChecks) {\n assertUrlSegment(\"branch\", branch);\n const args = [\n \"api\",\n \"-X\",\n \"PUT\",\n `repos/${repo}/branches/${branch}/protection`,\n \"-H\",\n \"Accept: application/vnd.github+json\",\n \"-F\",\n \"required_status_checks[strict]=true\",\n ...requiredChecks.flatMap((c) => [\"-f\", `required_status_checks[contexts][]=${c}`]),\n \"-F\",\n \"enforce_admins=true\",\n \"-F\",\n \"required_pull_request_reviews=null\",\n \"-F\",\n \"restrictions=null\",\n ];\n await gh(args);\n },\n async setRepoSecret(repo, name, value) {\n await gh([\"secret\", \"set\", name, \"--repo\", repo, \"--body\", value]);\n },\n async repoExists(repo) {\n const r = await spawn(\"gh\", [\"api\", `repos/${repo}`], { env, timeoutMs: 60_000 });\n return r.code === 0;\n },\n async defaultBranch(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".default_branch\"]);\n return out.trim();\n },\n // filesOnBranch and branchProtectionContexts call `spawn` directly (not the\n // throwing `gh()` helper) because a 404 is an expected, meaningful answer —\n // \"file/protection absent\" — not an error. The remaining readers use `gh()`\n // since a non-200 there is a genuine failure (e.g. missing token scope).\n async filesOnBranch(repo, branch, paths) {\n assertUrlSegment(\"branch\", branch);\n const present: string[] = [];\n for (const p of paths) {\n assertUrlSegment(\"path\", p);\n const r = await spawn(\"gh\", [`api`, `repos/${repo}/contents/${p}?ref=${branch}`], {\n env,\n timeoutMs: 60_000,\n });\n if (r.code === 0) present.push(p);\n }\n return present;\n },\n async fileContentsOnBranch(repo, branch, path) {\n // Same spawn-directly rationale as filesOnBranch: a 404 (file absent) is an\n // expected answer (null), not an error. The `raw` media type returns the file\n // bytes verbatim (no base64 envelope) so the result compares directly against a\n // canonical template.\n assertUrlSegment(\"branch\", branch);\n assertUrlSegment(\"path\", path);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/contents/${path}?ref=${branch}`,\n \"-H\",\n \"Accept: application/vnd.github.raw\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return null; // 404 = file absent on this branch\n return r.stdout;\n },\n async branchProtectionContexts(repo, branch) {\n assertUrlSegment(\"branch\", branch);\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/branches/${branch}/protection`,\n \"--jq\",\n \".required_status_checks.contexts[]?\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return []; // 404 = no protection configured\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n },\n async secretExists(repo, name) {\n // per_page=100: the REST default of 30 would false-negative on a repo with >30 secrets,\n // wrongly reporting an existing secret absent (→ a needless overwrite).\n const out = await gh([\n \"api\",\n `repos/${repo}/actions/secrets?per_page=100`,\n \"--jq\",\n \".secrets[].name\",\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .includes(name);\n },\n async autoMergeEnabled(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".allow_auto_merge\"]);\n return out.trim() === \"true\";\n },\n async findOpenSelfUpdatingPR(repo) {\n // per_page=100: with the REST default of 30, a repo with >30 open PRs (plausible under\n // Renovate) could page past the existing self-updating PR and open a duplicate.\n const out = await gh([\n \"api\",\n `repos/${repo}/pulls?state=open&per_page=100`,\n \"--jq\",\n '.[] | select(.head.ref | startswith(\"maint/self-updating-\")) | .html_url',\n ]);\n const first = out\n .split(\"\\n\")\n .map((l) => l.trim())\n .find((l) => l.length > 0);\n return first ?? null;\n },\n async openPullRequests(repo) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`openPullRequests: expected \"owner/repo\", got \"${repo}\"`);\n }\n const query =\n \"query($owner:String!,$name:String!){repository(owner:$owner,name:$name){\" +\n \"pullRequests(states:OPEN,first:100,orderBy:{field:CREATED_AT,direction:DESC}){nodes{number title url headRefName mergeable \" +\n \"commits(last:1){nodes{commit{statusCheckRollup{state}}}}}}}}\";\n const out = await gh([\n \"api\",\n \"graphql\",\n \"-f\",\n `query=${query}`,\n \"-F\",\n `owner=${owner}`,\n \"-F\",\n `name=${name}`,\n ]);\n const parsed = JSON.parse(out) as {\n data?: {\n repository?: {\n pullRequests?: {\n nodes?: Array<{\n number: number;\n title: string;\n url: string;\n headRefName: string;\n mergeable?: string;\n commits?: {\n nodes?: Array<{ commit?: { statusCheckRollup?: { state?: string } } }>;\n };\n }>;\n };\n };\n };\n };\n const nodes = parsed.data?.repository?.pullRequests?.nodes ?? [];\n return nodes.map((n) => ({\n number: n.number,\n title: n.title,\n url: n.url,\n headRef: n.headRefName,\n ciState: mapRollupState(n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state),\n mergeable: mapMergeable(n.mergeable),\n }));\n },\n async defaultBranchStatus(repo) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`defaultBranchStatus: expected \"owner/repo\", got \"${repo}\"`);\n }\n const query =\n \"query($owner:String!,$name:String!){repository(owner:$owner,name:$name){\" +\n \"defaultBranchRef{target{... on Commit{committedDate statusCheckRollup{state}}}}}}\";\n const out = await gh([\n \"api\",\n \"graphql\",\n \"-f\",\n `query=${query}`,\n \"-F\",\n `owner=${owner}`,\n \"-F\",\n `name=${name}`,\n ]);\n const parsed = JSON.parse(out) as {\n data?: {\n repository?: {\n defaultBranchRef?: {\n target?: { committedDate?: string; statusCheckRollup?: { state?: string } | null };\n } | null;\n };\n };\n };\n const target = parsed.data?.repository?.defaultBranchRef?.target;\n return {\n ciState: mapRollupState(target?.statusCheckRollup?.state),\n lastCommitAt: target?.committedDate ?? null,\n };\n },\n async mergedRenovatePullRequests(repo, sinceIso) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`mergedRenovatePullRequests: expected \"owner/repo\", got \"${repo}\"`);\n }\n // per_page=50: comfortably covers a week of Renovate merges on one repo. The\n // list endpoint returns merged_at + head.ref, so the filter is local.\n const out = await gh([\n \"api\",\n `repos/${owner}/${name}/pulls?state=closed&sort=updated&direction=desc&per_page=50`,\n ]);\n const arr = JSON.parse(out) as Array<{\n number: number;\n title: string;\n html_url: string;\n merged_at: string | null;\n head?: { ref?: string };\n }>;\n return arr\n .filter(\n (p) =>\n p.merged_at !== null &&\n p.merged_at >= sinceIso && // ISO8601 UTC strings sort lexicographically\n (p.head?.ref ?? \"\").startsWith(\"renovate/\"),\n )\n .map((p) => ({\n number: p.number,\n title: p.title,\n url: p.html_url,\n mergedAt: p.merged_at as string,\n }));\n },\n async dispatchWorkflow(repo, workflow, ref) {\n const [owner, name, ...rest] = repo.split(\"/\");\n if (!owner || !name || rest.length > 0) {\n throw new Error(`dispatchWorkflow: expected \"owner/repo\", got \"${repo}\"`);\n }\n // Every segment interpolates into the API path, so guard them all like the\n // other write methods do (defense in depth). `owner`/`name` are the most\n // operator-controlled (typed into Airtable's \"Git repo\"); `workflow` is a\n // constant today; `ref` is repo-sourced. A junk value like `repo?x=1` would\n // otherwise smuggle a query string past the bare two-part shape check.\n assertUrlSegment(\"path\", owner);\n assertUrlSegment(\"path\", name);\n assertUrlSegment(\"path\", workflow);\n assertUrlSegment(\"branch\", ref);\n await gh([\n \"api\",\n \"-X\",\n \"POST\",\n `repos/${owner}/${name}/actions/workflows/${workflow}/dispatches`,\n \"-f\",\n `ref=${ref}`,\n ]);\n },\n async listRepoRulesets(repo) {\n // includes_parents=false: only rulesets SOURCED on this repo are ours to\n // heal; an org-level ruleset (paid plans) must never be PUT from here.\n const out = await gh([\n \"api\",\n `repos/${repo}/rulesets?per_page=100&includes_parents=false`,\n \"--jq\",\n '.[] | \"\\\\(.id)\\\\t\\\\(.name)\"',\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.includes(\"\\t\")) // a tab-less line has no id/name split — never fabricate a NaN row from it\n .map((l) => {\n const tab = l.indexOf(\"\\t\");\n return { id: Number(l.slice(0, tab)), name: l.slice(tab + 1) };\n })\n .filter((r) => Number.isSafeInteger(r.id));\n },\n async getRuleset(repo, id) {\n const out = await gh([\"api\", `repos/${repo}/rulesets/${Math.trunc(id)}`]);\n return JSON.parse(out) as ExistingRuleset;\n },\n async createRuleset(repo, payload) {\n await ghJson(\"POST\", `repos/${repo}/rulesets`, payload);\n },\n async updateRuleset(repo, id, payload) {\n // PUT, not PATCH: PATCH on this endpoint 404s (verified live 2026-08-01),\n // which a naive implementation would surface as \"update failed\" at best —\n // or, with a lenient error path, as a silent no-op reported as healed.\n await ghJson(\"PUT\", `repos/${repo}/rulesets/${Math.trunc(id)}`, payload);\n },\n async checkContextObserved(repo, ref, context) {\n assertUrlSegment(\"branch\", ref);\n // spawn-direct (not the throwing gh()): a 404/409 — empty repo, no commits\n // on the ref yet — is an expected answer meaning \"no evidence\", not an\n // error. Any failure degrades to false, i.e. \"don't require the check\n // yet\", which is the safe direction (see checkContextObserved's contract).\n const r = await spawn(\n \"gh\",\n [\n \"api\",\n `repos/${repo}/commits/${ref}/check-runs?per_page=100`,\n \"--jq\",\n \".check_runs[].name\",\n ],\n { env, timeoutMs: 60_000 },\n );\n if (r.code !== 0) return false;\n return r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .includes(context);\n },\n async repoVisibility(repo) {\n const out = await gh([\"api\", `repos/${repo}`, \"--jq\", \".visibility\"]);\n return out.trim();\n },\n async listOrgRepos(org) {\n assertUrlSegment(\"path\", org);\n // --paginate: the org is >27 repos and the default page of 30 is exactly\n // the trap that produced the 2026-07-31 false \"queue is empty\".\n const out = await gh([\n \"api\",\n \"--paginate\",\n `orgs/${org}/repos?per_page=100`,\n \"--jq\",\n '.[] | \"\\\\(.name)\\\\t\\\\(.visibility)\\\\t\\\\(.archived)\\\\t\\\\(.security_and_analysis.secret_scanning.status // \"unavailable\")\\\\t\\\\(.security_and_analysis.secret_scanning_push_protection.status // \"unavailable\")\"',\n ]);\n return out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => {\n const [name = \"\", visibility = \"\", archived = \"\", ss = \"\", pp = \"\"] = l.split(\"\\t\");\n return {\n name,\n visibility,\n archived: archived === \"true\",\n secretScanning: ss || \"unavailable\",\n pushProtection: pp || \"unavailable\",\n };\n });\n },\n async workflowHealth(repo, filename) {\n // spawn-direct for the workflow GET: 404 (file not registered as a\n // workflow) is an expected ANSWER, not an error. Everything else throws —\n // per the contract, an unreadable workflow must surface as unverified.\n const wf = await spawn(\n \"gh\",\n [\"api\", `repos/${repo}/actions/workflows/${filename}`, \"--jq\", \".state\"],\n { env, timeoutMs: 60_000 },\n );\n if (wf.code !== 0) {\n if (/HTTP 404/.test(wf.stderr)) return { present: false };\n throw new Error(`workflowHealth(${repo}/${filename}) failed: ${wf.stderr.trim()}`);\n }\n // status=success: filter to runs that actually CONCLUDED success. Without\n // it, a schedule whose every run fails (revoked App key, broken config)\n // reads as hours-fresh forever while zero updates flow.\n const runs = await gh([\n \"api\",\n `repos/${repo}/actions/workflows/${filename}/runs?status=success&per_page=1`,\n \"--jq\",\n '.workflow_runs[0].created_at // \"\"',\n ]);\n return { present: true, state: wf.stdout.trim(), lastSuccessAt: runs.trim() || null };\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
isProbablyEmail,
|
|
7
7
|
parseAddresses
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FVVGDIGX.js";
|
|
9
9
|
import {
|
|
10
10
|
gatingHealth,
|
|
11
11
|
isSendOverridden,
|
|
12
12
|
listAllReports
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-MNE374CZ.js";
|
|
14
14
|
import {
|
|
15
15
|
listWebsites,
|
|
16
16
|
siteSlug
|
|
@@ -339,4 +339,4 @@ export {
|
|
|
339
339
|
preflight,
|
|
340
340
|
approveBlockers
|
|
341
341
|
};
|
|
342
|
-
//# sourceMappingURL=chunk-
|
|
342
|
+
//# sourceMappingURL=chunk-YIQ7IWE3.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-GASBX52O.js";
|
|
4
4
|
import {
|
|
5
5
|
assertUrlSegment
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VSHC4HOI.js";
|
|
7
7
|
import {
|
|
8
8
|
baselineVersions
|
|
9
9
|
} from "./chunk-LKGVSM2O.js";
|
|
@@ -604,6 +604,11 @@ async function readSiteConfig(sitePath) {
|
|
|
604
604
|
if (typeof url === "string" && url.length > 0) {
|
|
605
605
|
out.lighthouseUrl = url;
|
|
606
606
|
}
|
|
607
|
+
const routes = cfg.a11yRoutes;
|
|
608
|
+
if (Array.isArray(routes)) {
|
|
609
|
+
const clean = routes.filter((r) => typeof r === "string").map((r) => r.trim()).filter((r) => r.length > 0);
|
|
610
|
+
if (clean.length > 0) out.a11yRoutes = clean;
|
|
611
|
+
}
|
|
607
612
|
return out;
|
|
608
613
|
}
|
|
609
614
|
|
|
@@ -863,13 +868,13 @@ export default defineConfig({
|
|
|
863
868
|
});
|
|
864
869
|
`;
|
|
865
870
|
}
|
|
866
|
-
function buildSpec() {
|
|
871
|
+
function buildSpec(axePages) {
|
|
867
872
|
return `import { test, expect } from "@playwright/test";
|
|
868
873
|
import AxeBuilder from "@axe-core/playwright";
|
|
869
874
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
870
875
|
import { dirname } from "node:path";
|
|
871
876
|
|
|
872
|
-
const pages = ${JSON.stringify(
|
|
877
|
+
const pages = ${JSON.stringify(axePages)};
|
|
873
878
|
const smokePages = ${JSON.stringify(smokeRoutes)};
|
|
874
879
|
const OUTPUT = process.env.REDDOOR_A11Y_OUTPUT;
|
|
875
880
|
|
|
@@ -955,8 +960,13 @@ async function a11yAudit(ctx) {
|
|
|
955
960
|
const label = siteLabel(site);
|
|
956
961
|
const specDir = await mkdtemp2(join6(site.path, ".reddoor-a11y-spec-"));
|
|
957
962
|
try {
|
|
963
|
+
const { a11yRoutes: siteRoutes } = await readSiteConfig(site.path);
|
|
964
|
+
const axePages = [
|
|
965
|
+
...a11yRoutes,
|
|
966
|
+
...(siteRoutes ?? []).map((path) => ({ path, name: path }))
|
|
967
|
+
];
|
|
958
968
|
const specPath = join6(specDir, "a11y.spec.ts");
|
|
959
|
-
await writeFile2(specPath, buildSpec(), "utf-8");
|
|
969
|
+
await writeFile2(specPath, buildSpec(axePages), "utf-8");
|
|
960
970
|
const port = await findFreePort();
|
|
961
971
|
const configPath = join6(specDir, "playwright.config.ts");
|
|
962
972
|
await writeFile2(configPath, buildPlaywrightConfig(port, site.path), "utf-8");
|
|
@@ -1243,6 +1253,34 @@ async function discoverRoutes(deployedUrl, deps, cap = DEFAULT_CAP) {
|
|
|
1243
1253
|
}
|
|
1244
1254
|
|
|
1245
1255
|
// src/audits/browser.ts
|
|
1256
|
+
function sameSiteHost(a, b) {
|
|
1257
|
+
const ha = a.toLowerCase();
|
|
1258
|
+
const hb = b.toLowerCase();
|
|
1259
|
+
return ha === hb || ha.endsWith(`.${hb}`) || hb.endsWith(`.${ha}`);
|
|
1260
|
+
}
|
|
1261
|
+
async function canonicalizeBaseUrl(baseUrl, verify) {
|
|
1262
|
+
if (!verify.fetchRedirect) return { url: baseUrl };
|
|
1263
|
+
let origin;
|
|
1264
|
+
try {
|
|
1265
|
+
origin = new URL(baseUrl);
|
|
1266
|
+
} catch {
|
|
1267
|
+
return { url: baseUrl };
|
|
1268
|
+
}
|
|
1269
|
+
const seen = await verify.fetchRedirect(baseUrl);
|
|
1270
|
+
const isRedirect = seen.status !== null && [301, 302, 307, 308].includes(seen.status) && (seen.location ?? "") !== "";
|
|
1271
|
+
if (!isRedirect) return { url: baseUrl };
|
|
1272
|
+
let target;
|
|
1273
|
+
try {
|
|
1274
|
+
target = new URL(seen.location, baseUrl);
|
|
1275
|
+
} catch {
|
|
1276
|
+
return { url: baseUrl };
|
|
1277
|
+
}
|
|
1278
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") return { url: baseUrl };
|
|
1279
|
+
if (!sameSiteHost(origin.hostname, target.hostname)) {
|
|
1280
|
+
return { url: baseUrl, offsiteLocation: target.href };
|
|
1281
|
+
}
|
|
1282
|
+
return { url: target.href, redirectedFrom: baseUrl };
|
|
1283
|
+
}
|
|
1246
1284
|
function isBroken(status) {
|
|
1247
1285
|
return status === null || status >= 400;
|
|
1248
1286
|
}
|
|
@@ -1436,7 +1474,8 @@ async function browserAudit(ctx) {
|
|
|
1436
1474
|
const verifyDeps = ctx.verifyDeps ?? defaultVerifyDeps();
|
|
1437
1475
|
const runner = ctx.browserRunner ?? await defaultBrowserRunner();
|
|
1438
1476
|
try {
|
|
1439
|
-
const
|
|
1477
|
+
const canonical = await canonicalizeBaseUrl(site.deployedUrl, verifyDeps);
|
|
1478
|
+
const discovered = await discoverRoutes(canonical.url, discoverDeps);
|
|
1440
1479
|
const probed = await runner.probe(discovered.routes);
|
|
1441
1480
|
const { routes: reverifiedRoutes, reverified } = await reverifyRoutes(probed, verifyDeps);
|
|
1442
1481
|
const { routes: routeResults, excused } = excuseChallengedEngineChecks(reverifiedRoutes);
|
|
@@ -1459,7 +1498,7 @@ async function browserAudit(ctx) {
|
|
|
1459
1498
|
const linkReverifiedUrls = clearedLinks.map(
|
|
1460
1499
|
(v) => `${v.url}: first check saw ${v.firstStatus ?? "no response"}, plain re-fetch ${v.fetchedStatus}`
|
|
1461
1500
|
);
|
|
1462
|
-
const note = summary.note + (cleared.length > 0 ? `; ${cleared.length} route(s) re-verified reachable by plain fetch after a browser-side challenge/flake` : "") + (excused.length > 0 ? `; ${excused.length} engine/device check(s) excused (challenge on a re-verified-reachable route)` : "") + (clearedLinks.length > 0 ? `; ${clearedLinks.length} link(s) re-verified ok by plain fetch after cooldown` : "");
|
|
1501
|
+
const note = summary.note + (cleared.length > 0 ? `; ${cleared.length} route(s) re-verified reachable by plain fetch after a browser-side challenge/flake` : "") + (excused.length > 0 ? `; ${excused.length} engine/device check(s) excused (challenge on a re-verified-reachable route)` : "") + (clearedLinks.length > 0 ? `; ${clearedLinks.length} link(s) re-verified ok by plain fetch after cooldown` : "") + (canonical.redirectedFrom ? `; base url redirects to its canonical host \u2014 probed ${canonical.url}` : "") + (canonical.offsiteLocation ? `; base url redirects OFF-SITE to ${canonical.offsiteLocation} \u2014 not followed; update the site's url` : "");
|
|
1463
1502
|
const status = summary.desktopOk && summary.mobileOk && summary.linksOk && summary.templateOk ? "pass" : "warn";
|
|
1464
1503
|
return {
|
|
1465
1504
|
audit: "browser",
|
|
@@ -1472,6 +1511,10 @@ async function browserAudit(ctx) {
|
|
|
1472
1511
|
reverifiedUrls,
|
|
1473
1512
|
excusedEngineChecks: excused,
|
|
1474
1513
|
linkReverifiedUrls,
|
|
1514
|
+
// Canonicalization evidence (spread-conditional — exactOptionalPropertyTypes idiom):
|
|
1515
|
+
// which base was actually probed, and any off-site redirect the operator must act on.
|
|
1516
|
+
...canonical.redirectedFrom ? { canonicalBaseUrl: canonical.url, canonicalizedFrom: canonical.redirectedFrom } : {},
|
|
1517
|
+
...canonical.offsiteLocation ? { offsiteRedirect: canonical.offsiteLocation } : {},
|
|
1475
1518
|
checkedAt: now.toISOString()
|
|
1476
1519
|
}
|
|
1477
1520
|
};
|
|
@@ -1512,6 +1555,19 @@ function defaultVerifyDeps() {
|
|
|
1512
1555
|
} catch {
|
|
1513
1556
|
return { status: null, title: null, metaDescription: null };
|
|
1514
1557
|
}
|
|
1558
|
+
},
|
|
1559
|
+
// Base-URL preflight for canonicalizeBaseUrl: one no-follow GET so the redirect target is
|
|
1560
|
+
// observable (redirect:"follow" would hide it). Errors degrade to null = "no redirect seen".
|
|
1561
|
+
fetchRedirect: async (url) => {
|
|
1562
|
+
try {
|
|
1563
|
+
const res = await fetch(url, {
|
|
1564
|
+
redirect: "manual",
|
|
1565
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1566
|
+
});
|
|
1567
|
+
return { status: res.status, location: res.headers.get("location") };
|
|
1568
|
+
} catch {
|
|
1569
|
+
return { status: null, location: null };
|
|
1570
|
+
}
|
|
1515
1571
|
}
|
|
1516
1572
|
};
|
|
1517
1573
|
}
|
|
@@ -1923,6 +1979,13 @@ async function smokeAudit(ctx) {
|
|
|
1923
1979
|
}
|
|
1924
1980
|
}
|
|
1925
1981
|
}
|
|
1982
|
+
try {
|
|
1983
|
+
await spawn("pnpm", ["exec", "svelte-kit", "sync"], {
|
|
1984
|
+
cwd: site.path,
|
|
1985
|
+
timeoutMs: 6e4
|
|
1986
|
+
});
|
|
1987
|
+
} catch {
|
|
1988
|
+
}
|
|
1926
1989
|
const port = await findFreePort();
|
|
1927
1990
|
let raw;
|
|
1928
1991
|
try {
|
|
@@ -1960,7 +2023,7 @@ async function smokeAudit(ctx) {
|
|
|
1960
2023
|
|
|
1961
2024
|
// src/audits/form-e2e.ts
|
|
1962
2025
|
var CF_TEST_SITEKEY = "1x00000000000000000000AA";
|
|
1963
|
-
var
|
|
2026
|
+
var CONTACT_PATHS = ["/contact", "/"];
|
|
1964
2027
|
var LIVE_ENV_VAR = "REDDOOR_FORM_E2E_LIVE";
|
|
1965
2028
|
function liveRunnerEnabled(env = process.env) {
|
|
1966
2029
|
return env[LIVE_ENV_VAR] === "1" || env[LIVE_ENV_VAR] === "true";
|
|
@@ -2020,6 +2083,19 @@ async function formE2eAudit(ctx) {
|
|
|
2020
2083
|
var FILL_SETTLE_MS = 1200;
|
|
2021
2084
|
var PAGE_TIMEOUT_MS2 = 3e4;
|
|
2022
2085
|
var HEALTH_TIMEOUT_MS2 = 1e4;
|
|
2086
|
+
async function findFormPath(page, baseUrl, paths = CONTACT_PATHS) {
|
|
2087
|
+
for (const path of paths) {
|
|
2088
|
+
const url = new URL(path, baseUrl).toString();
|
|
2089
|
+
const resp = await page.goto(url, { waitUntil: "domcontentloaded", timeout: PAGE_TIMEOUT_MS2 }).catch(() => null);
|
|
2090
|
+
if (!resp || !resp.ok()) continue;
|
|
2091
|
+
const forms = await page.locator("form").count().catch(() => 0);
|
|
2092
|
+
if (forms === 0) continue;
|
|
2093
|
+
const emails = await page.locator('input[name="email"], input[type="email"]').count().catch(() => 0);
|
|
2094
|
+
if (emails === 0) continue;
|
|
2095
|
+
return url;
|
|
2096
|
+
}
|
|
2097
|
+
return null;
|
|
2098
|
+
}
|
|
2023
2099
|
async function declaresTestModeForwarding(baseUrl) {
|
|
2024
2100
|
try {
|
|
2025
2101
|
const res = await fetch(new URL("/health", baseUrl), {
|
|
@@ -2046,11 +2122,8 @@ async function defaultFormRunner() {
|
|
|
2046
2122
|
try {
|
|
2047
2123
|
const ctx = await browser.newContext();
|
|
2048
2124
|
const page = await ctx.newPage();
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
const form = page.locator("form").first();
|
|
2052
|
-
const hasForm = !!resp && resp.ok() && await form.count().catch(() => 0) > 0 && await page.locator('input[name="email"], input[type="email"]').count().catch(() => 0) > 0;
|
|
2053
|
-
if (!hasForm) return { formPresent: false };
|
|
2125
|
+
const found = await findFormPath(page, baseUrl);
|
|
2126
|
+
if (!found) return { formPresent: false };
|
|
2054
2127
|
await page.fill('[name="name"]', "Reddoor Monitor").catch(() => {
|
|
2055
2128
|
});
|
|
2056
2129
|
await page.fill('[name="email"]', "monitor+e2e@reddoorla.com").catch(() => {
|
|
@@ -2159,4 +2232,4 @@ export {
|
|
|
2159
2232
|
runAudits,
|
|
2160
2233
|
runAuditsAcross
|
|
2161
2234
|
};
|
|
2162
|
-
//# sourceMappingURL=chunk-
|
|
2235
|
+
//# sourceMappingURL=chunk-YSPA5AUT.js.map
|