@reddoorla/maintenance 0.55.0 → 0.57.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.
Files changed (150) hide show
  1. package/README.md +3 -1
  2. package/dist/airtable-2BJX4ATJ.js +9 -0
  3. package/dist/airtable-2BJX4ATJ.js.map +1 -0
  4. package/dist/announce-JEVCDDKL.js +171 -0
  5. package/dist/announce-JEVCDDKL.js.map +1 -0
  6. package/dist/bump-deps-O3SOLQYY.js +57 -0
  7. package/dist/bump-deps-O3SOLQYY.js.map +1 -0
  8. package/dist/chunk-2X322ZES.js +99 -0
  9. package/dist/chunk-2X322ZES.js.map +1 -0
  10. package/dist/chunk-4NAOR44V.js +153 -0
  11. package/dist/chunk-4NAOR44V.js.map +1 -0
  12. package/dist/chunk-4PY4I5OR.js +1167 -0
  13. package/dist/chunk-4PY4I5OR.js.map +1 -0
  14. package/dist/chunk-5L2QXUTU.js +279 -0
  15. package/dist/chunk-5L2QXUTU.js.map +1 -0
  16. package/dist/chunk-5WL3ET24.js +203 -0
  17. package/dist/chunk-5WL3ET24.js.map +1 -0
  18. package/dist/chunk-6LIWDIXU.js +38 -0
  19. package/dist/chunk-6LIWDIXU.js.map +1 -0
  20. package/dist/chunk-6VAL7XJT.js +52 -0
  21. package/dist/chunk-6VAL7XJT.js.map +1 -0
  22. package/dist/chunk-BWLUNY7L.js +585 -0
  23. package/dist/chunk-BWLUNY7L.js.map +1 -0
  24. package/dist/chunk-CASCWQQX.js +161 -0
  25. package/dist/chunk-CASCWQQX.js.map +1 -0
  26. package/dist/chunk-CLVTVDOU.js +274 -0
  27. package/dist/chunk-CLVTVDOU.js.map +1 -0
  28. package/dist/chunk-D3DQFC32.js +79 -0
  29. package/dist/chunk-D3DQFC32.js.map +1 -0
  30. package/dist/chunk-EYODPUHV.js +37 -0
  31. package/dist/chunk-EYODPUHV.js.map +1 -0
  32. package/dist/chunk-FDIFTIXC.js +70 -0
  33. package/dist/chunk-FDIFTIXC.js.map +1 -0
  34. package/dist/chunk-FUD4I2ZW.js +35 -0
  35. package/dist/chunk-FUD4I2ZW.js.map +1 -0
  36. package/dist/chunk-HG5DTFNF.js +62 -0
  37. package/dist/chunk-HG5DTFNF.js.map +1 -0
  38. package/dist/chunk-HN6I2XWI.js +65 -0
  39. package/dist/chunk-HN6I2XWI.js.map +1 -0
  40. package/dist/chunk-HVOOCK6L.js +84 -0
  41. package/dist/chunk-HVOOCK6L.js.map +1 -0
  42. package/dist/chunk-IMIR7SC2.js +51 -0
  43. package/dist/chunk-IMIR7SC2.js.map +1 -0
  44. package/dist/chunk-J3OPWFZE.js +64 -0
  45. package/dist/chunk-J3OPWFZE.js.map +1 -0
  46. package/dist/chunk-J4ECCFRT.js +221 -0
  47. package/dist/chunk-J4ECCFRT.js.map +1 -0
  48. package/dist/chunk-JNXCDUYB.js +211 -0
  49. package/dist/chunk-JNXCDUYB.js.map +1 -0
  50. package/dist/chunk-JQG222CI.js +49 -0
  51. package/dist/chunk-JQG222CI.js.map +1 -0
  52. package/dist/chunk-LKGVSM2O.js +43 -0
  53. package/dist/chunk-LKGVSM2O.js.map +1 -0
  54. package/dist/chunk-LYCKN7ZR.js +41 -0
  55. package/dist/chunk-LYCKN7ZR.js.map +1 -0
  56. package/dist/chunk-MKSZBODM.js +162 -0
  57. package/dist/chunk-MKSZBODM.js.map +1 -0
  58. package/dist/chunk-O65I7G7G.js +246 -0
  59. package/dist/chunk-O65I7G7G.js.map +1 -0
  60. package/dist/chunk-OCS7SZUZ.js +243 -0
  61. package/dist/chunk-OCS7SZUZ.js.map +1 -0
  62. package/dist/chunk-OGGRFBL4.js +14 -0
  63. package/dist/chunk-OGGRFBL4.js.map +1 -0
  64. package/dist/chunk-OQPMZ6BM.js +368 -0
  65. package/dist/chunk-OQPMZ6BM.js.map +1 -0
  66. package/dist/chunk-OTGB6YQO.js +52 -0
  67. package/dist/chunk-OTGB6YQO.js.map +1 -0
  68. package/dist/chunk-PZQ3WED2.js +14 -0
  69. package/dist/chunk-PZQ3WED2.js.map +1 -0
  70. package/dist/chunk-QTHKOIFJ.js +13 -0
  71. package/dist/chunk-QTHKOIFJ.js.map +1 -0
  72. package/dist/chunk-U5SZ4FOP.js +26 -0
  73. package/dist/chunk-U5SZ4FOP.js.map +1 -0
  74. package/dist/chunk-W3G7XFBZ.js +444 -0
  75. package/dist/chunk-W3G7XFBZ.js.map +1 -0
  76. package/dist/chunk-WEKL2HYJ.js +31 -0
  77. package/dist/chunk-WEKL2HYJ.js.map +1 -0
  78. package/dist/chunk-WVRPOXKH.js +74 -0
  79. package/dist/chunk-WVRPOXKH.js.map +1 -0
  80. package/dist/chunk-XKOZUVK2.js +121 -0
  81. package/dist/chunk-XKOZUVK2.js.map +1 -0
  82. package/dist/chunk-XO2RUYVM.js +138 -0
  83. package/dist/chunk-XO2RUYVM.js.map +1 -0
  84. package/dist/chunk-XTK5VIQB.js +97 -0
  85. package/dist/chunk-XTK5VIQB.js.map +1 -0
  86. package/dist/chunk-XXTZBPUY.js +9 -0
  87. package/dist/chunk-XXTZBPUY.js.map +1 -0
  88. package/dist/chunk-ZYBOT5PM.js +153 -0
  89. package/dist/chunk-ZYBOT5PM.js.map +1 -0
  90. package/dist/cli/bin.js +75 -7785
  91. package/dist/cli/bin.js.map +1 -1
  92. package/dist/cli/commands/audit.js +37 -2336
  93. package/dist/cli/commands/audit.js.map +1 -1
  94. package/dist/client-EPVO3FEH.js +10 -0
  95. package/dist/client-EPVO3FEH.js.map +1 -0
  96. package/dist/client-JA6QLEDW.js +48 -0
  97. package/dist/client-JA6QLEDW.js.map +1 -0
  98. package/dist/configs/lighthouse.js +4 -31
  99. package/dist/configs/lighthouse.js.map +1 -1
  100. package/dist/configs/playwright-a11y.js +6 -33
  101. package/dist/configs/playwright-a11y.js.map +1 -1
  102. package/dist/convert-to-pnpm-UR64X3OX.js +51 -0
  103. package/dist/convert-to-pnpm-UR64X3OX.js.map +1 -0
  104. package/dist/db-AXKMRTVA.js +23 -0
  105. package/dist/db-AXKMRTVA.js.map +1 -0
  106. package/dist/digest-YTO73BEO.js +408 -0
  107. package/dist/digest-YTO73BEO.js.map +1 -0
  108. package/dist/forms/index.js +3 -8
  109. package/dist/forms/index.js.map +1 -1
  110. package/dist/github-signals-XLQXXH6Q.js +116 -0
  111. package/dist/github-signals-XLQXXH6Q.js.map +1 -0
  112. package/dist/index.js +102 -4757
  113. package/dist/index.js.map +1 -1
  114. package/dist/init-JITQXFVL.js +85 -0
  115. package/dist/init-JITQXFVL.js.map +1 -0
  116. package/dist/launch-XW6PEKUL.js +207 -0
  117. package/dist/launch-XW6PEKUL.js.map +1 -0
  118. package/dist/lighthouse-airtable-4FKYKFH6.js +12 -0
  119. package/dist/lighthouse-airtable-4FKYKFH6.js.map +1 -0
  120. package/dist/migrate-P2LS4WXI.js +7 -0
  121. package/dist/migrate-P2LS4WXI.js.map +1 -0
  122. package/dist/onboard-TCQOP2W4.js +69 -0
  123. package/dist/onboard-TCQOP2W4.js.map +1 -0
  124. package/dist/orchestrate-LHH3LQNP.js +23 -0
  125. package/dist/orchestrate-LHH3LQNP.js.map +1 -0
  126. package/dist/recipes/sync-configs.js +9 -505
  127. package/dist/recipes/sync-configs.js.map +1 -1
  128. package/dist/renovate-dispatch-YVBTBJJY.js +100 -0
  129. package/dist/renovate-dispatch-YVBTBJJY.js.map +1 -0
  130. package/dist/report-WK3SKWJI.js +240 -0
  131. package/dist/report-WK3SKWJI.js.map +1 -0
  132. package/dist/resend-G2BJMAQV.js +7 -0
  133. package/dist/resend-G2BJMAQV.js.map +1 -0
  134. package/dist/self-updating-BYUBUNO4.js +61 -0
  135. package/dist/self-updating-BYUBUNO4.js.map +1 -0
  136. package/dist/svelte-codemods-BYRJAU7I.js +51 -0
  137. package/dist/svelte-codemods-BYRJAU7I.js.map +1 -0
  138. package/dist/sync-configs-JPRLAW5J.js +109 -0
  139. package/dist/sync-configs-JPRLAW5J.js.map +1 -0
  140. package/dist/upgrade-ERA45EVR.js +64 -0
  141. package/dist/upgrade-ERA45EVR.js.map +1 -0
  142. package/dist/util/git.js +19 -76
  143. package/dist/util/git.js.map +1 -1
  144. package/dist/util/pkg.js +5 -45
  145. package/dist/util/pkg.js.map +1 -1
  146. package/dist/websites-X5DLC2PC.js +43 -0
  147. package/dist/websites-X5DLC2PC.js.map +1 -0
  148. package/dist/write-audits-to-airtable-CE4POBAF.js +13 -0
  149. package/dist/write-audits-to-airtable-CE4POBAF.js.map +1 -0
  150. package/package.json +13 -12
@@ -1,508 +1,12 @@
1
- // src/recipes/sync-configs.ts
2
- import { readFile, writeFile, mkdir } from "fs/promises";
3
- import { join, dirname } from "path";
4
-
5
- // src/recipes/sync-configs/templates.ts
6
- var eslint = {
7
- config: "eslint",
8
- path: "eslint.config.js",
9
- contents: `import { createEslintConfig } from "@reddoorla/maintenance/configs/eslint";
10
- import svelteConfig from "./svelte.config.js";
11
-
12
- export default createEslintConfig({ svelteConfig });
13
- `
14
- };
15
- var prettier = {
16
- config: "prettier",
17
- path: ".prettierrc.json",
18
- contents: `{
19
- "trailingComma": "all",
20
- "singleQuote": false,
21
- "printWidth": 100,
22
- "plugins": ["prettier-plugin-svelte"]
23
- }
24
- `
25
- };
26
- var prettierIgnore = {
27
- config: "prettier-ignore",
28
- path: ".prettierignore",
29
- contents: `pnpm-lock.yaml
30
- .svelte-kit/
31
- build/
32
- .netlify/
33
- dist/
34
- `
35
- };
36
- var lighthouse = {
37
- config: "lighthouse",
38
- path: "lighthouserc.json",
39
- contents: `${JSON.stringify(
40
- {
41
- $note: "Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.",
42
- extends: "@reddoorla/maintenance/configs/lighthouse"
43
- },
44
- null,
45
- 2
46
- )}
47
- `
48
- };
49
- var playwrightA11y = {
50
- config: "playwright-a11y",
51
- path: "playwright.config.ts",
52
- contents: `export { default } from "@reddoorla/maintenance/configs/playwright-a11y";
53
- `
54
- };
55
- var svelte = {
56
- config: "svelte",
57
- path: "svelte.config.js",
58
- contents: `import { createSvelteConfig } from "@reddoorla/maintenance/configs/svelte";
59
- import adapter from "@sveltejs/adapter-netlify";
60
-
61
- /** @type {import('@sveltejs/kit').Config} */
62
- export default createSvelteConfig({
63
- kit: { adapter: adapter({ edge: false, split: false }) },
64
- });
65
- `
66
- };
67
- var ci = {
68
- config: "ci",
69
- path: ".github/workflows/ci.yml",
70
- contents: `name: ci
71
- on:
72
- pull_request:
73
- push:
74
- branches: [main]
75
- jobs:
76
- ci:
77
- uses: reddoorla/.github/.github/workflows/ci.yml@78c4da64b675f0f474961f12715f2a4c09d46eb5 # v1.0.0
78
- `
79
- };
80
- var renovateAction = {
81
- config: "renovate-action",
82
- path: ".github/workflows/renovate.yml",
83
- contents: `name: renovate
84
- on:
85
- schedule:
86
- - cron: "0 7 * * 1"
87
- workflow_dispatch:
88
- jobs:
89
- renovate:
90
- runs-on: ubuntu-latest
91
- steps:
92
- - uses: actions/checkout@v4
93
- - uses: renovatebot/github-action@v46.1.14
94
- with:
95
- token: \${{ secrets.RENOVATE_TOKEN }}
96
- env:
97
- RENOVATE_REPOSITORIES: \${{ github.repository }}
98
- `
99
- };
100
- var renovateConfig = {
101
- config: "renovate-config",
102
- path: "renovate.json",
103
- contents: `{
104
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
105
- "extends": ["github>reddoorla/.github:renovate-config"]
106
- }
107
- `
108
- };
109
- var netlify = {
110
- config: "netlify",
111
- path: "netlify.toml",
112
- contents: `[build]
113
- command = "pnpm build"
114
- publish = "build/"
115
- functions = "functions/"
116
-
117
- [build.environment]
118
- NODE_VERSION = "22"
119
- COREPACK_INTEGRITY_KEYS = "0"
120
-
121
- # Baseline security headers for all responses. CSP is emitted per-response by
122
- # SvelteKit (see \`kit.csp\` in svelte.config.js) so it is intentionally omitted
123
- # here to avoid conflicting duplicates.
124
- [[headers]]
125
- for = "/*"
126
- [headers.values]
127
- Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
128
- X-Content-Type-Options = "nosniff"
129
- X-Frame-Options = "SAMEORIGIN"
130
- Referrer-Policy = "strict-origin-when-cross-origin"
131
- Permissions-Policy = "camera=(), microphone=(), geolocation=(), interest-cohort=()"
132
- Cross-Origin-Opener-Policy = "same-origin"
133
-
134
- [[headers]]
135
- for = "/favicon.png"
136
- [headers.values]
137
- Cache-Control = "public, max-age=31536000, immutable"
138
-
139
- [[headers]]
140
- for = "/_app/immutable/*"
141
- [headers.values]
142
- Cache-Control = "public, max-age=31536000, immutable"
143
- `
144
- };
145
- var ALL_TEMPLATES = [
146
- eslint,
147
- prettier,
148
- prettierIgnore,
149
- lighthouse,
150
- playwrightA11y,
151
- svelte,
152
- ci,
153
- renovateAction,
154
- renovateConfig,
155
- netlify
156
- ];
157
- function templatesByName(which) {
158
- return ALL_TEMPLATES.filter((t) => which.includes(t.config));
159
- }
160
-
161
- // src/recipes/sync-configs/gitignore.ts
162
- var MANAGED_MARKER = "# canonical entries from @reddoorla/maintenance sync-configs";
163
- var CANONICAL_GITIGNORE_ENTRIES = [
164
- "node_modules/",
165
- "build/",
166
- "dist/",
167
- ".svelte-kit/",
168
- "coverage/",
169
- ".vitest-cache/",
170
- "playwright-report/",
171
- "test-results/",
172
- ".lighthouseci/",
173
- ".tsbuildinfo",
174
- ".env",
175
- ".env.*",
176
- "!.env.example",
177
- ".DS_Store",
178
- "*.log",
179
- ".vercel/",
180
- ".netlify/",
181
- ".reddoor-a11y/",
182
- // The a11y audit's transient spec dir, written inside the checkout and
183
- // normally cleaned, but a timeout-SIGKILL of the parent orphans it. Ignored
184
- // fleet-wide so it never dirties a self-updating repo's tree (2026-06-10 M-D).
185
- ".reddoor-a11y-spec-*/"
186
- ];
187
- function stripLeadingSlash(s) {
188
- return s.startsWith("/") ? s.slice(1) : s;
189
- }
190
- function stripTrailingSlash(s) {
191
- return s.endsWith("/") ? s.slice(0, -1) : s;
192
- }
193
- function normalizePresence(line) {
194
- return stripTrailingSlash(stripLeadingSlash(line.trim()));
195
- }
196
- function presentSet(existing) {
197
- const set = /* @__PURE__ */ new Set();
198
- for (const raw of existing.split(/\r?\n/)) {
199
- const trimmed = raw.trim();
200
- if (!trimmed) continue;
201
- if (trimmed.startsWith("#")) continue;
202
- set.add(normalizePresence(trimmed));
203
- }
204
- return set;
205
- }
206
- function mergeGitignore(existing, canonical) {
207
- if (existing === null) {
208
- const body = [MANAGED_MARKER, ...canonical].join("\n") + "\n";
209
- return { content: body, added: [...canonical] };
210
- }
211
- const present = presentSet(existing);
212
- const added = [];
213
- for (const entry of canonical) {
214
- const norm = normalizePresence(entry);
215
- if (!present.has(norm)) {
216
- added.push(entry);
217
- present.add(norm);
218
- }
219
- }
220
- if (added.length === 0) {
221
- return { content: existing, added: [] };
222
- }
223
- let base = existing;
224
- if (!base.endsWith("\n")) base += "\n";
225
- const block = ["", MANAGED_MARKER, ...added].join("\n") + "\n";
226
- return { content: base + block, added };
227
- }
228
- function findTrackedArtifacts(tracked, canonical) {
229
- const dirEntries = [];
230
- for (const raw of canonical) {
231
- const t = raw.trim();
232
- if (!t) continue;
233
- if (t.startsWith("!")) continue;
234
- if (/[*?[]/.test(t)) continue;
235
- const noLead = stripLeadingSlash(t);
236
- if (!noLead.endsWith("/")) continue;
237
- const name = stripTrailingSlash(noLead);
238
- if (!name) continue;
239
- dirEntries.push(name);
240
- }
241
- const matched = [];
242
- for (const path of tracked) {
243
- for (const dir of dirEntries) {
244
- if (path === dir || path.startsWith(dir + "/")) {
245
- matched.push(path);
246
- break;
247
- }
248
- }
249
- }
250
- return matched;
251
- }
252
-
253
- // src/util/git.ts
254
- import { execFile } from "child_process";
255
- import { promisify } from "util";
256
- var exec = promisify(execFile);
257
- async function git(cwd, args) {
258
- return exec("git", args, { cwd, env: process.env });
259
- }
260
- function branchName(recipe, when = /* @__PURE__ */ new Date()) {
261
- const compact = when.toISOString().replace(/[-:.]/g, "");
262
- return `maint/${recipe}-${compact}`;
263
- }
264
- async function currentBranch(cwd) {
265
- const { stdout } = await git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
266
- return stdout.trim();
267
- }
268
- async function isWorkingTreeClean(cwd) {
269
- const { stdout } = await git(cwd, ["status", "--porcelain"]);
270
- return stdout.trim().length === 0;
271
- }
272
- async function createBranch(cwd, name) {
273
- await git(cwd, ["checkout", "-b", name]);
274
- }
275
- async function checkoutBranch(cwd, name) {
276
- await git(cwd, ["checkout", name]);
277
- }
278
- async function forceCheckoutBranch(cwd, name) {
279
- await git(cwd, ["checkout", "-f", name]);
280
- }
281
- async function deleteBranch(cwd, name) {
282
- await git(cwd, ["branch", "-D", name]);
283
- }
284
- async function stageAll(cwd) {
285
- await git(cwd, ["add", "-A"]);
286
- }
287
- async function listTrackedFiles(cwd) {
288
- const { stdout } = await git(cwd, ["ls-files"]);
289
- return stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
290
- }
291
- async function removeFromIndex(cwd, paths) {
292
- if (paths.length === 0) return;
293
- await git(cwd, ["rm", "-r", "--cached", "--", ...paths]);
294
- }
295
- async function commit(cwd, message) {
296
- await stageAll(cwd);
297
- const { stdout: status } = await git(cwd, ["status", "--porcelain"]);
298
- if (status.trim().length === 0) return null;
299
- await git(cwd, ["commit", "-m", message]);
300
- const { stdout: sha } = await git(cwd, ["rev-parse", "HEAD"]);
301
- return sha.trim();
302
- }
303
-
304
- // src/util/site.ts
305
- function siteLabel(site) {
306
- return site.name || site.path;
307
- }
308
-
309
- // src/recipes/_with-recipe.ts
310
- async function withRecipe(body) {
311
- const label = siteLabel(body.site);
312
- if (body.checkTreeFirst && !await isWorkingTreeClean(body.site.path)) {
313
- throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);
314
- }
315
- const planned = await body.plan();
316
- if (planned.kind === "noop") {
317
- return {
318
- recipe: body.name,
319
- site: label,
320
- status: "noop",
321
- commits: [],
322
- ...planned.notes ? { notes: planned.notes } : {}
323
- };
324
- }
325
- if (planned.kind === "failed") {
326
- return {
327
- recipe: body.name,
328
- site: label,
329
- status: "failed",
330
- commits: [],
331
- notes: planned.notes
332
- };
333
- }
334
- if (!body.checkTreeFirst && !await isWorkingTreeClean(body.site.path)) {
335
- throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);
336
- }
337
- let original = null;
338
- try {
339
- original = await currentBranch(body.site.path);
340
- } catch {
341
- original = null;
342
- }
343
- const branch = branchName(body.name);
344
- await createBranch(body.site.path, branch);
345
- const restoreOriginal = async () => {
346
- if (original === null || original === branch) return;
347
- try {
348
- await checkoutBranch(body.site.path, original);
349
- } catch (err) {
350
- console.warn(
351
- `warning: could not restore branch ${original} after ${body.name}: ${err instanceof Error ? err.message : String(err)}`
352
- );
353
- }
354
- };
355
- const restoreAfterFailure = async () => {
356
- if (original === null || original === branch) return;
357
- try {
358
- await forceCheckoutBranch(body.site.path, original);
359
- } catch (err) {
360
- console.warn(
361
- `warning: could not force-restore branch ${original} after failed ${body.name}: ${err instanceof Error ? err.message : String(err)}`
362
- );
363
- return;
364
- }
365
- try {
366
- await deleteBranch(body.site.path, branch);
367
- } catch (err) {
368
- console.warn(
369
- `warning: could not delete recipe branch ${branch} after failed ${body.name}: ${err instanceof Error ? err.message : String(err)}`
370
- );
371
- }
372
- };
373
- const shas = [];
374
- let result;
375
- try {
376
- result = await body.apply(planned.plan, {
377
- cwd: body.site.path,
378
- branch,
379
- commit: async (msg) => {
380
- const sha = await commit(body.site.path, msg);
381
- if (sha) shas.push(sha);
382
- return sha;
383
- }
384
- });
385
- } catch (err) {
386
- await restoreAfterFailure();
387
- throw err;
388
- }
389
- if (result.kind === "failed") {
390
- await restoreAfterFailure();
391
- return {
392
- recipe: body.name,
393
- site: label,
394
- status: "failed",
395
- commits: shas,
396
- notes: result.notes
397
- };
398
- }
399
- if (shas.length === 0) {
400
- await restoreOriginal();
401
- }
402
- const notes = result.notes ? `${result.notes}; branch: ${branch}` : `branch: ${branch}`;
403
- return {
404
- recipe: body.name,
405
- site: label,
406
- status: shas.length > 0 ? "applied" : "noop",
407
- commits: shas,
408
- notes
409
- };
410
- }
411
-
412
- // src/recipes/sync-configs.ts
413
- var GITIGNORE_CONFIG = "gitignore";
414
- var SVELTE_CONFIG = "svelte";
415
- var NETLIFY_CONFIG = "netlify";
416
- function isSvelteConfigCompliant(contents) {
417
- return contents.includes("createSvelteConfig") && contents.includes("@sveltejs/adapter-netlify");
418
- }
419
- var SECURITY_HEADER_RE = /Strict-Transport-Security|Content-Security-Policy|X-Frame-Options|X-Content-Type-Options|Referrer-Policy|Permissions-Policy|Cross-Origin-Opener-Policy/i;
420
- function isNetlifyConfigCompliant(contents) {
421
- return contents.includes("[[headers]]") && SECURITY_HEADER_RE.test(contents);
422
- }
423
- var ALL_CONFIG_NAMES = [
424
- "lighthouse",
425
- "eslint",
426
- "prettier",
427
- "prettier-ignore",
428
- "playwright-a11y",
429
- "svelte",
430
- "gitignore",
431
- "ci",
432
- "renovate-action",
433
- "renovate-config",
434
- "netlify"
435
- ];
436
- function isConfigName(value) {
437
- return ALL_CONFIG_NAMES.includes(value);
438
- }
439
- async function readMaybe(path) {
440
- try {
441
- return await readFile(path, "utf-8");
442
- } catch {
443
- return null;
444
- }
445
- }
446
- async function planTemplateDiffs(cwd, templates) {
447
- const diffs = [];
448
- for (const t of templates) {
449
- const existing = await readMaybe(join(cwd, t.path));
450
- if (existing === t.contents) continue;
451
- if (t.config === SVELTE_CONFIG && existing !== null && isSvelteConfigCompliant(existing)) {
452
- continue;
453
- }
454
- if (t.config === NETLIFY_CONFIG && existing !== null && isNetlifyConfigCompliant(existing)) {
455
- continue;
456
- }
457
- diffs.push(t);
458
- }
459
- return diffs;
460
- }
461
- async function planGitignore(cwd) {
462
- const existing = await readMaybe(join(cwd, ".gitignore"));
463
- const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);
464
- const tracked = await listTrackedFiles(cwd);
465
- const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);
466
- if (merge.added.length === 0 && toUntrack.length === 0) return { kind: "noop" };
467
- return { kind: "apply", content: merge.content, toUntrack, added: merge.added };
468
- }
469
- async function applyGitignore(cwd, plan) {
470
- await writeFile(join(cwd, ".gitignore"), plan.content, "utf-8");
471
- if (plan.toUntrack.length > 0) {
472
- await removeFromIndex(cwd, plan.toUntrack);
473
- }
474
- }
475
- async function syncConfigs(site, opts = {}) {
476
- const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);
477
- const templateNames = requested.filter((c) => c !== GITIGNORE_CONFIG);
478
- const templates = templatesByName(templateNames);
479
- const includeGitignore = requested.includes(GITIGNORE_CONFIG);
480
- return withRecipe({
481
- name: "sync-configs",
482
- site,
483
- plan: async () => {
484
- const templateDiffs = await planTemplateDiffs(site.path, templates);
485
- const gitignorePlan = includeGitignore ? await planGitignore(site.path) : { kind: "noop" };
486
- if (templateDiffs.length === 0 && gitignorePlan.kind === "noop") {
487
- return { kind: "noop", notes: "all targeted configs already match" };
488
- }
489
- return { kind: "apply", plan: { templateDiffs, gitignorePlan } };
490
- },
491
- apply: async ({ templateDiffs, gitignorePlan }, { commit: commit2 }) => {
492
- for (const t of templateDiffs) {
493
- const dest = join(site.path, t.path);
494
- await mkdir(dirname(dest), { recursive: true });
495
- await writeFile(dest, t.contents, "utf-8");
496
- await commit2(`chore: sync ${t.config} config from @reddoorla/maintenance`);
497
- }
498
- if (gitignorePlan.kind === "apply") {
499
- await applyGitignore(site.path, gitignorePlan);
500
- await commit2(`chore: sync gitignore from @reddoorla/maintenance`);
501
- }
502
- return { kind: "ok" };
503
- }
504
- });
505
- }
1
+ import {
2
+ ALL_CONFIG_NAMES,
3
+ isConfigName,
4
+ syncConfigs
5
+ } from "../chunk-JNXCDUYB.js";
6
+ import "../chunk-CASCWQQX.js";
7
+ import "../chunk-XKOZUVK2.js";
8
+ import "../chunk-XXTZBPUY.js";
9
+ import "../chunk-XTK5VIQB.js";
506
10
  export {
507
11
  ALL_CONFIG_NAMES,
508
12
  isConfigName,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/recipes/sync-configs.ts","../../src/recipes/sync-configs/templates.ts","../../src/recipes/sync-configs/gitignore.ts","../../src/util/git.ts","../../src/util/site.ts","../../src/recipes/_with-recipe.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport type { RecipeResult, Site, ConfigName } from \"../types.js\";\nimport { ALL_TEMPLATES, templatesByName, type ConfigTemplate } from \"./sync-configs/templates.js\";\nimport {\n CANONICAL_GITIGNORE_ENTRIES,\n mergeGitignore,\n findTrackedArtifacts,\n} from \"./sync-configs/gitignore.js\";\nimport { listTrackedFiles, removeFromIndex } from \"../util/git.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type SyncConfigsOptions = {\n which?: ConfigName[];\n};\n\nconst GITIGNORE_CONFIG: ConfigName = \"gitignore\";\nconst SVELTE_CONFIG: ConfigName = \"svelte\";\nconst NETLIFY_CONFIG: ConfigName = \"netlify\";\n\n/** A site's `svelte.config.js` is \"compliant\" — and left untouched by sync —\n * once it builds on the canonical helpers (createSvelteConfig + adapter-netlify).\n *\n * Unlike the other exact-match templates, svelte.config legitimately carries\n * site-specific `kit.alias` and `compilerOptions`; an exact overwrite would\n * clobber those on every sync (it silently dropped MSOT's $utils alias,\n * 2026-06-04). So once a config is on the canonical pattern we preserve it as-is\n * and only rewrite a genuinely off-pattern (or missing) config. `createSvelteConfig`\n * now provides the canonical `$lib` aliases itself, and a site's own `kit.alias`\n * overrides per key (and may add more), so a site's additive customization is safe\n * to preserve. */\nfunction isSvelteConfigCompliant(contents: string): boolean {\n return contents.includes(\"createSvelteConfig\") && contents.includes(\"@sveltejs/adapter-netlify\");\n}\n\n/** Any of the baseline security headers — the marker that a netlify.toml is\n * deliberately hardened (vs. e.g. a cache-control-only `[[headers]]` block). */\nconst SECURITY_HEADER_RE =\n /Strict-Transport-Security|Content-Security-Policy|X-Frame-Options|X-Content-Type-Options|Referrer-Policy|Permissions-Policy|Cross-Origin-Opener-Policy/i;\n\n/** A site's `netlify.toml` is \"compliant\" — and left untouched by sync — once it\n * carries a `[[headers]]` block AND a security header (HSTS/CSP/X-Frame-Options/…).\n *\n * Like svelte.config, netlify.toml legitimately holds site-specific config\n * (custom CSP, redirects, per-route headers). The canonical template ships the\n * baseline security headers, but an exact overwrite would CLOBBER a site's own\n * hardening — that bug stripped gallerysonder's headers on a routine sync\n * (2026-06-10). So a genuinely-hardened file is left alone, while a missing,\n * header-less (previously-stripped), OR merely cache-header file is non-compliant\n * and gets the canonical template, which backfills the security baseline. */\nfunction isNetlifyConfigCompliant(contents: string): boolean {\n return contents.includes(\"[[headers]]\") && SECURITY_HEADER_RE.test(contents);\n}\n\n/** Runtime enumeration of every `ConfigName`. Mirror of the union in\n * `src/types.ts`. Used by CLI `--only` validation; a missing entry would\n * silently accept typos. The type-test in `tests/types.test.ts` guards\n * against drift between this array and the union. */\nexport const ALL_CONFIG_NAMES: ConfigName[] = [\n \"lighthouse\",\n \"eslint\",\n \"prettier\",\n \"prettier-ignore\",\n \"playwright-a11y\",\n \"svelte\",\n \"gitignore\",\n \"ci\",\n \"renovate-action\",\n \"renovate-config\",\n \"netlify\",\n];\n\nexport function isConfigName(value: string): value is ConfigName {\n return (ALL_CONFIG_NAMES as string[]).includes(value);\n}\n\nasync function readMaybe(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function planTemplateDiffs(\n cwd: string,\n templates: ConfigTemplate[],\n): Promise<ConfigTemplate[]> {\n const diffs: ConfigTemplate[] = [];\n for (const t of templates) {\n const existing = await readMaybe(join(cwd, t.path));\n if (existing === t.contents) continue;\n // svelte.config is compliance-checked, not exact-matched: an existing config\n // already on the canonical pattern is left alone so its aliases/compilerOptions\n // survive. A missing (null) or off-pattern config still gets the canonical template.\n if (t.config === SVELTE_CONFIG && existing !== null && isSvelteConfigCompliant(existing)) {\n continue;\n }\n // netlify.toml is likewise compliance-checked: a file that already carries\n // `[[headers]]` is hardened and left alone (an exact overwrite would strip\n // its security headers). A header-less / missing file gets the template.\n if (t.config === NETLIFY_CONFIG && existing !== null && isNetlifyConfigCompliant(existing)) {\n continue;\n }\n diffs.push(t);\n }\n return diffs;\n}\n\ntype GitignorePlan =\n | { kind: \"noop\" }\n | { kind: \"apply\"; content: string; toUntrack: string[]; added: string[] };\n\nasync function planGitignore(cwd: string): Promise<GitignorePlan> {\n const existing = await readMaybe(join(cwd, \".gitignore\"));\n const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);\n const tracked = await listTrackedFiles(cwd);\n const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);\n if (merge.added.length === 0 && toUntrack.length === 0) return { kind: \"noop\" };\n return { kind: \"apply\", content: merge.content, toUntrack, added: merge.added };\n}\n\nasync function applyGitignore(\n cwd: string,\n plan: Extract<GitignorePlan, { kind: \"apply\" }>,\n): Promise<void> {\n await writeFile(join(cwd, \".gitignore\"), plan.content, \"utf-8\");\n if (plan.toUntrack.length > 0) {\n await removeFromIndex(cwd, plan.toUntrack);\n }\n}\n\nexport async function syncConfigs(\n site: Site,\n opts: SyncConfigsOptions = {},\n): Promise<RecipeResult> {\n const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);\n const templateNames = requested.filter((c): c is ConfigName => c !== GITIGNORE_CONFIG);\n const templates = templatesByName(templateNames);\n const includeGitignore = requested.includes(GITIGNORE_CONFIG);\n\n return withRecipe({\n name: \"sync-configs\",\n site,\n plan: async () => {\n const templateDiffs = await planTemplateDiffs(site.path, templates);\n const gitignorePlan: GitignorePlan = includeGitignore\n ? await planGitignore(site.path)\n : { kind: \"noop\" };\n if (templateDiffs.length === 0 && gitignorePlan.kind === \"noop\") {\n return { kind: \"noop\", notes: \"all targeted configs already match\" };\n }\n return { kind: \"apply\", plan: { templateDiffs, gitignorePlan } };\n },\n apply: async ({ templateDiffs, gitignorePlan }, { commit }) => {\n for (const t of templateDiffs) {\n const dest = join(site.path, t.path);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, t.contents, \"utf-8\");\n await commit(`chore: sync ${t.config} config from @reddoorla/maintenance`);\n }\n if (gitignorePlan.kind === \"apply\") {\n await applyGitignore(site.path, gitignorePlan);\n await commit(`chore: sync gitignore from @reddoorla/maintenance`);\n }\n return { kind: \"ok\" };\n },\n });\n}\n","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// The `ci:` job name below + the reusable workflow's `ci` job name produce the\n// branch-protection check context \"ci / ci\" — kept in sync with REQUIRED_CHECK in\n// src/recipes/self-updating/index.ts. Renaming this job means updating that constant.\nconst ci: ConfigTemplate = {\n config: \"ci\",\n path: \".github/workflows/ci.yml\",\n contents: `name: ci\non:\n pull_request:\n push:\n branches: [main]\njobs:\n ci:\n uses: reddoorla/.github/.github/workflows/ci.yml@78c4da64b675f0f474961f12715f2a4c09d46eb5 # v1.0.0\n`,\n};\n\nconst renovateAction: ConfigTemplate = {\n config: \"renovate-action\",\n path: \".github/workflows/renovate.yml\",\n contents: `name: renovate\non:\n schedule:\n - cron: \"0 7 * * 1\"\n workflow_dispatch:\njobs:\n renovate:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: renovatebot/github-action@v46.1.14\n with:\n token: \\${{ secrets.RENOVATE_TOKEN }}\n env:\n RENOVATE_REPOSITORIES: \\${{ github.repository }}\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 ci,\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","/**\n * Comment line written above the appended block so future runs (and humans)\n * can recognize the managed section. Presence of this line is incidental —\n * the merge logic is keyed on each entry's normalized form, not on the marker.\n */\nexport const MANAGED_MARKER = \"# canonical entries from @reddoorla/maintenance sync-configs\";\n\n/**\n * Build artifacts, test outputs, deploy caches, and secrets that should never\n * be tracked across the reddoor fleet. Sites may keep additional site-specific\n * entries — they are preserved on merge.\n */\nexport const CANONICAL_GITIGNORE_ENTRIES: readonly string[] = [\n \"node_modules/\",\n \"build/\",\n \"dist/\",\n \".svelte-kit/\",\n \"coverage/\",\n \".vitest-cache/\",\n \"playwright-report/\",\n \"test-results/\",\n \".lighthouseci/\",\n \".tsbuildinfo\",\n \".env\",\n \".env.*\",\n \"!.env.example\",\n \".DS_Store\",\n \"*.log\",\n \".vercel/\",\n \".netlify/\",\n \".reddoor-a11y/\",\n // The a11y audit's transient spec dir, written inside the checkout and\n // normally cleaned, but a timeout-SIGKILL of the parent orphans it. Ignored\n // fleet-wide so it never dirties a self-updating repo's tree (2026-06-10 M-D).\n \".reddoor-a11y-spec-*/\",\n];\n\nexport type MergeResult = { content: string; added: string[] };\n\nfunction stripLeadingSlash(s: string): string {\n return s.startsWith(\"/\") ? s.slice(1) : s;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n\n/**\n * Normalize for presence comparison only: strip leading `/`, trailing `/`,\n * and surrounding whitespace. `build`, `/build`, `build/`, and `/build/` all\n * collapse to the same key.\n */\nfunction normalizePresence(line: string): string {\n return stripTrailingSlash(stripLeadingSlash(line.trim()));\n}\n\nfunction presentSet(existing: string): Set<string> {\n const set = new Set<string>();\n for (const raw of existing.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n set.add(normalizePresence(trimmed));\n }\n return set;\n}\n\n/**\n * Merge `canonical` entries into `existing` .gitignore content.\n *\n * - Missing entries are appended under a managed marker comment.\n * - Existing entries (in any normalized variant — `/build`, `build/`, etc.)\n * are preserved as-is; we never rewrite the site's own lines.\n * - When every canonical entry is already present, returns the original\n * content unchanged with `added: []` — the recipe can treat that as noop.\n */\nexport function mergeGitignore(existing: string | null, canonical: readonly string[]): MergeResult {\n if (existing === null) {\n const body = [MANAGED_MARKER, ...canonical].join(\"\\n\") + \"\\n\";\n return { content: body, added: [...canonical] };\n }\n const present = presentSet(existing);\n const added: string[] = [];\n for (const entry of canonical) {\n const norm = normalizePresence(entry);\n if (!present.has(norm)) {\n added.push(entry);\n present.add(norm);\n }\n }\n if (added.length === 0) {\n return { content: existing, added: [] };\n }\n let base = existing;\n if (!base.endsWith(\"\\n\")) base += \"\\n\";\n const block = [\"\", MANAGED_MARKER, ...added].join(\"\\n\") + \"\\n\";\n return { content: base + block, added };\n}\n\n/**\n * Of the tracked paths, return those that fall under a canonical *directory*\n * entry — i.e., paths that the freshly-synced .gitignore now wants ignored\n * but which git currently has in the index.\n *\n * File-pattern entries (`.env`, `*.log`, `.DS_Store`) are intentionally\n * skipped: they may contain user-meaningful data, and `git rm --cached`\n * cannot scrub secrets from history anyway. Surfaced for manual review\n * instead of auto-removing.\n */\nexport function findTrackedArtifacts(\n tracked: readonly string[],\n canonical: readonly string[],\n): string[] {\n const dirEntries: string[] = [];\n for (const raw of canonical) {\n const t = raw.trim();\n if (!t) continue;\n if (t.startsWith(\"!\")) continue;\n if (/[*?[]/.test(t)) continue;\n const noLead = stripLeadingSlash(t);\n if (!noLead.endsWith(\"/\")) continue;\n const name = stripTrailingSlash(noLead);\n if (!name) continue;\n dirEntries.push(name);\n }\n const matched: string[] = [];\n for (const path of tracked) {\n for (const dir of dirEntries) {\n if (path === dir || path.startsWith(dir + \"/\")) {\n matched.push(path);\n break;\n }\n }\n }\n return matched;\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n // ISO with millisecond precision: 2026-05-20T10:30:00.123Z → 20260520T103000123Z.\n // Millis (vs. second-precision) shrinks the collision window for parallel runs.\n const compact = when.toISOString().replace(/[-:.]/g, \"\");\n return `maint/${recipe}-${compact}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\n/** Check out an existing branch. Throws (via git) if the branch is missing or\n * the checkout is blocked (e.g. uncommitted changes that would be overwritten). */\nexport async function checkoutBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", name]);\n}\n\n/**\n * Force-check-out an existing branch, DISCARDING any uncommitted changes on the\n * current branch. Used by the recipe failure path to return the operator to\n * their original branch even when a recipe left the work-in-progress branch\n * dirty. Only ever called with the operator's ORIGINAL branch as `name`. Does\n * not run `git clean`, so untracked operator files are left untouched.\n */\nexport async function forceCheckoutBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-f\", name]);\n}\n\n/**\n * Delete a local branch with `-D` (force). Used by the recipe failure path to\n * remove the branch the recipe itself created so a re-run starts clean. Callers\n * MUST only ever pass the recipe-created branch here, never the operator's\n * original branch.\n */\nexport async function deleteBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"branch\", \"-D\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n\n/**\n * Strict GitHub repo identity: exactly two `[A-Za-z0-9._-]` segments separated\n * by a single slash. Rejects a scheme, host, extra path segment, traversal\n * (`..`), whitespace, or an argv flag — anything that could retarget a `gh`\n * write at an unintended (attacker/typo-controlled) repo.\n */\nexport const OWNER_REPO_RE = /^[A-Za-z0-9._-]+\\/[A-Za-z0-9._-]+$/;\n\n/** True when `repo` is a clean `owner/repo` (see {@link OWNER_REPO_RE}). The\n * explicit `..` reject covers traversal segments the char-class would otherwise\n * admit (`.` is a legal repo char, so `../evil` matches the shape regex). */\nexport function isOwnerRepo(repo: string): boolean {\n if (repo.includes(\"..\")) return false;\n return OWNER_REPO_RE.test(repo);\n}\n\n/**\n * True when two repo references name the same `owner/repo`. Each side may be a\n * full remote URL (https or scp-style), or already an `owner/repo`. Used to\n * verify an existing checkout's `origin` matches the site's expected repo\n * before reusing it. Returns false if either side is unparseable.\n */\nexport function sameOwnerRepo(a: string, b: string): boolean {\n const na = isOwnerRepo(a.trim()) ? a.trim() : parseOwnerRepo(a);\n const nb = isOwnerRepo(b.trim()) ? b.trim() : parseOwnerRepo(b);\n if (na === null || nb === null) return false;\n return na.toLowerCase() === nb.toLowerCase();\n}\n\n/** Derive `owner/repo` from a git remote URL (https or scp-style). Null if unparseable. */\nexport function parseOwnerRepo(remoteUrl: string): string | null {\n const trimmed = remoteUrl\n .trim()\n .replace(/\\.git$/, \"\")\n .replace(/\\/$/, \"\");\n // scp-style: git@github.com:owner/repo\n const scp = trimmed.match(/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:(.+)$/);\n const path = scp ? scp[1]! : trimmed.replace(/^https?:\\/\\/[^/]+\\//, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n if (segments.length < 2) return null;\n return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;\n}\n\n/** `origin` remote URL for a checkout, trimmed. Throws (via git) if there's no origin. */\nexport async function getRemoteUrl(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"remote\", \"get-url\", \"origin\"]);\n return stdout.trim();\n}\n\n/** Push a branch to origin, setting upstream. Throws on non-zero (execFile rejects). */\nexport async function push(cwd: string, branch: string): Promise<void> {\n await git(cwd, [\"push\", \"-u\", \"origin\", branch]);\n}\n","import type { Site } from \"../types.js\";\n\n/** Human-friendly label for log/output formatting. Prefer the inventory's\n * `name` when present (e.g. \"caltex-landing\") and fall back to the\n * filesystem `path` when unnamed. Every audit + recipe uses this.\n *\n * Uses `||` (not `??`) deliberately: an Airtable Name that slugs to the EMPTY\n * string (`siteSlug(\"!!!\")` → \"\") is `\"\"`, not null/undefined, so `??` would let\n * it through and render a blank label. `||` falls back to the path. */\nexport function siteLabel(site: Site): string {\n return site.name || site.path;\n}\n","import type { RecipeName, RecipeResult, Site } from \"../types.js\";\nimport {\n branchName,\n checkoutBranch,\n commit as gitCommit,\n createBranch,\n currentBranch,\n deleteBranch,\n forceCheckoutBranch,\n isWorkingTreeClean,\n} from \"../util/git.js\";\nimport { siteLabel } from \"../util/site.js\";\n\n/** Outcome of the read-only planning phase. `noop` and `failed` short-circuit\n * without creating a branch; `apply` carries the recipe-specific plan data\n * forward to the apply phase. */\nexport type RecipePlan<P> =\n | { kind: \"noop\"; notes?: string }\n | { kind: \"failed\"; notes: string }\n | { kind: \"apply\"; plan: P };\n\nexport type RecipeApplyCtx = {\n /** Stage all current changes and commit. Returns the SHA, or null if\n * nothing was staged. The wrapper accumulates SHAs into the final\n * RecipeResult. */\n commit: (message: string) => Promise<string | null>;\n /** Branch name that was created for this run. */\n branch: string;\n /** Site path — same as `site.path`. */\n cwd: string;\n};\n\nexport type RecipeApplyResult = { kind: \"ok\"; notes?: string } | { kind: \"failed\"; notes: string };\n\nexport type RecipeBody<P> = {\n name: RecipeName;\n site: Site;\n /** Inspect the site and decide: noop, failed, or proceed (with plan data\n * passed to apply). Runs before the working-tree clean check unless\n * `checkTreeFirst: true` is set, so most recipes can noop on a dirty\n * tree without throwing. */\n plan: () => Promise<RecipePlan<P>>;\n /** Make the actual changes. Use `ctx.commit(msg)` for each logical step;\n * the wrapper collects SHAs into `RecipeResult.commits`. Return\n * `{ kind: \"failed\", notes }` to abort partway and surface the failure. */\n apply: (plan: P, ctx: RecipeApplyCtx) => Promise<RecipeApplyResult>;\n /** Check working tree clean BEFORE `plan()` runs. Use only when plan\n * itself mutates the tree (e.g. `bump-deps` runs `pnpm install` in plan\n * for an accurate outdated probe). Default false — clean check happens\n * after plan only if plan returns proceed, allowing noop-on-dirty for\n * read-only plans (a tree with stray edits + no recipe work to do\n * should not throw). */\n checkTreeFirst?: boolean;\n};\n\n/** Wrap a recipe's plan/apply phases. Centralises the siteLabel /\n * clean-tree check / branch creation / commit accumulation / RecipeResult\n * construction boilerplate that every recipe used to re-implement. */\nexport async function withRecipe<P>(body: RecipeBody<P>): Promise<RecipeResult> {\n const label = siteLabel(body.site);\n\n if (body.checkTreeFirst && !(await isWorkingTreeClean(body.site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);\n }\n\n const planned = await body.plan();\n\n if (planned.kind === \"noop\") {\n return {\n recipe: body.name,\n site: label,\n status: \"noop\",\n commits: [],\n ...(planned.notes ? { notes: planned.notes } : {}),\n };\n }\n if (planned.kind === \"failed\") {\n return {\n recipe: body.name,\n site: label,\n status: \"failed\",\n commits: [],\n notes: planned.notes,\n };\n }\n\n if (!body.checkTreeFirst && !(await isWorkingTreeClean(body.site.path))) {\n throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);\n }\n\n // Capture the operator's branch BEFORE we create the recipe branch, so we can\n // return them to it afterwards (#2) and so the failure path (#3) knows which\n // branch to force-restore to. Best-effort: if we can't read it (detached HEAD,\n // git error) we proceed with `original = null` and skip any force operations\n // rather than guess — we must NEVER force-discard/delete a branch we're unsure\n // of.\n let original: string | null = null;\n try {\n original = await currentBranch(body.site.path);\n } catch {\n original = null;\n }\n\n const branch = branchName(body.name);\n await createBranch(body.site.path, branch);\n\n /**\n * Best-effort restore to the operator's original branch. Never throws — a\n * restore failure must not turn an otherwise-clean recipe result into a\n * failure (#2). Skipped when we couldn't capture the original branch.\n *\n * IMPORTANT (composition): this is invoked only on the NOOP-from-apply path\n * (the recipe created a branch but committed nothing — leaving the operator\n * parked on an empty maint branch is pure downside). It is deliberately NOT\n * invoked on the APPLIED path: the fleet onboarding pipeline composes recipes\n * by running them in sequence against the SAME checkout, each building on the\n * prior's committed files in the working tree (convert-to-pnpm's lockfile →\n * onboard's deps → sync-configs → svelte-codemods). Restoring to the base\n * branch after an applied recipe would strip those files from the working\n * tree and break composition (verified live across the fleet). selfUpdating,\n * which PUSHES its branch, does its own post-push restore since its local\n * branch is disposable.\n */\n const restoreOriginal = async (): Promise<void> => {\n if (original === null || original === branch) return;\n try {\n await checkoutBranch(body.site.path, original);\n } catch (err) {\n // Leave the operator on the recipe branch rather than fail the result.\n console.warn(\n `warning: could not restore branch ${original} after ${body.name}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n };\n\n /**\n * Failure restore (#3): force the checkout back to the captured original\n * branch (discarding the recipe branch's uncommitted changes) and delete the\n * recipe branch, so a re-run starts clean. SAFETY: only ever force-checks-out\n * the captured `original` and only ever deletes `branch` (the recipe-created\n * branch); never deletes `original`, never runs `git clean`. If `original` is\n * unavailable we do nothing (the safe subset) — better to leave the operator\n * parked than to force anything we're unsure about. Best-effort: never throws.\n */\n const restoreAfterFailure = async (): Promise<void> => {\n if (original === null || original === branch) return;\n try {\n await forceCheckoutBranch(body.site.path, original);\n } catch (err) {\n console.warn(\n `warning: could not force-restore branch ${original} after failed ${body.name}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n // If we couldn't get off the recipe branch, deleting it would fail anyway;\n // and we must never delete the branch we're still on.\n return;\n }\n try {\n await deleteBranch(body.site.path, branch);\n } catch (err) {\n console.warn(\n `warning: could not delete recipe branch ${branch} after failed ${body.name}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n };\n\n const shas: string[] = [];\n let result: RecipeApplyResult;\n try {\n result = await body.apply(planned.plan, {\n cwd: body.site.path,\n branch,\n commit: async (msg) => {\n const sha = await gitCommit(body.site.path, msg);\n if (sha) shas.push(sha);\n return sha;\n },\n });\n } catch (err) {\n // Body threw mid-mutation: force-restore + delete the recipe branch so the\n // checkout is retriable, then re-throw (preserve the prior throw semantics —\n // callers like init treat an uncaught throw as an `error` step).\n await restoreAfterFailure();\n throw err;\n }\n\n if (result.kind === \"failed\") {\n await restoreAfterFailure();\n return {\n recipe: body.name,\n site: label,\n status: \"failed\",\n commits: shas,\n notes: result.notes,\n };\n }\n\n // NOOP-from-apply only: no commits to compose, so don't leave the operator\n // parked on an empty maint branch. The APPLIED path intentionally stays on the\n // maint branch so the onboarding pipeline can compose (see restoreOriginal).\n if (shas.length === 0) {\n await restoreOriginal();\n }\n\n const notes = result.notes ? `${result.notes}; branch: ${branch}` : `branch: ${branch}`;\n return {\n recipe: body.name,\n site: label,\n status: shas.length > 0 ? \"applied\" : \"noop\",\n commits: shas,\n notes,\n };\n}\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,MAAM,eAAe;;;ACO9B,IAAM,SAAyB;AAAA,EAC7B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAKZ;AAEA,IAAM,WAA2B;AAAA,EAC/B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOZ;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAMZ;AAEA,IAAM,aAA6B;AAAA,EACjC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU,GAAG,KAAK;AAAA,IAChB;AAAA,MACE,OACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAAA;AAEH;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAEZ;AAEA,IAAM,SAAyB;AAAA,EAC7B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQZ;AAKA,IAAM,KAAqB;AAAA,EACzB,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASZ;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBZ;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAKZ;AAEA,IAAM,UAA0B;AAAA,EAC9B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCZ;AAEO,IAAM,gBAAkC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,gBAAgB,OAAuC;AACrE,SAAO,cAAc,OAAO,CAAC,MAAM,MAAM,SAAS,EAAE,MAAM,CAAC;AAC7D;;;AC3KO,IAAM,iBAAiB;AAOvB,IAAM,8BAAiD;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AACF;AAIA,SAAS,kBAAkB,GAAmB;AAC5C,SAAO,EAAE,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,IAAI;AAC1C;AAEA,SAAS,mBAAmB,GAAmB;AAC7C,SAAO,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI;AAC5C;AAOA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,mBAAmB,kBAAkB,KAAK,KAAK,CAAC,CAAC;AAC1D;AAEA,SAAS,WAAW,UAA+B;AACjD,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,OAAO,SAAS,MAAM,OAAO,GAAG;AACzC,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,CAAC,QAAS;AACd,QAAI,QAAQ,WAAW,GAAG,EAAG;AAC7B,QAAI,IAAI,kBAAkB,OAAO,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAWO,SAAS,eAAe,UAAyB,WAA2C;AACjG,MAAI,aAAa,MAAM;AACrB,UAAM,OAAO,CAAC,gBAAgB,GAAG,SAAS,EAAE,KAAK,IAAI,IAAI;AACzD,WAAO,EAAE,SAAS,MAAM,OAAO,CAAC,GAAG,SAAS,EAAE;AAAA,EAChD;AACA,QAAM,UAAU,WAAW,QAAQ;AACnC,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,WAAW;AAC7B,UAAM,OAAO,kBAAkB,KAAK;AACpC,QAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACtB,YAAM,KAAK,KAAK;AAChB,cAAQ,IAAI,IAAI;AAAA,IAClB;AAAA,EACF;AACA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,EAAE,SAAS,UAAU,OAAO,CAAC,EAAE;AAAA,EACxC;AACA,MAAI,OAAO;AACX,MAAI,CAAC,KAAK,SAAS,IAAI,EAAG,SAAQ;AAClC,QAAM,QAAQ,CAAC,IAAI,gBAAgB,GAAG,KAAK,EAAE,KAAK,IAAI,IAAI;AAC1D,SAAO,EAAE,SAAS,OAAO,OAAO,MAAM;AACxC;AAYO,SAAS,qBACd,SACA,WACU;AACV,QAAM,aAAuB,CAAC;AAC9B,aAAW,OAAO,WAAW;AAC3B,UAAM,IAAI,IAAI,KAAK;AACnB,QAAI,CAAC,EAAG;AACR,QAAI,EAAE,WAAW,GAAG,EAAG;AACvB,QAAI,QAAQ,KAAK,CAAC,EAAG;AACrB,UAAM,SAAS,kBAAkB,CAAC;AAClC,QAAI,CAAC,OAAO,SAAS,GAAG,EAAG;AAC3B,UAAM,OAAO,mBAAmB,MAAM;AACtC,QAAI,CAAC,KAAM;AACX,eAAW,KAAK,IAAI;AAAA,EACtB;AACA,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,SAAS;AAC1B,eAAW,OAAO,YAAY;AAC5B,UAAI,SAAS,OAAO,KAAK,WAAW,MAAM,GAAG,GAAG;AAC9C,gBAAQ,KAAK,IAAI;AACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACvIA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAE1B,IAAM,OAAO,UAAU,QAAQ;AAE/B,eAAe,IAAI,KAAa,MAA6D;AAC3F,SAAO,KAAK,OAAO,MAAM,EAAE,KAAK,KAAK,QAAQ,IAAI,CAAC;AACpD;AAEO,SAAS,WAAW,QAAgB,OAAa,oBAAI,KAAK,GAAW;AAG1E,QAAM,UAAU,KAAK,YAAY,EAAE,QAAQ,UAAU,EAAE;AACvD,SAAO,SAAS,MAAM,IAAI,OAAO;AACnC;AAEA,eAAsB,cAAc,KAA8B;AAChE,QAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,aAAa,gBAAgB,MAAM,CAAC;AACvE,SAAO,OAAO,KAAK;AACrB;AAEA,eAAsB,mBAAmB,KAA+B;AACtE,QAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,UAAU,aAAa,CAAC;AAC3D,SAAO,OAAO,KAAK,EAAE,WAAW;AAClC;AAEA,eAAsB,aAAa,KAAa,MAA6B;AAC3E,QAAM,IAAI,KAAK,CAAC,YAAY,MAAM,IAAI,CAAC;AACzC;AAIA,eAAsB,eAAe,KAAa,MAA6B;AAC7E,QAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC;AACnC;AASA,eAAsB,oBAAoB,KAAa,MAA6B;AAClF,QAAM,IAAI,KAAK,CAAC,YAAY,MAAM,IAAI,CAAC;AACzC;AAQA,eAAsB,aAAa,KAAa,MAA6B;AAC3E,QAAM,IAAI,KAAK,CAAC,UAAU,MAAM,IAAI,CAAC;AACvC;AAEA,eAAsB,SAAS,KAA4B;AACzD,QAAM,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC;AAC9B;AAEA,eAAsB,iBAAiB,KAAgC;AACrE,QAAM,EAAE,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;AAC9C,SAAO,OACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/B;AAEA,eAAsB,gBAAgB,KAAa,OAAgC;AACjF,MAAI,MAAM,WAAW,EAAG;AACxB,QAAM,IAAI,KAAK,CAAC,MAAM,MAAM,YAAY,MAAM,GAAG,KAAK,CAAC;AACzD;AAMA,eAAsB,OAAO,KAAa,SAAyC;AACjF,QAAM,SAAS,GAAG;AAClB,QAAM,EAAE,QAAQ,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC,UAAU,aAAa,CAAC;AACnE,MAAI,OAAO,KAAK,EAAE,WAAW,EAAG,QAAO;AACvC,QAAM,IAAI,KAAK,CAAC,UAAU,MAAM,OAAO,CAAC;AACxC,QAAM,EAAE,QAAQ,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,CAAC;AAC5D,SAAO,IAAI,KAAK;AAClB;;;AC5EO,SAAS,UAAU,MAAoB;AAC5C,SAAO,KAAK,QAAQ,KAAK;AAC3B;;;AC+CA,eAAsB,WAAc,MAA4C;AAC9E,QAAM,QAAQ,UAAU,KAAK,IAAI;AAEjC,MAAI,KAAK,kBAAkB,CAAE,MAAM,mBAAmB,KAAK,KAAK,IAAI,GAAI;AACtE,UAAM,IAAI,MAAM,iDAAiD,KAAK,KAAK,IAAI,EAAE;AAAA,EACnF;AAEA,QAAM,UAAU,MAAM,KAAK,KAAK;AAEhC,MAAI,QAAQ,SAAS,QAAQ;AAC3B,WAAO;AAAA,MACL,QAAQ,KAAK;AAAA,MACb,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS,CAAC;AAAA,MACV,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,IAClD;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,UAAU;AAC7B,WAAO;AAAA,MACL,QAAQ,KAAK;AAAA,MACb,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS,CAAC;AAAA,MACV,OAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,kBAAkB,CAAE,MAAM,mBAAmB,KAAK,KAAK,IAAI,GAAI;AACvE,UAAM,IAAI,MAAM,iDAAiD,KAAK,KAAK,IAAI,EAAE;AAAA,EACnF;AAQA,MAAI,WAA0B;AAC9B,MAAI;AACF,eAAW,MAAM,cAAc,KAAK,KAAK,IAAI;AAAA,EAC/C,QAAQ;AACN,eAAW;AAAA,EACb;AAEA,QAAM,SAAS,WAAW,KAAK,IAAI;AACnC,QAAM,aAAa,KAAK,KAAK,MAAM,MAAM;AAmBzC,QAAM,kBAAkB,YAA2B;AACjD,QAAI,aAAa,QAAQ,aAAa,OAAQ;AAC9C,QAAI;AACF,YAAM,eAAe,KAAK,KAAK,MAAM,QAAQ;AAAA,IAC/C,SAAS,KAAK;AAEZ,cAAQ;AAAA,QACN,qCAAqC,QAAQ,UAAU,KAAK,IAAI,KAC9D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAWA,QAAM,sBAAsB,YAA2B;AACrD,QAAI,aAAa,QAAQ,aAAa,OAAQ;AAC9C,QAAI;AACF,YAAM,oBAAoB,KAAK,KAAK,MAAM,QAAQ;AAAA,IACpD,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,2CAA2C,QAAQ,iBAAiB,KAAK,IAAI,KAC3E,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,MACF;AAGA;AAAA,IACF;AACA,QAAI;AACF,YAAM,aAAa,KAAK,KAAK,MAAM,MAAM;AAAA,IAC3C,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,2CAA2C,MAAM,iBAAiB,KAAK,IAAI,KACzE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAiB,CAAC;AACxB,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,KAAK,MAAM,QAAQ,MAAM;AAAA,MACtC,KAAK,KAAK,KAAK;AAAA,MACf;AAAA,MACA,QAAQ,OAAO,QAAQ;AACrB,cAAM,MAAM,MAAM,OAAU,KAAK,KAAK,MAAM,GAAG;AAC/C,YAAI,IAAK,MAAK,KAAK,GAAG;AACtB,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AAIZ,UAAM,oBAAoB;AAC1B,UAAM;AAAA,EACR;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,oBAAoB;AAC1B,WAAO;AAAA,MACL,QAAQ,KAAK;AAAA,MACb,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,OAAO,OAAO;AAAA,IAChB;AAAA,EACF;AAKA,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,gBAAgB;AAAA,EACxB;AAEA,QAAM,QAAQ,OAAO,QAAQ,GAAG,OAAO,KAAK,aAAa,MAAM,KAAK,WAAW,MAAM;AACrF,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,MAAM;AAAA,IACN,QAAQ,KAAK,SAAS,IAAI,YAAY;AAAA,IACtC,SAAS;AAAA,IACT;AAAA,EACF;AACF;;;ALzMA,IAAM,mBAA+B;AACrC,IAAM,gBAA4B;AAClC,IAAM,iBAA6B;AAanC,SAAS,wBAAwB,UAA2B;AAC1D,SAAO,SAAS,SAAS,oBAAoB,KAAK,SAAS,SAAS,2BAA2B;AACjG;AAIA,IAAM,qBACJ;AAYF,SAAS,yBAAyB,UAA2B;AAC3D,SAAO,SAAS,SAAS,aAAa,KAAK,mBAAmB,KAAK,QAAQ;AAC7E;AAMO,IAAM,mBAAiC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,aAAa,OAAoC;AAC/D,SAAQ,iBAA8B,SAAS,KAAK;AACtD;AAEA,eAAe,UAAU,MAAsC;AAC7D,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,OAAO;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,kBACb,KACA,WAC2B;AAC3B,QAAM,QAA0B,CAAC;AACjC,aAAW,KAAK,WAAW;AACzB,UAAM,WAAW,MAAM,UAAU,KAAK,KAAK,EAAE,IAAI,CAAC;AAClD,QAAI,aAAa,EAAE,SAAU;AAI7B,QAAI,EAAE,WAAW,iBAAiB,aAAa,QAAQ,wBAAwB,QAAQ,GAAG;AACxF;AAAA,IACF;AAIA,QAAI,EAAE,WAAW,kBAAkB,aAAa,QAAQ,yBAAyB,QAAQ,GAAG;AAC1F;AAAA,IACF;AACA,UAAM,KAAK,CAAC;AAAA,EACd;AACA,SAAO;AACT;AAMA,eAAe,cAAc,KAAqC;AAChE,QAAM,WAAW,MAAM,UAAU,KAAK,KAAK,YAAY,CAAC;AACxD,QAAM,QAAQ,eAAe,UAAU,2BAA2B;AAClE,QAAM,UAAU,MAAM,iBAAiB,GAAG;AAC1C,QAAM,YAAY,qBAAqB,SAAS,2BAA2B;AAC3E,MAAI,MAAM,MAAM,WAAW,KAAK,UAAU,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAC9E,SAAO,EAAE,MAAM,SAAS,SAAS,MAAM,SAAS,WAAW,OAAO,MAAM,MAAM;AAChF;AAEA,eAAe,eACb,KACA,MACe;AACf,QAAM,UAAU,KAAK,KAAK,YAAY,GAAG,KAAK,SAAS,OAAO;AAC9D,MAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,UAAM,gBAAgB,KAAK,KAAK,SAAS;AAAA,EAC3C;AACF;AAEA,eAAsB,YACpB,MACA,OAA2B,CAAC,GACL;AACvB,QAAM,YAAY,KAAK,SAAS,cAAc,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,gBAAgB;AAC1F,QAAM,gBAAgB,UAAU,OAAO,CAAC,MAAuB,MAAM,gBAAgB;AACrF,QAAM,YAAY,gBAAgB,aAAa;AAC/C,QAAM,mBAAmB,UAAU,SAAS,gBAAgB;AAE5D,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN;AAAA,IACA,MAAM,YAAY;AAChB,YAAM,gBAAgB,MAAM,kBAAkB,KAAK,MAAM,SAAS;AAClE,YAAM,gBAA+B,mBACjC,MAAM,cAAc,KAAK,IAAI,IAC7B,EAAE,MAAM,OAAO;AACnB,UAAI,cAAc,WAAW,KAAK,cAAc,SAAS,QAAQ;AAC/D,eAAO,EAAE,MAAM,QAAQ,OAAO,qCAAqC;AAAA,MACrE;AACA,aAAO,EAAE,MAAM,SAAS,MAAM,EAAE,eAAe,cAAc,EAAE;AAAA,IACjE;AAAA,IACA,OAAO,OAAO,EAAE,eAAe,cAAc,GAAG,EAAE,QAAAA,QAAO,MAAM;AAC7D,iBAAW,KAAK,eAAe;AAC7B,cAAM,OAAO,KAAK,KAAK,MAAM,EAAE,IAAI;AACnC,cAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,cAAM,UAAU,MAAM,EAAE,UAAU,OAAO;AACzC,cAAMA,QAAO,eAAe,EAAE,MAAM,qCAAqC;AAAA,MAC3E;AACA,UAAI,cAAc,SAAS,SAAS;AAClC,cAAM,eAAe,KAAK,MAAM,aAAa;AAC7C,cAAMA,QAAO,mDAAmD;AAAA,MAClE;AACA,aAAO,EAAE,MAAM,KAAK;AAAA,IACtB;AAAA,EACF,CAAC;AACH;","names":["commit"]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}