@reddoorla/maintenance 0.94.1 → 0.95.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.
Files changed (150) hide show
  1. package/README.md +31 -0
  2. package/dist/{announce-PGT4SZZB.js → announce-CLQYX5I4.js} +4 -4
  3. package/dist/{blux-NXQLMS4W.js → blux-4KNXUP2K.js} +1 -1
  4. package/dist/{bump-deps-6OR7LL4E.js → bump-deps-C5G2EFBB.js} +6 -6
  5. package/dist/{chunk-LS6QPANO.js → chunk-2LYOAB2E.js} +2 -2
  6. package/dist/{chunk-KQB2BX4V.js → chunk-2ZAZ7ETT.js} +2 -2
  7. package/dist/{chunk-XTK5VIQB.js → chunk-AVEQUR44.js} +48 -1
  8. package/dist/chunk-AVEQUR44.js.map +1 -0
  9. package/dist/{chunk-B6NUZ2ZX.js → chunk-BCKEP5S6.js} +2 -2
  10. package/dist/{chunk-DMPHP7UP.js → chunk-BI2VI6EM.js} +2 -2
  11. package/dist/{chunk-5FCBYK6L.js → chunk-BVEDKMNG.js} +10 -10
  12. package/dist/{chunk-OCNSIBMP.js → chunk-C3IQQKXL.js} +26 -1
  13. package/dist/{chunk-OCNSIBMP.js.map → chunk-C3IQQKXL.js.map} +1 -1
  14. package/dist/{chunk-PPIZHDJL.js → chunk-CJ2RMTT7.js} +2 -2
  15. package/dist/{chunk-7CJ3DVZW.js → chunk-CWXTNPNZ.js} +2 -2
  16. package/dist/{chunk-TGUESYGF.js → chunk-DFKBTBO3.js} +2 -2
  17. package/dist/{chunk-WHWVYL7B.js → chunk-FSGICFXB.js} +7 -32
  18. package/dist/chunk-FSGICFXB.js.map +1 -0
  19. package/dist/{chunk-3HS6LPFX.js → chunk-FUWFLAWQ.js} +4 -4
  20. package/dist/{chunk-4AIYWWKV.js → chunk-KJRVMMM3.js} +2 -2
  21. package/dist/{chunk-TCF6S7AD.js → chunk-LM2NHRCJ.js} +2 -2
  22. package/dist/{chunk-AEUABPAJ.js → chunk-R5IMFAFR.js} +2 -2
  23. package/dist/{chunk-NTLSMJHW.js → chunk-S7RYFUIK.js} +2 -2
  24. package/dist/{chunk-MLT4QY5X.js → chunk-SLFG7Y76.js} +1 -1
  25. package/dist/chunk-SLFG7Y76.js.map +1 -0
  26. package/dist/{chunk-REJCSA4H.js → chunk-TR3HFH6J.js} +4 -3
  27. package/dist/chunk-TR3HFH6J.js.map +1 -0
  28. package/dist/{chunk-22GKJO5F.js → chunk-VSGMCCGQ.js} +2 -2
  29. package/dist/{chunk-4R3YAQNV.js → chunk-WSJ7RO54.js} +2 -2
  30. package/dist/chunk-XPJQXSC4.js +112 -0
  31. package/dist/chunk-XPJQXSC4.js.map +1 -0
  32. package/dist/chunk-ZCL7C5LH.js +2698 -0
  33. package/dist/chunk-ZCL7C5LH.js.map +1 -0
  34. package/dist/{chunk-ZTPPCHDB.js → chunk-ZQV7UQNE.js} +2 -2
  35. package/dist/cli/bin.js +39 -26
  36. package/dist/cli/bin.js.map +1 -1
  37. package/dist/cli/commands/audit.d.ts +1 -1
  38. package/dist/cli/commands/audit.js +8 -8
  39. package/dist/client-G2WGWPCA.js +11 -0
  40. package/dist/configs/eslint.js +27 -1
  41. package/dist/configs/eslint.js.map +1 -1
  42. package/dist/{convert-to-pnpm-3RVGUBBR.js → convert-to-pnpm-YTFREUJK.js} +6 -6
  43. package/dist/{db-FL7ZBF6H.js → db-XKZYDCKM.js} +9 -9
  44. package/dist/{digest-TZ3QQOSF.js → digest-7UBGUBRK.js} +14 -14
  45. package/dist/{digest-collectors-EHFY2OQC.js → digest-collectors-DJFN5ZDK.js} +8 -8
  46. package/dist/{ensure-site-WIDO6LGU.js → ensure-site-OAAE5YE2.js} +2 -2
  47. package/dist/{forms-notify-target-2LWF5C5B.js → forms-notify-target-UC6PF3AE.js} +2 -2
  48. package/dist/{github-signals-GAHNOAHS.js → github-signals-TX5FPYP6.js} +5 -5
  49. package/dist/{header-image-YN3AQRY3.js → header-image-PSPT7CUM.js} +2 -2
  50. package/dist/{health-endpoint-XLNBWVHK.js → health-endpoint-KVCJYWET.js} +6 -6
  51. package/dist/{health-mirror-EB76EERV.js → health-mirror-XJAKA3EY.js} +3 -3
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.js +31 -29
  54. package/dist/index.js.map +1 -1
  55. package/dist/{init-YCLSGIEP.js → init-FFE4FCO2.js} +13 -13
  56. package/dist/{launch-FSHDTXSM.js → launch-UD36AHUN.js} +221 -7
  57. package/dist/launch-UD36AHUN.js.map +1 -0
  58. package/dist/match-harness-QRG6XZAR.js +70 -0
  59. package/dist/match-harness-QRG6XZAR.js.map +1 -0
  60. package/dist/migrate-Y7R4CESL.js +7 -0
  61. package/dist/{onboard-NKE6GZKL.js → onboard-TM2KESGF.js} +6 -6
  62. package/dist/{orchestrate-MWXENQOS.js → orchestrate-HYVQPRCE.js} +6 -6
  63. package/dist/{pipeline-MATFYQRC.js → pipeline-YROAY6DT.js} +2 -2
  64. package/dist/{preflight-4VQP7JJL.js → preflight-RYIP54NY.js} +7 -7
  65. package/dist/{prismic-ci-DM352L2Q.js → prismic-ci-6GKKZGB5.js} +13 -11
  66. package/dist/prismic-ci-6GKKZGB5.js.map +1 -0
  67. package/dist/{prismic-models-Q32ARSYS.js → prismic-models-P6FWWVRS.js} +5 -5
  68. package/dist/prospect/types.d.ts +1 -1
  69. package/dist/{prospect-audit-MT6RGMOY.js → prospect-audit-PRHU53HS.js} +5 -5
  70. package/dist/{prospect-audits-RVYEA4KH.js → prospect-audits-R3LWF6U7.js} +10 -4
  71. package/dist/recipes/sync-configs.d.ts +1 -1
  72. package/dist/recipes/sync-configs.js +4 -4
  73. package/dist/{render-JE5P26H5.js → render-YGNGTX3W.js} +2 -2
  74. package/dist/render-from-row-IFSBHWDH.js +1 -1
  75. package/dist/{report-6FFHGUUS.js → report-LQS4ECTM.js} +12 -12
  76. package/dist/{report-mirror-IP64R7B3.js → report-mirror-HDTOJE64.js} +3 -3
  77. package/dist/{schema-2V7BGYVT.js → schema-A4WCPIF4.js} +1 -1
  78. package/dist/{schema-2V7BGYVT.js.map → schema-A4WCPIF4.js.map} +1 -1
  79. package/dist/{self-updating-5DVDIEHB.js → self-updating-MWXIZLL7.js} +6 -6
  80. package/dist/{selftest-LJYXBIO5.js → selftest-VXTHRPRN.js} +9 -9
  81. package/dist/{site-mirror-UKHREAD6.js → site-mirror-WON4FTJ2.js} +3 -3
  82. package/dist/{smoke-suite-NUTC7IUK.js → smoke-suite-7DGITCTZ.js} +6 -6
  83. package/dist/{submissions-5GKJPCIH.js → submissions-OVZPVBYG.js} +2 -2
  84. package/dist/{svelte-codemods-U4NIT6CV.js → svelte-codemods-WSV2FGNG.js} +6 -6
  85. package/dist/{sync-configs-S2JYY6QU.js → sync-configs-RRIHNLIJ.js} +7 -7
  86. package/dist/{types-OIQZ5hWN.d.ts → types-D_bpy89i.d.ts} +1 -1
  87. package/dist/{upgrade-YFZCMUPE.js → upgrade-VTD6V4HS.js} +6 -6
  88. package/dist/util/git.d.ts +46 -1
  89. package/dist/util/git.js +7 -1
  90. package/package.json +1 -1
  91. package/dist/chunk-7MKT4I5T.js +0 -62
  92. package/dist/chunk-7MKT4I5T.js.map +0 -1
  93. package/dist/chunk-MLT4QY5X.js.map +0 -1
  94. package/dist/chunk-REJCSA4H.js.map +0 -1
  95. package/dist/chunk-WHWVYL7B.js.map +0 -1
  96. package/dist/chunk-XTK5VIQB.js.map +0 -1
  97. package/dist/client-M2V6FHH5.js +0 -11
  98. package/dist/launch-FSHDTXSM.js.map +0 -1
  99. package/dist/migrate-K4JETR36.js +0 -7
  100. package/dist/prismic-ci-DM352L2Q.js.map +0 -1
  101. /package/dist/{announce-PGT4SZZB.js.map → announce-CLQYX5I4.js.map} +0 -0
  102. /package/dist/{blux-NXQLMS4W.js.map → blux-4KNXUP2K.js.map} +0 -0
  103. /package/dist/{bump-deps-6OR7LL4E.js.map → bump-deps-C5G2EFBB.js.map} +0 -0
  104. /package/dist/{chunk-LS6QPANO.js.map → chunk-2LYOAB2E.js.map} +0 -0
  105. /package/dist/{chunk-KQB2BX4V.js.map → chunk-2ZAZ7ETT.js.map} +0 -0
  106. /package/dist/{chunk-B6NUZ2ZX.js.map → chunk-BCKEP5S6.js.map} +0 -0
  107. /package/dist/{chunk-DMPHP7UP.js.map → chunk-BI2VI6EM.js.map} +0 -0
  108. /package/dist/{chunk-5FCBYK6L.js.map → chunk-BVEDKMNG.js.map} +0 -0
  109. /package/dist/{chunk-PPIZHDJL.js.map → chunk-CJ2RMTT7.js.map} +0 -0
  110. /package/dist/{chunk-7CJ3DVZW.js.map → chunk-CWXTNPNZ.js.map} +0 -0
  111. /package/dist/{chunk-TGUESYGF.js.map → chunk-DFKBTBO3.js.map} +0 -0
  112. /package/dist/{chunk-3HS6LPFX.js.map → chunk-FUWFLAWQ.js.map} +0 -0
  113. /package/dist/{chunk-4AIYWWKV.js.map → chunk-KJRVMMM3.js.map} +0 -0
  114. /package/dist/{chunk-TCF6S7AD.js.map → chunk-LM2NHRCJ.js.map} +0 -0
  115. /package/dist/{chunk-AEUABPAJ.js.map → chunk-R5IMFAFR.js.map} +0 -0
  116. /package/dist/{chunk-NTLSMJHW.js.map → chunk-S7RYFUIK.js.map} +0 -0
  117. /package/dist/{chunk-22GKJO5F.js.map → chunk-VSGMCCGQ.js.map} +0 -0
  118. /package/dist/{chunk-4R3YAQNV.js.map → chunk-WSJ7RO54.js.map} +0 -0
  119. /package/dist/{chunk-ZTPPCHDB.js.map → chunk-ZQV7UQNE.js.map} +0 -0
  120. /package/dist/{client-M2V6FHH5.js.map → client-G2WGWPCA.js.map} +0 -0
  121. /package/dist/{convert-to-pnpm-3RVGUBBR.js.map → convert-to-pnpm-YTFREUJK.js.map} +0 -0
  122. /package/dist/{db-FL7ZBF6H.js.map → db-XKZYDCKM.js.map} +0 -0
  123. /package/dist/{digest-TZ3QQOSF.js.map → digest-7UBGUBRK.js.map} +0 -0
  124. /package/dist/{digest-collectors-EHFY2OQC.js.map → digest-collectors-DJFN5ZDK.js.map} +0 -0
  125. /package/dist/{ensure-site-WIDO6LGU.js.map → ensure-site-OAAE5YE2.js.map} +0 -0
  126. /package/dist/{forms-notify-target-2LWF5C5B.js.map → forms-notify-target-UC6PF3AE.js.map} +0 -0
  127. /package/dist/{github-signals-GAHNOAHS.js.map → github-signals-TX5FPYP6.js.map} +0 -0
  128. /package/dist/{header-image-YN3AQRY3.js.map → header-image-PSPT7CUM.js.map} +0 -0
  129. /package/dist/{health-endpoint-XLNBWVHK.js.map → health-endpoint-KVCJYWET.js.map} +0 -0
  130. /package/dist/{health-mirror-EB76EERV.js.map → health-mirror-XJAKA3EY.js.map} +0 -0
  131. /package/dist/{init-YCLSGIEP.js.map → init-FFE4FCO2.js.map} +0 -0
  132. /package/dist/{migrate-K4JETR36.js.map → migrate-Y7R4CESL.js.map} +0 -0
  133. /package/dist/{onboard-NKE6GZKL.js.map → onboard-TM2KESGF.js.map} +0 -0
  134. /package/dist/{orchestrate-MWXENQOS.js.map → orchestrate-HYVQPRCE.js.map} +0 -0
  135. /package/dist/{pipeline-MATFYQRC.js.map → pipeline-YROAY6DT.js.map} +0 -0
  136. /package/dist/{preflight-4VQP7JJL.js.map → preflight-RYIP54NY.js.map} +0 -0
  137. /package/dist/{prismic-models-Q32ARSYS.js.map → prismic-models-P6FWWVRS.js.map} +0 -0
  138. /package/dist/{prospect-audit-MT6RGMOY.js.map → prospect-audit-PRHU53HS.js.map} +0 -0
  139. /package/dist/{prospect-audits-RVYEA4KH.js.map → prospect-audits-R3LWF6U7.js.map} +0 -0
  140. /package/dist/{render-JE5P26H5.js.map → render-YGNGTX3W.js.map} +0 -0
  141. /package/dist/{report-6FFHGUUS.js.map → report-LQS4ECTM.js.map} +0 -0
  142. /package/dist/{report-mirror-IP64R7B3.js.map → report-mirror-HDTOJE64.js.map} +0 -0
  143. /package/dist/{self-updating-5DVDIEHB.js.map → self-updating-MWXIZLL7.js.map} +0 -0
  144. /package/dist/{selftest-LJYXBIO5.js.map → selftest-VXTHRPRN.js.map} +0 -0
  145. /package/dist/{site-mirror-UKHREAD6.js.map → site-mirror-WON4FTJ2.js.map} +0 -0
  146. /package/dist/{smoke-suite-NUTC7IUK.js.map → smoke-suite-7DGITCTZ.js.map} +0 -0
  147. /package/dist/{submissions-5GKJPCIH.js.map → submissions-OVZPVBYG.js.map} +0 -0
  148. /package/dist/{svelte-codemods-U4NIT6CV.js.map → svelte-codemods-WSV2FGNG.js.map} +0 -0
  149. /package/dist/{sync-configs-S2JYY6QU.js.map → sync-configs-RRIHNLIJ.js.map} +0 -0
  150. /package/dist/{upgrade-YFZCMUPE.js.map → upgrade-VTD6V4HS.js.map} +0 -0
@@ -78,7 +78,7 @@ async function runSubmissionsCommand(action, opts) {
78
78
  return { output: "--apply and --dry-run conflict. Pick one.", code: 2 };
79
79
  }
80
80
  const apply = opts.apply === true;
81
- const { openDb, readDbConfig } = await import("./client-M2V6FHH5.js");
81
+ const { openDb, readDbConfig } = await import("./client-G2WGWPCA.js");
82
82
  const { SPAM_THRESHOLD } = await import("./spam-classifier-T5H4VTP6.js");
83
83
  const cfg = opts.url ? { url: opts.url } : readDbConfig();
84
84
  const db = await openDb(cfg);
@@ -119,4 +119,4 @@ export {
119
119
  rescoreNewSubmissions,
120
120
  runSubmissionsCommand
121
121
  };
122
- //# sourceMappingURL=submissions-5GKJPCIH.js.map
122
+ //# sourceMappingURL=submissions-OVZPVBYG.js.map
@@ -5,20 +5,20 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-TCF6S7AD.js";
8
+ } from "./chunk-LM2NHRCJ.js";
9
9
  import {
10
10
  resolveSites
11
11
  } from "./chunk-Z2K4JPQ3.js";
12
12
  import {
13
13
  svelteCodemods
14
- } from "./chunk-LS6QPANO.js";
14
+ } from "./chunk-2LYOAB2E.js";
15
15
  import "./chunk-J7SZQUCW.js";
16
- import "./chunk-ZTPPCHDB.js";
16
+ import "./chunk-ZQV7UQNE.js";
17
17
  import "./chunk-Z3JXLULD.js";
18
18
  import "./chunk-LBYOLBW7.js";
19
- import "./chunk-XXTZBPUY.js";
20
19
  import "./chunk-RARREDQE.js";
21
- import "./chunk-XTK5VIQB.js";
20
+ import "./chunk-XXTZBPUY.js";
21
+ import "./chunk-AVEQUR44.js";
22
22
 
23
23
  // src/cli/commands/svelte-codemods.ts
24
24
  import { resolve } from "path";
@@ -51,4 +51,4 @@ async function runSvelteCodemodsCommand(site, opts) {
51
51
  export {
52
52
  runSvelteCodemodsCommand
53
53
  };
54
- //# sourceMappingURL=svelte-codemods-U4NIT6CV.js.map
54
+ //# sourceMappingURL=svelte-codemods-WSV2FGNG.js.map
@@ -5,7 +5,7 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-TCF6S7AD.js";
8
+ } from "./chunk-LM2NHRCJ.js";
9
9
  import {
10
10
  resolveSites
11
11
  } from "./chunk-Z2K4JPQ3.js";
@@ -16,17 +16,17 @@ import {
16
16
  mergeGitignore,
17
17
  planTemplateDiffs,
18
18
  syncConfigs
19
- } from "./chunk-3HS6LPFX.js";
20
- import "./chunk-ZTPPCHDB.js";
19
+ } from "./chunk-FUWFLAWQ.js";
20
+ import "./chunk-ZQV7UQNE.js";
21
21
  import {
22
22
  ALL_TEMPLATES,
23
23
  templatesByName
24
- } from "./chunk-MLT4QY5X.js";
24
+ } from "./chunk-SLFG7Y76.js";
25
25
  import "./chunk-Z3JXLULD.js";
26
26
  import "./chunk-LBYOLBW7.js";
27
- import "./chunk-XXTZBPUY.js";
28
27
  import "./chunk-RARREDQE.js";
29
- import "./chunk-XTK5VIQB.js";
28
+ import "./chunk-XXTZBPUY.js";
29
+ import "./chunk-AVEQUR44.js";
30
30
 
31
31
  // src/cli/commands/sync-configs.ts
32
32
  import { readFile } from "fs/promises";
@@ -107,4 +107,4 @@ async function runSyncConfigsCommand(site, opts) {
107
107
  export {
108
108
  runSyncConfigsCommand
109
109
  };
110
- //# sourceMappingURL=sync-configs-S2JYY6QU.js.map
110
+ //# sourceMappingURL=sync-configs-RRIHNLIJ.js.map
@@ -15,7 +15,7 @@ type Site = {
15
15
  meta?: Record<string, unknown>;
16
16
  };
17
17
  type AuditName = "deps" | "lighthouse" | "a11y" | "security" | "lint" | "domain" | "browser" | "netlify-deploy" | "function-health" | "smoke" | "form-e2e";
18
- type RecipeName = "sync-configs" | "bump-deps" | "svelte-4-to-5" | "svelte-codemods" | "convert-to-pnpm" | "onboard" | "a11y-fixtures-page" | "health-endpoint" | "smoke-suite" | "self-updating" | "prismic-ci" | "init";
18
+ type RecipeName = "sync-configs" | "bump-deps" | "svelte-4-to-5" | "svelte-codemods" | "convert-to-pnpm" | "onboard" | "a11y-fixtures-page" | "health-endpoint" | "smoke-suite" | "self-updating" | "prismic-ci" | "match-harness" | "init";
19
19
  type ConfigName = "lighthouse" | "eslint" | "prettier" | "prettier-ignore" | "playwright-a11y" | "svelte" | "gitignore" | "renovate-action" | "renovate-config" | "netlify";
20
20
  type AuditResult = {
21
21
  audit: AuditName;
@@ -5,21 +5,21 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-TCF6S7AD.js";
8
+ } from "./chunk-LM2NHRCJ.js";
9
9
  import {
10
10
  resolveSites
11
11
  } from "./chunk-Z2K4JPQ3.js";
12
12
  import {
13
13
  upgradeSvelte4to5
14
- } from "./chunk-B6NUZ2ZX.js";
14
+ } from "./chunk-BCKEP5S6.js";
15
15
  import "./chunk-J7SZQUCW.js";
16
- import "./chunk-ZTPPCHDB.js";
16
+ import "./chunk-ZQV7UQNE.js";
17
17
  import "./chunk-OTGB6YQO.js";
18
18
  import "./chunk-Z3JXLULD.js";
19
19
  import "./chunk-LBYOLBW7.js";
20
- import "./chunk-XXTZBPUY.js";
21
20
  import "./chunk-RARREDQE.js";
22
- import "./chunk-XTK5VIQB.js";
21
+ import "./chunk-XXTZBPUY.js";
22
+ import "./chunk-AVEQUR44.js";
23
23
 
24
24
  // src/cli/commands/upgrade.ts
25
25
  import { resolve } from "path";
@@ -60,4 +60,4 @@ async function runUpgradeCommand(upgradeName, site, opts = {}) {
60
60
  export {
61
61
  runUpgradeCommand
62
62
  };
63
- //# sourceMappingURL=upgrade-YFZCMUPE.js.map
63
+ //# sourceMappingURL=upgrade-VTD6V4HS.js.map
@@ -1,3 +1,5 @@
1
+ import { S as Site } from '../types-D_bpy89i.js';
2
+
1
3
  declare function branchName(recipe: string, when?: Date): string;
2
4
  declare function currentBranch(cwd: string): Promise<string>;
3
5
  declare function isWorkingTreeClean(cwd: string): Promise<boolean>;
@@ -23,6 +25,30 @@ declare function deleteBranch(cwd: string, name: string): Promise<void>;
23
25
  declare function stageAll(cwd: string): Promise<void>;
24
26
  declare function listTrackedFiles(cwd: string): Promise<string[]>;
25
27
  declare function removeFromIndex(cwd: string, paths: string[]): Promise<void>;
28
+ /**
29
+ * The subset of `paths` that is NOT in HEAD's tree — what a fresh clone would
30
+ * NOT get. POSITIVE evidence: a path counts as installed only when it is FOUND
31
+ * in `git ls-tree`'s output. `git add -A` honours .gitignore and exits 0 either
32
+ * way, so "staging did not error" proves nothing about what landed.
33
+ *
34
+ * `-z`, because `ls-tree` C-quotes any path outside core.quotePath's safe set
35
+ * (`"caf\303\251.txt"`), which would read as missing. NOT `--full-tree`:
36
+ * without it `ls-tree` reports paths relative to `cwd`, the same base the
37
+ * caller's relative paths use.
38
+ *
39
+ * A git failure (no HEAD yet, not a repo) yields an EMPTY tree, so every path
40
+ * comes back missing. An error may only ever DENY.
41
+ */
42
+ declare function pathsMissingFromHead(cwd: string, paths: readonly string[]): Promise<string[]>;
43
+ /**
44
+ * Why git is excluding `paths`: `<source>:<line>:<pattern>\t<path>` rows from
45
+ * `git check-ignore -v --no-index`. A path with no row is absent for some other
46
+ * reason (a nested repository, say) and the caller must say so rather than
47
+ * guess. Never throws: check-ignore exits 1 when NOTHING matches and 128 on
48
+ * error, and both must read as "no rule found" — the caller is already
49
+ * reporting a failure and must not lose it to a second one.
50
+ */
51
+ declare function ignoreRulesFor(cwd: string, paths: readonly string[]): Promise<string[]>;
26
52
  /**
27
53
  * Stages all current changes and commits with `message`. Returns the commit SHA,
28
54
  * or `null` if there was nothing to commit.
@@ -50,7 +76,26 @@ declare function sameOwnerRepo(a: string, b: string): boolean;
50
76
  declare function parseOwnerRepo(remoteUrl: string): string | null;
51
77
  /** `origin` remote URL for a checkout, trimmed. Throws (via git) if there's no origin. */
52
78
  declare function getRemoteUrl(cwd: string): Promise<string>;
79
+ /**
80
+ * Resolve the `owner/repo` a recipe will act on. An explicit `site.gitRepo`
81
+ * (Airtable, or a JSON inventory) wins; otherwise derive it from the checkout's
82
+ * `origin`.
83
+ *
84
+ * Returns `null` when there is nothing to act on (no `gitRepo`, no origin) — a
85
+ * benign "nothing wired" state. THROWS when a value IS present but does not
86
+ * match the strict `owner/repo` shape: callers write repo secrets, branch
87
+ * protection and pull requests at this identity, so a typo'd or
88
+ * attacker-controlled value must be rejected here, before the first `gh` call.
89
+ *
90
+ * Extracted from `self-updating`'s private `resolveRepo` because `prismic-ci`
91
+ * read `site.gitRepo` directly and therefore refused every POSITIONAL run with
92
+ * "no Git repo on this site" — `localPath()` (src/inventory/local.ts:11) builds
93
+ * `{ path, name }` and nothing else. A site being bootstrapped is exactly the
94
+ * case that has no Airtable row yet (`--fleet airtable` filters pre-launch
95
+ * statuses), so the positional path is the ONLY one `/new-site` can use.
96
+ */
97
+ declare function resolveOwnerRepo(site: Site): Promise<string | null>;
53
98
  /** Push a branch to origin, setting upstream. Throws on non-zero (execFile rejects). */
54
99
  declare function push(cwd: string, branch: string): Promise<void>;
55
100
 
56
- export { OWNER_REPO_RE, branchName, checkoutBranch, commit, createBranch, currentBranch, deleteBranch, forceCheckoutBranch, getRemoteUrl, isOwnerRepo, isWorkingTreeClean, listTrackedFiles, parseOwnerRepo, push, removeFromIndex, sameOwnerRepo, stageAll };
101
+ export { OWNER_REPO_RE, branchName, checkoutBranch, commit, createBranch, currentBranch, deleteBranch, forceCheckoutBranch, getRemoteUrl, ignoreRulesFor, isOwnerRepo, isWorkingTreeClean, listTrackedFiles, parseOwnerRepo, pathsMissingFromHead, push, removeFromIndex, resolveOwnerRepo, sameOwnerRepo, stageAll };
package/dist/util/git.js CHANGED
@@ -8,15 +8,18 @@ import {
8
8
  deleteBranch,
9
9
  forceCheckoutBranch,
10
10
  getRemoteUrl,
11
+ ignoreRulesFor,
11
12
  isOwnerRepo,
12
13
  isWorkingTreeClean,
13
14
  listTrackedFiles,
14
15
  parseOwnerRepo,
16
+ pathsMissingFromHead,
15
17
  push,
16
18
  removeFromIndex,
19
+ resolveOwnerRepo,
17
20
  sameOwnerRepo,
18
21
  stageAll
19
- } from "../chunk-XTK5VIQB.js";
22
+ } from "../chunk-AVEQUR44.js";
20
23
  export {
21
24
  OWNER_REPO_RE,
22
25
  branchName,
@@ -27,12 +30,15 @@ export {
27
30
  deleteBranch,
28
31
  forceCheckoutBranch,
29
32
  getRemoteUrl,
33
+ ignoreRulesFor,
30
34
  isOwnerRepo,
31
35
  isWorkingTreeClean,
32
36
  listTrackedFiles,
33
37
  parseOwnerRepo,
38
+ pathsMissingFromHead,
34
39
  push,
35
40
  removeFromIndex,
41
+ resolveOwnerRepo,
36
42
  sameOwnerRepo,
37
43
  stageAll
38
44
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reddoorla/maintenance",
3
- "version": "0.94.1",
3
+ "version": "0.95.1",
4
4
  "description": "Canonical maintenance configs, audits, and recipes for the reddoor stack.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,62 +0,0 @@
1
- // src/db/prospect-audits.ts
2
- import { randomBytes } from "crypto";
3
- function generateToken() {
4
- return randomBytes(16).toString("base64url");
5
- }
6
- function isValidToken(s) {
7
- return /^[A-Za-z0-9_-]{22}$/.test(s);
8
- }
9
- function newProspectAuditId() {
10
- return `pa_${crypto.randomUUID()}`;
11
- }
12
- function siteKey(url) {
13
- const raw = url.trim();
14
- if (raw === "") return "";
15
- try {
16
- const u = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`);
17
- const host = u.hostname.toLowerCase().replace(/^www\./, "");
18
- const path = u.pathname.replace(/\/+$/, "");
19
- return `${host}${path}`;
20
- } catch {
21
- return raw;
22
- }
23
- }
24
- async function createProspectAudit(db, audit) {
25
- const id = newProspectAuditId();
26
- const token = generateToken();
27
- await db.insertInto("prospect_audits").values({
28
- id,
29
- token,
30
- url: audit.url,
31
- site_key: siteKey(audit.url),
32
- business: audit.business,
33
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
34
- status: audit.status ?? "complete",
35
- result_json: audit.resultJson
36
- }).execute();
37
- return { id, token };
38
- }
39
- async function getProspectAuditByToken(db, token) {
40
- const row = await db.selectFrom("prospect_audits").select(["id", "url", "business", "created_at", "status", "result_json"]).where("token", "=", token).executeTakeFirst();
41
- return row ?? null;
42
- }
43
- var MAX_RECENT_PROSPECT_AUDITS = 100;
44
- function clampLimit(limit) {
45
- const n = Number.isFinite(limit) ? Math.trunc(limit) : 1;
46
- return Math.min(Math.max(n, 1), MAX_RECENT_PROSPECT_AUDITS);
47
- }
48
- async function listRecentProspectAudits(db, limit) {
49
- return db.selectFrom("prospect_audits").select(["id", "token", "url", "business", "status", "created_at"]).orderBy("created_at", "desc").limit(clampLimit(limit)).execute();
50
- }
51
-
52
- export {
53
- generateToken,
54
- isValidToken,
55
- newProspectAuditId,
56
- siteKey,
57
- createProspectAudit,
58
- getProspectAuditByToken,
59
- MAX_RECENT_PROSPECT_AUDITS,
60
- listRecentProspectAudits
61
- };
62
- //# sourceMappingURL=chunk-7MKT4I5T.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/db/prospect-audits.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport type { Db } from \"./client.js\";\n\n/** 16 random bytes → 22-char base64url string: unguessable, URL-safe. */\nexport function generateToken(): string {\n return randomBytes(16).toString(\"base64url\");\n}\n\n/** Anything not exactly generateToken()-shaped is a probe, not a report. */\nexport function isValidToken(s: string): boolean {\n return /^[A-Za-z0-9_-]{22}$/.test(s);\n}\n\n/** Opaque, collision-free id (mirrors newSubmissionId / newDeadLetterId).\n * crypto is a Node 20 global — no new dep. Nothing reads this format; `token`\n * is the public handle. */\nexport function newProspectAuditId(): string {\n return `pa_${crypto.randomUUID()}`;\n}\n\n/** \"complete\" when every pipeline stage succeeded; \"partial\" when any stage\n * failed or was deliberately skipped (StageResult's `ok: false` covers\n * both — the pipeline doesn't distinguish them at this layer). The report\n * itself already degrades each failed section to \"not measured\"; this is\n * what lets a future dashboard tell complete from partial without\n * deserializing the whole result_json. */\nexport type ProspectAuditStatus = \"complete\" | \"partial\";\n\n/**\n * The lineage handle for a site: one key for every way of writing its address.\n *\n * `url` is kept exactly as it was given, because what we audited is a fact\n * about the run. It is the wrong thing to group by, though, and the corpus\n * already proves it — `beachfrontdentistry.com` and `beachfrontdentistry.com/`\n * are two separate histories in the table today, as are the two spellings of\n * ludlowkingsley.com. Nothing has broken only because nothing yet compares two\n * audits of one site. The moment the report offers a before and an after, a\n * trailing slash turns a returning client into a stranger.\n *\n * Scheme, a leading \"www.\", a trailing slash, a query string and a fragment all\n * go: none of them distinguish one site from another. The path stays, because a\n * site living in a subdirectory genuinely is a different site.\n *\n * Never throws. A key we cannot compute must not take an audit down with it —\n * an unparseable input falls back to its trimmed self, which groups nothing and\n * so is no worse than having no key at all.\n */\nexport function siteKey(url: string): string {\n const raw = url.trim();\n if (raw === \"\") return \"\";\n try {\n const u = new URL(/^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `https://${raw}`);\n const host = u.hostname.toLowerCase().replace(/^www\\./, \"\");\n const path = u.pathname.replace(/\\/+$/, \"\");\n return `${host}${path}`;\n } catch {\n return raw;\n }\n}\n\nexport type NewProspectAudit = {\n url: string;\n business: string | null;\n resultJson: string;\n /** Defaults to \"complete\" — the column's own SQL default — for callers\n * (tests, ad-hoc scripts) that don't track per-stage outcomes. The\n * prospect-audit CLI, the only real writer, always computes and passes\n * this explicitly. */\n status?: ProspectAuditStatus;\n};\n\nexport async function createProspectAudit(\n db: Db,\n audit: NewProspectAudit,\n): Promise<{ id: string; token: string }> {\n const id = newProspectAuditId();\n const token = generateToken();\n await db\n .insertInto(\"prospect_audits\")\n .values({\n id,\n token,\n url: audit.url,\n site_key: siteKey(audit.url),\n business: audit.business,\n created_at: new Date().toISOString(),\n status: audit.status ?? \"complete\",\n result_json: audit.resultJson,\n })\n .execute();\n return { id, token };\n}\n\nexport type ProspectAuditRow = {\n id: string;\n url: string;\n business: string | null;\n created_at: string;\n status: string;\n result_json: string;\n};\n\nexport async function getProspectAuditByToken(\n db: Db,\n token: string,\n): Promise<ProspectAuditRow | null> {\n const row = await db\n .selectFrom(\"prospect_audits\")\n .select([\"id\", \"url\", \"business\", \"created_at\", \"status\", \"result_json\"])\n .where(\"token\", \"=\", token)\n .executeTakeFirst();\n return row ?? null;\n}\n\n/** The columns the cockpit's /audits listing page shows. Deliberately NOT\n * `result_json` — large, and useless to a list — but `token` IS selected:\n * the listing needs it to build each row's `/r/{token}` link, so it's\n * rendered only into an href, never displayed as a raw value. */\nexport type ProspectAuditListItem = {\n id: string;\n token: string;\n url: string;\n business: string | null;\n status: string;\n created_at: string;\n};\n\n/** Ceiling on `listRecentProspectAudits`' `limit`, enforced defensively (a\n * caller passing 10_000 must not get 10_000 rows) — well past anything a\n * listing page would ever render on one screen. */\nexport const MAX_RECENT_PROSPECT_AUDITS = 100;\n\n/** Clamp a caller-supplied limit into [1, MAX_RECENT_PROSPECT_AUDITS], with a\n * safe fallback of 1 for anything non-finite (NaN, ±Infinity) rather than\n * letting a bad input reach the query unclamped. */\nfunction clampLimit(limit: number): number {\n const n = Number.isFinite(limit) ? Math.trunc(limit) : 1;\n return Math.min(Math.max(n, 1), MAX_RECENT_PROSPECT_AUDITS);\n}\n\n/** Newest-first audits for the cockpit's /audits listing page, capped both\n * defensively (see clampLimit) and by an index (migration 0010) so the\n * ORDER BY never falls back to a raw scan + a temp b-tree sort — see the\n * EXPLAIN-query-plan gate, tests/db/query-plans.test.ts. */\nexport async function listRecentProspectAudits(\n db: Db,\n limit: number,\n): Promise<ProspectAuditListItem[]> {\n return db\n .selectFrom(\"prospect_audits\")\n .select([\"id\", \"token\", \"url\", \"business\", \"status\", \"created_at\"])\n .orderBy(\"created_at\", \"desc\")\n .limit(clampLimit(limit))\n .execute();\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAIrB,SAAS,gBAAwB;AACtC,SAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAC7C;AAGO,SAAS,aAAa,GAAoB;AAC/C,SAAO,sBAAsB,KAAK,CAAC;AACrC;AAKO,SAAS,qBAA6B;AAC3C,SAAO,MAAM,OAAO,WAAW,CAAC;AAClC;AA6BO,SAAS,QAAQ,KAAqB;AAC3C,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,QAAQ,GAAI,QAAO;AACvB,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,2BAA2B,KAAK,GAAG,IAAI,MAAM,WAAW,GAAG,EAAE;AAC/E,UAAM,OAAO,EAAE,SAAS,YAAY,EAAE,QAAQ,UAAU,EAAE;AAC1D,UAAM,OAAO,EAAE,SAAS,QAAQ,QAAQ,EAAE;AAC1C,WAAO,GAAG,IAAI,GAAG,IAAI;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAaA,eAAsB,oBACpB,IACA,OACwC;AACxC,QAAM,KAAK,mBAAmB;AAC9B,QAAM,QAAQ,cAAc;AAC5B,QAAM,GACH,WAAW,iBAAiB,EAC5B,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,KAAK,MAAM;AAAA,IACX,UAAU,QAAQ,MAAM,GAAG;AAAA,IAC3B,UAAU,MAAM;AAAA,IAChB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,QAAQ,MAAM,UAAU;AAAA,IACxB,aAAa,MAAM;AAAA,EACrB,CAAC,EACA,QAAQ;AACX,SAAO,EAAE,IAAI,MAAM;AACrB;AAWA,eAAsB,wBACpB,IACA,OACkC;AAClC,QAAM,MAAM,MAAM,GACf,WAAW,iBAAiB,EAC5B,OAAO,CAAC,MAAM,OAAO,YAAY,cAAc,UAAU,aAAa,CAAC,EACvE,MAAM,SAAS,KAAK,KAAK,EACzB,iBAAiB;AACpB,SAAO,OAAO;AAChB;AAkBO,IAAM,6BAA6B;AAK1C,SAAS,WAAW,OAAuB;AACzC,QAAM,IAAI,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI;AACvD,SAAO,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,0BAA0B;AAC5D;AAMA,eAAsB,yBACpB,IACA,OACkC;AAClC,SAAO,GACJ,WAAW,iBAAiB,EAC5B,OAAO,CAAC,MAAM,SAAS,OAAO,YAAY,UAAU,YAAY,CAAC,EACjE,QAAQ,cAAc,MAAM,EAC5B,MAAM,WAAW,KAAK,CAAC,EACvB,QAAQ;AACb;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/recipes/sync-configs/templates.ts","../src/recipes/sync-configs/renovate-action.ts"],"sourcesContent":["import type { ConfigName } from \"../../types.js\";\n\nexport type ConfigTemplate = {\n config: ConfigName;\n path: string;\n contents: string;\n};\n\nconst eslint: ConfigTemplate = {\n config: \"eslint\",\n path: \"eslint.config.js\",\n contents: `import { createEslintConfig } from \"@reddoorla/maintenance/configs/eslint\";\nimport svelteConfig from \"./svelte.config.js\";\n\nexport default createEslintConfig({ svelteConfig });\n`,\n};\n\nconst prettier: ConfigTemplate = {\n config: \"prettier\",\n path: \".prettierrc.json\",\n contents: `{\n \"trailingComma\": \"all\",\n \"singleQuote\": false,\n \"printWidth\": 100,\n \"plugins\": [\"prettier-plugin-svelte\"]\n}\n`,\n};\n\nconst prettierIgnore: ConfigTemplate = {\n config: \"prettier-ignore\",\n path: \".prettierignore\",\n contents: `pnpm-lock.yaml\n.svelte-kit/\nbuild/\n.netlify/\ndist/\n`,\n};\n\nconst lighthouse: ConfigTemplate = {\n config: \"lighthouse\",\n path: \"lighthouserc.json\",\n contents: `${JSON.stringify(\n {\n $note:\n \"Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.\",\n extends: \"@reddoorla/maintenance/configs/lighthouse\",\n },\n null,\n 2,\n )}\n`,\n};\n\nconst playwrightA11y: ConfigTemplate = {\n config: \"playwright-a11y\",\n path: \"playwright.config.ts\",\n contents: `export { default } from \"@reddoorla/maintenance/configs/playwright-a11y\";\n`,\n};\n\nconst svelte: ConfigTemplate = {\n config: \"svelte\",\n path: \"svelte.config.js\",\n contents: `import { createSvelteConfig } from \"@reddoorla/maintenance/configs/svelte\";\nimport adapter from \"@sveltejs/adapter-netlify\";\n\n/** @type {import('@sveltejs/kit').Config} */\nexport default createSvelteConfig({\n kit: { adapter: adapter({ edge: false, split: false }) },\n});\n`,\n};\n\n// There is deliberately NO ci.yml template. Every live fleet ci.yml carries\n// per-site values a static template cannot own (`netlify-site:`, plus\n// `permissions:` and `node-version:` inputs) — an exact-match heal against a\n// canonical byte template would strip them fleet-wide in green auto-mergeable\n// PRs (the \"armed clobber\" the 2026-08-02 architecture review flagged; it was\n// this file's `ci` entry). Ownership is split instead: the STARTER clone\n// provides each site's ci.yml shape at bootstrap, and Renovate's\n// github-actions manager bumps the pinned reusable-workflow ref per repo when\n// reddoorla/.github tags a new version. The caller job name `ci` + the\n// reusable workflow's job `ci` produce the check context \"ci / ci\" —\n// REQUIRED_CHECK in src/recipes/self-updating/index.ts depends on that naming.\n\nconst renovateAction: ConfigTemplate = {\n config: \"renovate-action\",\n path: \".github/workflows/renovate.yml\",\n contents: `name: renovate\non:\n schedule:\n # Twice daily. GitHub's platform auto-merge is disabled fleet-wide, so Renovate\n # performs its own merges from inside this run — which means this cron is the\n # MERGE cadence, not just the PR-creation cadence.\n - cron: 0 */12 * * *\n workflow_dispatch:\npermissions:\n contents: read\njobs:\n renovate:\n runs-on: ubuntu-latest\n steps:\n # Renovate authenticates as the reddoor-renovate GitHub App (installed\n # org-wide, \"All repositories\" — future repos are auto-covered), NOT as an\n # operator PAT. Why: the App is a distinct identity, so the audit trail\n # finally distinguishes bot from human (the 2026-07-26 unreviewed-major\n # forensics took hours precisely because it couldn't); the credential is\n # permission-scoped and centrally revocable instead of a fleet-write PAT;\n # and the installation gets its own API-quota pool instead of sharing the\n # operator's. The minted token is scoped to THIS repository only\n # (create-github-app-token's default when owner/repositories are omitted).\n # RENOVATE_APP_ID / RENOVATE_APP_PRIVATE_KEY are org-level (all-repos\n # visibility) — nothing to configure per repo.\n # Digest-pinned: this workflow mints a repo-write token, so a mutable tag\n # ref here is a supply-chain hole — a retagged \\`@v3\\`/\\`@v46\\` would run\n # attacker code with that token. Pin to the commit; Renovate bumps these.\n - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0\n id: app-token\n with:\n app-id: \\${{ vars.RENOVATE_APP_ID }}\n private-key: \\${{ secrets.RENOVATE_APP_PRIVATE_KEY }}\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7\n - uses: renovatebot/github-action@1a96852b0384df1837619d04c60b2d10d1f9ff08 # v46.1.21\n with:\n token: \\${{ steps.app-token.outputs.token }}\n env:\n RENOVATE_REPOSITORIES: \\${{ github.repository }}\n # Commit/PR identity = the App's bot user, so gitAuthor matches the\n # token identity (mismatched authors read as \"operator pushed this\").\n # 312185038 = the bot's user id (gh api 'users/reddoor-renovate[bot]' --jq .id).\n RENOVATE_USERNAME: reddoor-renovate[bot]\n RENOVATE_GIT_AUTHOR: reddoor-renovate[bot] <312185038+reddoor-renovate[bot]@users.noreply.github.com>\n`,\n};\n\nconst renovateConfig: ConfigTemplate = {\n config: \"renovate-config\",\n path: \"renovate.json\",\n contents: `{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\"github>reddoorla/.github:renovate-config\"]\n}\n`,\n};\n\nconst netlify: ConfigTemplate = {\n config: \"netlify\",\n path: \"netlify.toml\",\n contents: `[build]\n command = \"pnpm build\"\n publish = \"build/\"\n functions = \"functions/\"\n\n[build.environment]\n NODE_VERSION = \"22\"\n COREPACK_INTEGRITY_KEYS = \"0\"\n\n# Baseline security headers for all responses. CSP is emitted per-response by\n# SvelteKit (see \\`kit.csp\\` in svelte.config.js) so it is intentionally omitted\n# here to avoid conflicting duplicates.\n[[headers]]\n for = \"/*\"\n [headers.values]\n Strict-Transport-Security = \"max-age=63072000; includeSubDomains; preload\"\n X-Content-Type-Options = \"nosniff\"\n X-Frame-Options = \"SAMEORIGIN\"\n Referrer-Policy = \"strict-origin-when-cross-origin\"\n Permissions-Policy = \"camera=(), microphone=(), geolocation=(), interest-cohort=()\"\n Cross-Origin-Opener-Policy = \"same-origin\"\n\n[[headers]]\n for = \"/favicon.png\"\n [headers.values]\n Cache-Control = \"public, max-age=31536000, immutable\"\n\n[[headers]]\n for = \"/_app/immutable/*\"\n [headers.values]\n Cache-Control = \"public, max-age=31536000, immutable\"\n`,\n};\n\nexport const ALL_TEMPLATES: ConfigTemplate[] = [\n eslint,\n prettier,\n prettierIgnore,\n lighthouse,\n playwrightA11y,\n svelte,\n renovateAction,\n renovateConfig,\n netlify,\n];\n\nexport function templatesByName(which: ConfigName[]): ConfigTemplate[] {\n return ALL_TEMPLATES.filter((t) => which.includes(t.config));\n}\n","import type { ConfigName } from \"../../types.js\";\n\n/**\n * `.github/workflows/renovate.yml` (the `renovate-action` template) is\n * compliance-checked, not byte-matched, for the same reason `svelte.config.js`\n * and `netlify.toml` already are (see `isSvelteConfigCompliant` /\n * `isNetlifyConfigCompliant` in `sync-configs.ts`): an exact overwrite is the\n * wrong operation whenever a file legitimately diverges from the template\n * without being wrong. For renovate.yml there are TWO such legitimate\n * divergences, both observed live on 2026-08-31 (issue #651):\n *\n * 1. Renovate bumps its own digest pins. The template pins\n * `renovatebot/github-action@1a96852b...` (# v46.1.21); a site Renovate\n * maintains moves that pin forward on its own (reddoor-starter was already\n * at `@e09d604f...` / v46.2.2). A byte-match recipe reads that as drift and\n * opens a PR that DOWNGRADES the pin — reverting Renovate's own\n * supply-chain update, the exact thing the template's own comment says\n * Renovate is supposed to do. Observed live: it opened\n * `reddoorla/reddoor-starter-blux#1`, closed unmerged.\n * 2. YAML scalar quoting. The template writes an unquoted twice-daily cron and\n * `RENOVATE_USERNAME: reddoor-renovate[bot]` unquoted; a site's prettier\n * may render both quoted instead. Both forms are prettier-clean (verified\n * against the repo's own prettier), so prettier never normalizes one\n * toward the other — once a site's copy diverges in quoting, a byte-match\n * recipe reports drift FOREVER.\n *\n * The fix, following the svelte.config/netlify.toml precedent exactly: stop\n * asking \"is this file byte-identical to the template?\" and ask \"does it\n * still satisfy the invariants we actually own?\". `renovateActionGaps`\n * returns that — a `string[]` of human-readable gaps, empty meaning\n * compliant and left alone, the same shape as `rulesetGaps` (src/github/rulesets.ts)\n * and `renovateGaps` (src/audits/protection-coverage.ts).\n *\n * When a file genuinely IS non-compliant (e.g. its cron changed) the recipe\n * heals it by writing the template — but writing the template verbatim would\n * itself re-introduce the downgrade bug above, overwriting the site's newer\n * digest pins with the template's older ones. `withRenovatePinsFrom` carries\n * the site's own (still-digest-pinned) refs forward onto the template before\n * it is written — see that function's own doc comment for exactly what\n * guarantee this is (and, as importantly, isn't).\n *\n * Every check below runs against a COMMENT-STRIPPED copy of the file (see\n * `stripComments`). A raw-text `.match()`/`.includes()` takes the FIRST hit\n * anywhere in the file, comments included — a canonical pin, cron, or job\n * body sitting in a `#` comment above the real (non-compliant) line reads as\n * compliant, and worse, `withRenovatePinsFrom` would scrape a sha out of a\n * comment like `# old: uses: actions/checkout@1111…1111 # v4` and carry a\n * *commented* pin forward into the healed file — a pin downgrade delivered by\n * the very recipe that exists to prevent pin downgrades.\n */\n\nconst DIGEST_RE = /^[0-9a-f]{40}$/;\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Strip full-line `#` comments before any compliance check or ref extraction\n * runs, so a canonical-looking line that exists only in a comment can never\n * read as the real thing.\n *\n * Full-line only: a trailing `# vX.Y.Z` after a real `uses:` line is\n * meaningful (it's the human-readable version tag) and must survive — only\n * strip a line whose first non-whitespace character is `#`. This is safe\n * specifically because this workflow's YAML has no block scalars (`|`/`>`),\n * so there is no line in a compliant or non-compliant renovate.yml where a\n * leading `#` is literal string content rather than a comment.\n */\nfunction stripComments(contents: string): string {\n return contents.replace(/^[ \\t]*#[^\\n]*$/gm, \"\");\n}\n\n/** A `uses:` line, anchored to the start of a line (allowing only leading\n * indentation and an optional `- ` list marker before `uses:`) so a match\n * can only ever land on a real step, never on prose inside a comment that\n * happens to contain the word `uses:`. Always run against comment-stripped\n * text (see `stripComments`) — the anchor alone does not exclude comments,\n * since a full-line comment's content can itself start with `- uses:` at\n * matching indentation. */\nconst USES_LINE_RE = /^([ \\t]*(?:- )?)uses:\\s*([\\w.-]+\\/[\\w.-]+)@(\\S+)(?:[ \\t]*#[^\\r\\n]*)?/gm;\n\n/** The ref (and trailing `# vX.Y.Z` comment, if any) a `uses: <slug>@<ref>`\n * line carries in `contents`, or null if that action isn't referenced.\n * Callers must pass comment-stripped text — see `stripComments`. */\nfunction usesRefAndComment(\n contents: string,\n slug: string,\n): { ref: string; comment: string } | null {\n const re = new RegExp(\n `^[ \\\\t]*(?:- )?uses:\\\\s*${escapeRegExp(slug)}@(\\\\S+)([ \\\\t]*#[^\\\\r\\\\n]*)?`,\n \"m\",\n );\n const m = contents.match(re);\n if (!m) return null;\n return { ref: m[1]!, comment: m[2] ?? \"\" };\n}\n\n/** Gap text for one `uses:` action that must be present AND digest-pinned.\n * This workflow mints a repo-write token (the App-token step), so a mutable\n * tag ref on ANY step here is a supply-chain hole — a retagged `@v3`/`@v46`\n * would run attacker code with that token. (The three actions checked here\n * are the ones this workflow ships with by default; `renovateActionGaps`\n * separately widens this to ANY `uses:` line, so an action added later is\n * covered too.) `contents` must already be comment-stripped. */\nfunction pinGap(slug: string, contents: string): string | null {\n const found = usesRefAndComment(contents, slug);\n if (!found || !DIGEST_RE.test(found.ref)) {\n return `${slug} is missing or not digest-pinned (needs @<40-hex-sha>) — this workflow mints a repo-write token, so a mutable tag ref is a supply-chain hole`;\n }\n return null;\n}\n\n/** The three actions this workflow ships with by default and always requires,\n * present and digest-pinned. Checked individually (via `pinGap`, above) so a\n * gap can say which one of the three is the problem; the widen loop in\n * `renovateActionGaps` then covers every OTHER `uses:` line so a step added\n * later can't slip in on a mutable ref unnoticed. */\nconst KNOWN_PINNED_ACTIONS = [\n \"renovatebot/github-action\",\n \"actions/create-github-app-token\",\n \"actions/checkout\",\n];\n\nconst APP_AUTH_MARKERS = [\n \"vars.RENOVATE_APP_ID\",\n \"secrets.RENOVATE_APP_PRIVATE_KEY\",\n \"steps.app-token.outputs.token\",\n];\n\n/** Gaps in a site's `.github/workflows/renovate.yml`. Empty = compliant,\n * leave it alone — see the module doc comment above for why this is\n * compliance-checked rather than byte-matched. */\nexport function renovateActionGaps(contents: string): string[] {\n const gaps: string[] = [];\n const withoutComments = stripComments(contents);\n\n for (const slug of KNOWN_PINNED_ACTIONS) {\n const gap = pinGap(slug, withoutComments);\n if (gap) gaps.push(gap);\n }\n\n // Widen the pin check to every OTHER `uses:` line in the file — the doc\n // comment on `pinGap` says a mutable ref on ANY step is a supply-chain\n // hole; without this, a step added later (e.g. `pnpm/action-setup@v4`)\n // would pass clean just because it isn't one of the three known actions.\n // A local `uses: ./.github/actions/foo` path ref never matches — the regex\n // requires a literal `@<ref>`, which a bare path reference doesn't have.\n for (const m of withoutComments.matchAll(USES_LINE_RE)) {\n const slug = m[2]!;\n const ref = m[3]!;\n if (KNOWN_PINNED_ACTIONS.includes(slug)) continue; // already covered above\n if (!DIGEST_RE.test(ref)) {\n gaps.push(\n `${slug}@${ref} is not digest-pinned (needs @<40-hex-sha>) — this workflow mints a repo-write token, so every action reference must be digest-pinned`,\n );\n }\n }\n\n const missingAuth = APP_AUTH_MARKERS.filter((m) => !withoutComments.includes(m));\n if (missingAuth.length > 0) {\n gaps.push(\n `App auth is not fully wired — missing ${missingAuth.join(\", \")} (Renovate authenticates as the reddoor-renovate GitHub App, not an operator PAT)`,\n );\n }\n\n if (withoutComments.includes(\"RENOVATE_TOKEN\")) {\n gaps.push(\n \"contains RENOVATE_TOKEN — that is the retired operator-PAT identity the App migration replaced\",\n );\n }\n\n // Checked (and reported) as two independent gaps, not one folded string, so\n // a caller can tell which field actually failed.\n if (!/RENOVATE_USERNAME:\\s*[\"']?reddoor-renovate\\[bot\\]/.test(withoutComments)) {\n gaps.push(\"RENOVATE_USERNAME does not identify reddoor-renovate[bot]\");\n }\n if (!/RENOVATE_GIT_AUTHOR:\\s*[\"']?reddoor-renovate\\[bot\\]/.test(withoutComments)) {\n gaps.push(\"RENOVATE_GIT_AUTHOR does not identify reddoor-renovate[bot]\");\n }\n\n if (\n !/RENOVATE_REPOSITORIES:\\s*[\"']?\\$\\{\\{\\s*github\\.repository\\s*\\}\\}[\"']?/.test(withoutComments)\n ) {\n gaps.push(\n \"RENOVATE_REPOSITORIES is not scoped to ${{ github.repository }} — this workflow must only ever touch its own repo\",\n );\n }\n\n if (!/cron:\\s*[\"']?0 \\*\\/12 \\* \\* \\*[\"']?/.test(withoutComments)) {\n gaps.push(\n 'cron is not the twice-daily \"0 */12 * * *\" schedule — this cron IS the fleet\\'s merge cadence (platform auto-merge is disabled fleet-wide, so Renovate merges from inside this run)',\n );\n }\n\n if (!withoutComments.includes(\"workflow_dispatch\")) {\n gaps.push(\"missing workflow_dispatch trigger\");\n }\n\n // A `permissions:` key is present somewhere AND a `contents: read` line\n // (optionally quoted) exists somewhere — not anchored to top-level-first-key,\n // since this predicate doesn't actually need to assert that shape (a\n // job-level permissions block satisfies the same invariant), and requiring\n // `contents: read` to be the first entry under `permissions:` false-positived\n // on both quoting (`contents: \"read\"`) and ordering (`contents: read` listed\n // after another key, e.g. `pull-requests: write`).\n const hasPermissionsKey = withoutComments.includes(\"permissions:\");\n const hasContentsRead = /^[ \\t]*contents:\\s*[\"']?read[\"']?[ \\t]*$/m.test(withoutComments);\n if (!hasPermissionsKey || !hasContentsRead) {\n gaps.push(\"permissions: does not grant contents: read\");\n }\n\n return gaps;\n}\n\n/**\n * The canonical template with each action's `uses:` ref replaced by the ref\n * the site already has, when the site's is digest-pinned.\n *\n * Why: when a file IS genuinely non-compliant (say its cron changed) the\n * recipe heals it by writing the template — and writing the template verbatim\n * would put the template's OLDER pins over the site's newer ones, re-\n * introducing the downgrade this fix removes. So carry the site's pins\n * forward onto the (otherwise-canonical) healed file.\n *\n * This is PIN-NEUTRAL, not downgrade-avoiding: it substitutes the site's ref\n * whenever it is 40 hex characters, with no notion of \"newer\" — digests are\n * unorderable, so there is no way to tell from the strings alone. It never\n * changes a digest-pinned ref in either direction; some fleet sites carry a\n * pin OLDER than the template's and will heal onto their own older pin\n * (defensible — Renovate re-advances it on its own), so \"pin-neutral\" is the\n * accurate description, not \"downgrade-avoiding\".\n *\n * `current === null` (no existing file) returns the template unchanged — there\n * is nothing to carry forward. A non-digest ref on the site (a mutable tag) is\n * never substituted in, since that would carry the mutable-ref hole forward\n * instead of healing it. Likewise, a ref that appears only inside a `#`\n * comment in `current` is never carried forward (see `stripComments`) — a\n * commented sha must never be scraped and pinned into the healed file.\n */\nexport function withRenovatePinsFrom(template: string, current: string | null): string {\n if (current === null) return template;\n const currentWithoutComments = stripComments(current);\n return template.replace(USES_LINE_RE, (full, prefix: string, slug: string) => {\n const found = usesRefAndComment(currentWithoutComments, slug);\n if (!found || !DIGEST_RE.test(found.ref)) return full;\n return `${prefix}uses: ${slug}@${found.ref}${found.comment}`;\n });\n}\n\n/** The `ConfigName` for `.github/workflows/renovate.yml`. Exported once here\n * (rather than re-declared as a local constant in both `sync-configs.ts` and\n * `self-updating/index.ts`) so the two call sites can't drift apart. */\nexport const RENOVATE_ACTION_CONFIG: ConfigName = \"renovate-action\";\n"],"mappings":"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","names":[]}