@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
@@ -0,0 +1,368 @@
1
+ // src/recipes/svelte-5/step-gotchas.ts
2
+ import { readFile, writeFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { glob } from "tinyglobby";
5
+
6
+ // src/recipes/svelte-5/codemods/on-event-to-handler.ts
7
+ var SCRIPT_BLOCK = /<script\b[^>]*>[\s\S]*?<\/script>/g;
8
+ var SIMPLE_ON_EVENT = /\bon:([a-z]+)(?=\s*=)/g;
9
+ var MODIFIER_EVENT = /\bon:[a-z]+\|[a-zA-Z]+(?=\s*=)/g;
10
+ function flagEventModifiers(source) {
11
+ const insertions = [];
12
+ let m;
13
+ MODIFIER_EVENT.lastIndex = 0;
14
+ while ((m = MODIFIER_EVENT.exec(source)) !== null) {
15
+ const tagStart = source.lastIndexOf("<", m.index);
16
+ if (tagStart === -1) continue;
17
+ const prevLineEnd = tagStart - 1;
18
+ if (prevLineEnd >= 0) {
19
+ const prevLineStart = source.lastIndexOf("\n", prevLineEnd - 1) + 1;
20
+ const prevLine = source.slice(prevLineStart, prevLineEnd + 1);
21
+ if (/<!--\s*@migration-task/.test(prevLine)) continue;
22
+ }
23
+ const lineStart = source.lastIndexOf("\n", tagStart - 1) + 1;
24
+ const indent = source.slice(lineStart, tagStart);
25
+ const safeIndent = /^[ \t]*$/.test(indent) ? indent : "";
26
+ insertions.push({ tagStart, indent: safeIndent, modifier: m[0] });
27
+ }
28
+ let out = source;
29
+ for (let i = insertions.length - 1; i >= 0; i--) {
30
+ const { tagStart, indent, modifier } = insertions[i];
31
+ const comment = `<!-- @migration-task: Svelte 5 removed event modifier syntax (\`${modifier}\`). Rewrite inline, e.g. onclick={(e) => { e.preventDefault(); ... }}. -->
32
+ ${indent}`;
33
+ out = out.slice(0, tagStart) + comment + out.slice(tagStart);
34
+ }
35
+ return out;
36
+ }
37
+ function onEventToHandler(source) {
38
+ const masked = [];
39
+ const placeholder = (i) => ` SCRIPT_${i} `;
40
+ const intermediate = source.replace(SCRIPT_BLOCK, (match) => {
41
+ masked.push(match);
42
+ return placeholder(masked.length - 1);
43
+ });
44
+ let processed = intermediate.replace(SIMPLE_ON_EVENT, (_full, name) => `on${name}`);
45
+ processed = flagEventModifiers(processed);
46
+ let out = processed;
47
+ masked.forEach((blk, i) => {
48
+ out = out.replace(placeholder(i), blk);
49
+ });
50
+ return out;
51
+ }
52
+
53
+ // src/recipes/svelte-5/codemods/dollar-props.ts
54
+ var SCRIPT_BLOCK2 = /<script\b([^>]*)>([\s\S]*?)<\/script>/;
55
+ var EXPORT_LET = /^\s*export\s+let\s+(\w+)\s*(?::\s*([^=;\n]+))?\s*(?:=\s*([^;\n]+))?;?\s*$/gm;
56
+ function transformScript(scriptBody, isTs) {
57
+ const props = [];
58
+ const cleaned = scriptBody.replace(
59
+ EXPORT_LET,
60
+ (_full, name, type, defaultExpr) => {
61
+ props.push({
62
+ name,
63
+ type: type?.trim(),
64
+ defaultExpr: defaultExpr?.trim()
65
+ });
66
+ return "";
67
+ }
68
+ );
69
+ if (props.length === 0) return { body: scriptBody, changed: false };
70
+ const destructured = props.map((p) => p.defaultExpr ? `${p.name} = ${p.defaultExpr}` : p.name).join(", ");
71
+ let decl;
72
+ if (isTs) {
73
+ const typeSig = props.map((p) => {
74
+ const optional = p.defaultExpr ? "?" : "";
75
+ return `${p.name}${optional}: ${p.type ?? "unknown"}`;
76
+ }).join("; ");
77
+ decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;
78
+ } else {
79
+ decl = ` let { ${destructured} } = $props();`;
80
+ }
81
+ const next = cleaned.replace(/^(\s*)/, (m) => `${m}${decl}
82
+ `);
83
+ return { body: next, changed: true };
84
+ }
85
+ function exportLetToProps(source) {
86
+ const match = source.match(SCRIPT_BLOCK2);
87
+ if (!match) return source;
88
+ const attrs = match[1] ?? "";
89
+ const inner = match[2] ?? "";
90
+ const isTs = /\blang=["']ts["']/.test(attrs);
91
+ const { body, changed } = transformScript(inner, isTs);
92
+ if (!changed) return source;
93
+ return source.replace(SCRIPT_BLOCK2, (full) => full.replace(inner, body));
94
+ }
95
+
96
+ // src/util/svelte-source.ts
97
+ function findStringEnd(source, openIdx) {
98
+ const quote = source[openIdx];
99
+ let i = openIdx + 1;
100
+ while (i < source.length) {
101
+ const ch = source[i];
102
+ if (ch === "\\") {
103
+ i += 2;
104
+ continue;
105
+ }
106
+ if (ch === quote) return i;
107
+ i++;
108
+ }
109
+ return -1;
110
+ }
111
+
112
+ // src/recipes/svelte-5/codemods/dollar-restprops.ts
113
+ function removeInterfaceBlock(source) {
114
+ const re = /^\s*interface\s+\$\$Props\s*\{/m;
115
+ let out = source;
116
+ while (true) {
117
+ const match = re.exec(out);
118
+ if (!match) return out;
119
+ const openBraceIdx = match.index + match[0].length - 1;
120
+ let depth = 1;
121
+ let i = openBraceIdx + 1;
122
+ while (i < out.length && depth > 0) {
123
+ const ch = out[i];
124
+ if (ch === "{") depth++;
125
+ else if (ch === "}") depth--;
126
+ i++;
127
+ }
128
+ if (depth !== 0) return out;
129
+ let endIdx = i;
130
+ while (endIdx < out.length && /[ \t]/.test(out[endIdx] ?? "")) endIdx++;
131
+ if (out[endIdx] === "\n") endIdx++;
132
+ out = out.slice(0, match.index) + out.slice(endIdx);
133
+ }
134
+ }
135
+ function maskStringLiterals(source) {
136
+ const strings = [];
137
+ let out = "";
138
+ let i = 0;
139
+ while (i < source.length) {
140
+ const ch = source[i];
141
+ if (ch === '"' || ch === "'" || ch === "`") {
142
+ const closeIdx = findStringEnd(source, i);
143
+ if (closeIdx === -1) {
144
+ out += source.slice(i);
145
+ break;
146
+ }
147
+ const literal = source.slice(i, closeIdx + 1);
148
+ out += `__RDMNT_STR_${strings.length}__`;
149
+ strings.push(literal);
150
+ i = closeIdx + 1;
151
+ } else {
152
+ out += ch;
153
+ i++;
154
+ }
155
+ }
156
+ return {
157
+ masked: out,
158
+ restore: (s) => s.replace(/__RDMNT_STR_(\d+)__/g, (_full, idx) => strings[Number(idx)] ?? "")
159
+ };
160
+ }
161
+ var PROPS_DECL = /let\s*\{([^}]*)\}\s*(?::\s*\{([^}]*)\})?\s*=\s*\$props\(\)\s*;?/;
162
+ function injectRestIntoProps(scriptBody) {
163
+ const match = scriptBody.match(PROPS_DECL);
164
+ if (!match) return scriptBody;
165
+ const destructured = match[1] ?? "";
166
+ if (/\.\.\.\s*\w+/.test(destructured)) return scriptBody;
167
+ const trimmed = destructured.trim().replace(/,\s*$/, "");
168
+ const newDestructured = trimmed === "" ? " ...rest " : ` ${trimmed}, ...rest `;
169
+ let replacement;
170
+ if (match[2] !== void 0) {
171
+ const typeBody = match[2];
172
+ const hasIndexSig = /\[\s*key\s*:\s*string\s*\]\s*:/.test(typeBody);
173
+ const newTypeBody = hasIndexSig ? typeBody : `${typeBody.trimEnd().replace(/;?\s*$/, "")}; [key: string]: unknown `;
174
+ replacement = `let {${newDestructured}}: {${newTypeBody}} = $props();`;
175
+ } else {
176
+ replacement = `let {${newDestructured}} = $props();`;
177
+ }
178
+ return scriptBody.replace(PROPS_DECL, replacement);
179
+ }
180
+ var SCRIPT_BLOCK3 = /<script\b([^>]*)>([\s\S]*?)<\/script>/;
181
+ var HAS_PROPS_CALL = /\$props\(\s*\)/;
182
+ function removeDollarRestProps(source) {
183
+ const next = removeInterfaceBlock(source);
184
+ const scriptMatch = next.match(SCRIPT_BLOCK3);
185
+ if (!scriptMatch) return next;
186
+ if (!HAS_PROPS_CALL.test(scriptMatch[2] ?? "")) {
187
+ return next;
188
+ }
189
+ const scriptInner = scriptMatch[2] ?? "";
190
+ const { masked, restore } = maskStringLiterals(scriptInner);
191
+ let processed = injectRestIntoProps(masked);
192
+ processed = processed.replace(/\$\$restProps/g, "rest");
193
+ const restoredInner = restore(processed);
194
+ const newScriptBlock = scriptMatch[0].replace(scriptInner, () => restoredInner);
195
+ const before = next.slice(0, scriptMatch.index);
196
+ const after = next.slice(scriptMatch.index + scriptMatch[0].length);
197
+ return before.replace(/\$\$restProps/g, "rest") + newScriptBlock + after.replace(/\$\$restProps/g, "rest");
198
+ }
199
+
200
+ // src/recipes/svelte-5/codemods/state-effect-sync.ts
201
+ var PATTERN = /let\s+(\w+)\s*=\s*\$state\(\s*([^)]+?)\s*\)\s*;[ \t\r\n]*\$effect\(\s*\(\s*\)\s*=>\s*\{\s*\w+\s*;\s*\1\s*=\s*([^;}]+?)\s*;?\s*\}\s*\)\s*;?/g;
202
+ function stateEffectSyncToDerived(source) {
203
+ return source.replace(PATTERN, (full, name, initExpr, effectExpr) => {
204
+ if (initExpr.trim() !== effectExpr.trim()) return full;
205
+ return `let ${name} = $derived(${initExpr.trim()});`;
206
+ });
207
+ }
208
+
209
+ // src/recipes/svelte-5/codemods/dollar-props-class.ts
210
+ var PROPS_DESTRUCTURE = /let\s*\{([\s\S]*?)\}(\s*:\s*\{([\s\S]*?)\})?\s*=\s*\$props\(\)/;
211
+ var HAS_DOLLAR_PROPS_CLASS = /\$\$props\.class\b/;
212
+ var DOLLAR_PROPS_CLASS_GLOBAL = /\$\$props\.class\b/g;
213
+ var DOLLAR_PROPS_ANY = /\$\$props\b/;
214
+ var SCRIPT_BLOCK4 = /<script\b[^>]*>[\s\S]*?<\/script>/g;
215
+ var MIGRATION_TASK = /^<!--\s*@migration-task[\s\S]*?-->\s*\n?/gm;
216
+ var IDENT = "className";
217
+ function maskScripts(source) {
218
+ const blocks = [];
219
+ const masked = source.replace(SCRIPT_BLOCK4, (m) => {
220
+ blocks.push(m);
221
+ return `__SCRIPT_${blocks.length - 1}__`;
222
+ });
223
+ return { masked, blocks };
224
+ }
225
+ function restoreScripts(masked, blocks) {
226
+ let out = masked;
227
+ blocks.forEach((blk, i) => {
228
+ out = out.replace(`__SCRIPT_${i}__`, blk);
229
+ });
230
+ return out;
231
+ }
232
+ function dollarPropsClass(source) {
233
+ const { masked } = maskScripts(source);
234
+ if (!HAS_DOLLAR_PROPS_CLASS.test(masked)) return source;
235
+ if (!PROPS_DESTRUCTURE.test(source)) return source;
236
+ let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {
237
+ if (/\bclass\s*:/.test(body)) return full;
238
+ const cleanBody = body.trim().replace(/,\s*$/, "").trim();
239
+ const newBody = cleanBody ? `${cleanBody}, class: ${IDENT} = ""` : `class: ${IDENT} = ""`;
240
+ if (typeAnno) {
241
+ const cleanType = (typeBody ?? "").trim().replace(/;\s*$/, "").trim();
242
+ const newType = cleanType ? `${cleanType}; class?: string` : `class?: string`;
243
+ return `let { ${newBody} }: { ${newType} } = $props()`;
244
+ }
245
+ return `let { ${newBody} } = $props()`;
246
+ });
247
+ const reMasked = maskScripts(updated);
248
+ const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS_GLOBAL, IDENT);
249
+ updated = restoreScripts(templateRewritten, reMasked.blocks);
250
+ const stripped = updated.replace(MIGRATION_TASK, "");
251
+ if (!DOLLAR_PROPS_ANY.test(stripped)) {
252
+ updated = stripped;
253
+ }
254
+ return updated;
255
+ }
256
+
257
+ // src/recipes/svelte-5/codemods/legacy-reactive.ts
258
+ var SCRIPT_BLOCK5 = /<script\b([^>]*)>([\s\S]*?)<\/script>/g;
259
+ var SIMPLE_REACTIVE = /^([ \t]*)\$:\s*(\w+)\s*=\s*([^;\n]+);?[ \t]*$/gm;
260
+ var BLOCK_REACTIVE_HEAD = /(^|\n)([ \t]*)\$:\s*\{/g;
261
+ function findMatchingClose(source, openIdx) {
262
+ let depth = 0;
263
+ let i = openIdx;
264
+ while (i < source.length) {
265
+ const ch = source[i];
266
+ if (ch === '"' || ch === "'" || ch === "`") {
267
+ const closeStr = findStringEnd(source, i);
268
+ if (closeStr === -1) return -1;
269
+ i = closeStr + 1;
270
+ continue;
271
+ }
272
+ if (ch === "/") {
273
+ const next = source[i + 1];
274
+ if (next === "/") {
275
+ const eol = source.indexOf("\n", i + 2);
276
+ i = eol === -1 ? source.length : eol;
277
+ continue;
278
+ }
279
+ if (next === "*") {
280
+ const end = source.indexOf("*/", i + 2);
281
+ if (end === -1) return -1;
282
+ i = end + 2;
283
+ continue;
284
+ }
285
+ }
286
+ if (ch === "{") depth++;
287
+ else if (ch === "}") {
288
+ depth--;
289
+ if (depth === 0) return i;
290
+ }
291
+ i++;
292
+ }
293
+ return -1;
294
+ }
295
+ var MIGRATION_MARKER = "// @migration-task: $effect won't trigger UI updates on plain `let` bindings \u2014 refine mutated locals to $state or split into per-variable $derived.";
296
+ function transformBlocks(body) {
297
+ const out = [];
298
+ let last = 0;
299
+ BLOCK_REACTIVE_HEAD.lastIndex = 0;
300
+ let m;
301
+ while ((m = BLOCK_REACTIVE_HEAD.exec(body)) !== null) {
302
+ const leadingNewline = m[1] ?? "";
303
+ const indent = m[2] ?? "";
304
+ const headEnd = m.index + m[0].length;
305
+ const openBraceIdx = headEnd - 1;
306
+ const closeBraceIdx = findMatchingClose(body, openBraceIdx);
307
+ if (closeBraceIdx === -1) continue;
308
+ out.push(body.slice(last, m.index));
309
+ out.push(leadingNewline);
310
+ const blockBody = body.slice(openBraceIdx + 1, closeBraceIdx);
311
+ out.push(`${indent}${MIGRATION_MARKER}
312
+ `);
313
+ out.push(`${indent}$effect(() => {${blockBody}});`);
314
+ last = closeBraceIdx + 1;
315
+ BLOCK_REACTIVE_HEAD.lastIndex = last;
316
+ }
317
+ out.push(body.slice(last));
318
+ return out.join("");
319
+ }
320
+ function transformSimple(body) {
321
+ return body.replace(SIMPLE_REACTIVE, (_full, indent, name, expr) => {
322
+ return `${indent}let ${name} = $derived(${expr.trim()});`;
323
+ });
324
+ }
325
+ function legacyReactiveToRunes(source) {
326
+ return source.replace(SCRIPT_BLOCK5, (full, _attrs, body) => {
327
+ let next = transformBlocks(body);
328
+ next = transformSimple(next);
329
+ if (next === body) return full;
330
+ return full.replace(body, next);
331
+ });
332
+ }
333
+
334
+ // src/recipes/svelte-5/step-gotchas.ts
335
+ var SVELTE_GLOBS = ["src/**/*.svelte"];
336
+ var IGNORE = ["node_modules/**", ".svelte-kit/**", "build/**"];
337
+ var CODEMODS = [
338
+ onEventToHandler,
339
+ exportLetToProps,
340
+ removeDollarRestProps,
341
+ stateEffectSyncToDerived,
342
+ dollarPropsClass,
343
+ legacyReactiveToRunes
344
+ ];
345
+ async function planGotchaCodemods(cwd) {
346
+ const changes = [];
347
+ const relPaths = await glob(SVELTE_GLOBS, { cwd, ignore: IGNORE, absolute: false });
348
+ for (const rel of relPaths) {
349
+ const path = join(cwd, rel);
350
+ const before = await readFile(path, "utf-8");
351
+ const after = CODEMODS.reduce((s, fn) => fn(s), before);
352
+ if (after !== before) changes.push({ rel, after });
353
+ }
354
+ return changes;
355
+ }
356
+ async function applyGotchaCodemods(cwd) {
357
+ const changes = await planGotchaCodemods(cwd);
358
+ for (const c of changes) {
359
+ await writeFile(join(cwd, c.rel), c.after, "utf-8");
360
+ }
361
+ return { filesChanged: changes.length };
362
+ }
363
+
364
+ export {
365
+ planGotchaCodemods,
366
+ applyGotchaCodemods
367
+ };
368
+ //# sourceMappingURL=chunk-OQPMZ6BM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recipes/svelte-5/step-gotchas.ts","../src/recipes/svelte-5/codemods/on-event-to-handler.ts","../src/recipes/svelte-5/codemods/dollar-props.ts","../src/util/svelte-source.ts","../src/recipes/svelte-5/codemods/dollar-restprops.ts","../src/recipes/svelte-5/codemods/state-effect-sync.ts","../src/recipes/svelte-5/codemods/dollar-props-class.ts","../src/recipes/svelte-5/codemods/legacy-reactive.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport { onEventToHandler } from \"./codemods/on-event-to-handler.js\";\nimport { exportLetToProps } from \"./codemods/dollar-props.js\";\nimport { removeDollarRestProps } from \"./codemods/dollar-restprops.js\";\nimport { stateEffectSyncToDerived } from \"./codemods/state-effect-sync.js\";\nimport { dollarPropsClass } from \"./codemods/dollar-props-class.js\";\nimport { legacyReactiveToRunes } from \"./codemods/legacy-reactive.js\";\n\nconst SVELTE_GLOBS = [\"src/**/*.svelte\"];\nconst IGNORE = [\"node_modules/**\", \".svelte-kit/**\", \"build/**\"];\n\ntype Codemod = (src: string) => string;\n\n// Order matters: exportLetToProps creates the $props() destructuring that\n// dollarPropsClass extends with a `class:` named prop.\nconst CODEMODS: Codemod[] = [\n onEventToHandler,\n exportLetToProps,\n removeDollarRestProps,\n stateEffectSyncToDerived,\n dollarPropsClass,\n legacyReactiveToRunes,\n];\n\nexport type CodemodChange = { rel: string; after: string };\n\nexport async function planGotchaCodemods(cwd: string): Promise<CodemodChange[]> {\n const changes: CodemodChange[] = [];\n const relPaths = await glob(SVELTE_GLOBS, { cwd, ignore: IGNORE, absolute: false });\n for (const rel of relPaths) {\n const path = join(cwd, rel);\n const before = await readFile(path, \"utf-8\");\n const after = CODEMODS.reduce((s, fn) => fn(s), before);\n if (after !== before) changes.push({ rel, after });\n }\n return changes;\n}\n\nexport async function applyGotchaCodemods(cwd: string): Promise<{ filesChanged: number }> {\n const changes = await planGotchaCodemods(cwd);\n for (const c of changes) {\n await writeFile(join(cwd, c.rel), c.after, \"utf-8\");\n }\n return { filesChanged: changes.length };\n}\n","const SCRIPT_BLOCK = /<script\\b[^>]*>[\\s\\S]*?<\\/script>/g;\nconst SIMPLE_ON_EVENT = /\\bon:([a-z]+)(?=\\s*=)/g;\nconst MODIFIER_EVENT = /\\bon:[a-z]+\\|[a-zA-Z]+(?=\\s*=)/g;\n\n/** Svelte 5 removed event modifier syntax (`on:click|preventDefault={fn}`).\n * The rewrite is non-trivial — the modifier behavior must be inlined into\n * the handler body — so this codemod doesn't attempt it automatically.\n * Instead it inserts a `@migration-task` marker immediately above each\n * offending element so the user gets a visible audit trail rather than\n * a silent build error from the Svelte 5 compiler. */\nfunction flagEventModifiers(source: string): string {\n const insertions: Array<{ tagStart: number; indent: string; modifier: string }> = [];\n let m: RegExpExecArray | null;\n MODIFIER_EVENT.lastIndex = 0;\n while ((m = MODIFIER_EVENT.exec(source)) !== null) {\n const tagStart = source.lastIndexOf(\"<\", m.index);\n if (tagStart === -1) continue;\n\n // Idempotency: if the line immediately above the tag already carries an\n // @migration-task marker for this site, don't double-insert on re-run.\n const prevLineEnd = tagStart - 1;\n if (prevLineEnd >= 0) {\n const prevLineStart = source.lastIndexOf(\"\\n\", prevLineEnd - 1) + 1;\n const prevLine = source.slice(prevLineStart, prevLineEnd + 1);\n if (/<!--\\s*@migration-task/.test(prevLine)) continue;\n }\n\n const lineStart = source.lastIndexOf(\"\\n\", tagStart - 1) + 1;\n const indent = source.slice(lineStart, tagStart);\n const safeIndent = /^[ \\t]*$/.test(indent) ? indent : \"\";\n insertions.push({ tagStart, indent: safeIndent, modifier: m[0] });\n }\n\n // Apply back-to-front so earlier insertion offsets stay valid.\n let out = source;\n for (let i = insertions.length - 1; i >= 0; i--) {\n const { tagStart, indent, modifier } = insertions[i]!;\n const comment = `<!-- @migration-task: Svelte 5 removed event modifier syntax (\\`${modifier}\\`). Rewrite inline, e.g. onclick={(e) => { e.preventDefault(); ... }}. -->\\n${indent}`;\n out = out.slice(0, tagStart) + comment + out.slice(tagStart);\n }\n return out;\n}\n\nexport function onEventToHandler(source: string): string {\n const masked: string[] = [];\n const placeholder = (i: number): string => ` SCRIPT_${i} `;\n const intermediate = source.replace(SCRIPT_BLOCK, (match) => {\n masked.push(match);\n return placeholder(masked.length - 1);\n });\n\n let processed = intermediate.replace(SIMPLE_ON_EVENT, (_full, name: string) => `on${name}`);\n processed = flagEventModifiers(processed);\n\n let out = processed;\n masked.forEach((blk, i) => {\n out = out.replace(placeholder(i), blk);\n });\n\n return out;\n}\n","const SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/;\nconst EXPORT_LET = /^\\s*export\\s+let\\s+(\\w+)\\s*(?::\\s*([^=;\\n]+))?\\s*(?:=\\s*([^;\\n]+))?;?\\s*$/gm;\n\ntype Prop = { name: string; type?: string | undefined; defaultExpr?: string | undefined };\n\nfunction transformScript(scriptBody: string, isTs: boolean): { body: string; changed: boolean } {\n const props: Prop[] = [];\n const cleaned = scriptBody.replace(\n EXPORT_LET,\n (_full, name: string, type?: string, defaultExpr?: string) => {\n props.push({\n name,\n type: type?.trim(),\n defaultExpr: defaultExpr?.trim(),\n });\n return \"\";\n },\n );\n if (props.length === 0) return { body: scriptBody, changed: false };\n\n const destructured = props\n .map((p) => (p.defaultExpr ? `${p.name} = ${p.defaultExpr}` : p.name))\n .join(\", \");\n\n let decl: string;\n if (isTs) {\n const typeSig = props\n .map((p) => {\n const optional = p.defaultExpr ? \"?\" : \"\";\n return `${p.name}${optional}: ${p.type ?? \"unknown\"}`;\n })\n .join(\"; \");\n decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;\n } else {\n decl = ` let { ${destructured} } = $props();`;\n }\n\n const next = cleaned.replace(/^(\\s*)/, (m) => `${m}${decl}\\n`);\n return { body: next, changed: true };\n}\n\nexport function exportLetToProps(source: string): string {\n const match = source.match(SCRIPT_BLOCK);\n if (!match) return source;\n const attrs = match[1] ?? \"\";\n const inner = match[2] ?? \"\";\n const isTs = /\\blang=[\"']ts[\"']/.test(attrs);\n const { body, changed } = transformScript(inner, isTs);\n if (!changed) return source;\n return source.replace(SCRIPT_BLOCK, (full) => full.replace(inner, body));\n}\n","/** Find the index of the closing quote for a string literal that opens at\n * `openIdx`. Handles backslash escapes. Returns -1 if the string is\n * unterminated.\n *\n * Treats backtick template literals the same as `'…'` / `\"…\"` — the\n * closing backtick terminates. Callers needing precise `${…}` interpolation\n * handling will need a real parser; this helper is intentionally simple\n * and good enough for the codemod-grade string masking we do today. */\nexport function findStringEnd(source: string, openIdx: number): number {\n const quote = source[openIdx];\n let i = openIdx + 1;\n while (i < source.length) {\n const ch = source[i];\n if (ch === \"\\\\\") {\n i += 2;\n continue;\n }\n if (ch === quote) return i;\n i++;\n }\n return -1;\n}\n","/** Locate `interface $$Props {` declarations and remove them, including\n * the matching closing `}` even if the body has nested braces or spans\n * multiple lines. Regex alone can't do balanced-brace matching, so we\n * walk the string manually. */\nfunction removeInterfaceBlock(source: string): string {\n const re = /^\\s*interface\\s+\\$\\$Props\\s*\\{/m;\n let out = source;\n while (true) {\n const match = re.exec(out);\n if (!match) return out;\n\n const openBraceIdx = match.index + match[0].length - 1;\n let depth = 1;\n let i = openBraceIdx + 1;\n while (i < out.length && depth > 0) {\n const ch = out[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") depth--;\n i++;\n }\n if (depth !== 0) return out; // unbalanced; bail rather than corrupt\n\n // Consume trailing whitespace through end-of-line.\n let endIdx = i;\n while (endIdx < out.length && /[ \\t]/.test(out[endIdx] ?? \"\")) endIdx++;\n if (out[endIdx] === \"\\n\") endIdx++;\n\n out = out.slice(0, match.index) + out.slice(endIdx);\n }\n}\n\nimport { findStringEnd } from \"../../../util/svelte-source.js\";\n\n/** Mask every `'…'`, `\"…\"`, and template literal in `source` with a placeholder\n * so subsequent regex passes can rewrite identifiers without corrupting string\n * contents. Returns the masked body and a function to restore originals. */\nfunction maskStringLiterals(source: string): {\n masked: string;\n restore: (s: string) => string;\n} {\n const strings: string[] = [];\n let out = \"\";\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n const closeIdx = findStringEnd(source, i);\n if (closeIdx === -1) {\n out += source.slice(i);\n break;\n }\n const literal = source.slice(i, closeIdx + 1);\n out += `__RDMNT_STR_${strings.length}__`;\n strings.push(literal);\n i = closeIdx + 1;\n } else {\n out += ch;\n i++;\n }\n }\n return {\n masked: out,\n restore: (s) => s.replace(/__RDMNT_STR_(\\d+)__/g, (_full, idx) => strings[Number(idx)] ?? \"\"),\n };\n}\n\nconst PROPS_DECL = /let\\s*\\{([^}]*)\\}\\s*(?::\\s*\\{([^}]*)\\})?\\s*=\\s*\\$props\\(\\)\\s*;?/;\n\n/** If the script declares `let { … } = $props();` (with or without an inline\n * type annotation) and doesn't already collect `...rest`, inject it. For TS,\n * widen the inline type with an `[key: string]: unknown` index signature so\n * the rest binding actually captures excess attributes (without the widening,\n * TS infers `rest` as `{}` and the spread forwards nothing). */\nfunction injectRestIntoProps(scriptBody: string): string {\n const match = scriptBody.match(PROPS_DECL);\n if (!match) return scriptBody;\n const destructured = match[1] ?? \"\";\n if (/\\.\\.\\.\\s*\\w+/.test(destructured)) return scriptBody; // already has rest\n\n // Strip any trailing comma left over from a multi-line destructuring shape\n // (e.g. `{ foo, bar, }`). Without this, the template literal below emits\n // `bar,, ...rest` — invalid syntax that the codemod was happily committing\n // (regression: caltex's Accordian.svelte, 2026-05-27).\n const trimmed = destructured.trim().replace(/,\\s*$/, \"\");\n const newDestructured = trimmed === \"\" ? \" ...rest \" : ` ${trimmed}, ...rest `;\n\n let replacement: string;\n if (match[2] !== undefined) {\n const typeBody = match[2];\n const hasIndexSig = /\\[\\s*key\\s*:\\s*string\\s*\\]\\s*:/.test(typeBody);\n const newTypeBody = hasIndexSig\n ? typeBody\n : `${typeBody.trimEnd().replace(/;?\\s*$/, \"\")}; [key: string]: unknown `;\n replacement = `let {${newDestructured}}: {${newTypeBody}} = $props();`;\n } else {\n replacement = `let {${newDestructured}} = $props();`;\n }\n return scriptBody.replace(PROPS_DECL, replacement);\n}\n\nconst SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/;\nconst HAS_PROPS_CALL = /\\$props\\(\\s*\\)/;\n\nexport function removeDollarRestProps(source: string): string {\n const next = removeInterfaceBlock(source);\n\n const scriptMatch = next.match(SCRIPT_BLOCK);\n if (!scriptMatch) return next;\n if (!HAS_PROPS_CALL.test(scriptMatch[2] ?? \"\")) {\n // No $props() in this script — refuse to rewrite $$restProps anywhere, since\n // doing so would emit references to an undeclared identifier. The user sees\n // the original $$restProps and a clear Svelte 5 build error to migrate by hand.\n return next;\n }\n\n const scriptInner = scriptMatch[2] ?? \"\";\n const { masked, restore } = maskStringLiterals(scriptInner);\n let processed = injectRestIntoProps(masked);\n processed = processed.replace(/\\$\\$restProps/g, \"rest\");\n const restoredInner = restore(processed);\n\n // Use a function callback so `$$` in the restored script body isn't\n // interpreted as the `$` substitution pattern by String.prototype.replace.\n const newScriptBlock = scriptMatch[0].replace(scriptInner, () => restoredInner);\n const before = next.slice(0, scriptMatch.index!);\n const after = next.slice(scriptMatch.index! + scriptMatch[0].length);\n\n // Template (outside script) gets a plain swap. Template attribute strings\n // containing the literal text \"$$restProps\" are vanishingly rare in practice;\n // accept the limitation rather than parse the whole template.\n return (\n before.replace(/\\$\\$restProps/g, \"rest\") +\n newScriptBlock +\n after.replace(/\\$\\$restProps/g, \"rest\")\n );\n}\n","/**\n * Collapses the \"manual sync state with prop\" anti-pattern into `$derived`.\n *\n * Input:\n * let content = $state(data.page.data);\n * $effect(() => { data; content = data.page.data });\n *\n * Output:\n * let content = $derived(data.page.data);\n *\n * Only transforms when the `$state(...)` initializer expression matches the\n * effect's right-hand assignment exactly (after trim). Intervening statements\n * between the `let` and the `$effect` block prevent the match — keeps the\n * codemod conservative.\n *\n * Triggered by Svelte 5's `state_referenced_locally` warning, which fires\n * whenever a local `let X = $state(prop.expr)` captures a prop reference\n * only at init time.\n */\n// `;?` before the closing `}` so the multi-line effect form matches:\n// $effect(() => {\n// data;\n// content = data.page.data;\n// });\n// as well as the single-line form: $effect(() => { data; content = data.page.data })\nconst PATTERN =\n /let\\s+(\\w+)\\s*=\\s*\\$state\\(\\s*([^)]+?)\\s*\\)\\s*;[ \\t\\r\\n]*\\$effect\\(\\s*\\(\\s*\\)\\s*=>\\s*\\{\\s*\\w+\\s*;\\s*\\1\\s*=\\s*([^;}]+?)\\s*;?\\s*\\}\\s*\\)\\s*;?/g;\n\nexport function stateEffectSyncToDerived(source: string): string {\n return source.replace(PATTERN, (full, name: string, initExpr: string, effectExpr: string) => {\n if (initExpr.trim() !== effectExpr.trim()) return full;\n return `let ${name} = $derived(${initExpr.trim()});`;\n });\n}\n","/**\n * Converts the legacy `$$props.class` pattern (passing extra HTML class from\n * a parent component) to a Svelte 5 named-prop destructuring.\n *\n * Input:\n * <script lang=\"ts\">\n * let { foo }: { foo?: string } = $props();\n * </script>\n * <div class=\"other {$$props.class || ''}\">x</div>\n *\n * Output:\n * <script lang=\"ts\">\n * let { foo, class: className = \"\" }: { foo?: string; class?: string } = $props();\n * </script>\n * <div class=\"other {className || ''}\">x</div>\n *\n * Triggered by Svelte 5 build errors:\n * \"Cannot use `$$props` in runes mode\" (svelte.dev/e/legacy_props_invalid)\n *\n * The original svelte-migrate tool flagged this with a `@migration-task`\n * comment because it couldn't safely combine `$$props` with already-named\n * props. We can: `class` is the dominant case across the reddoor fleet,\n * so we destructure it as `class: className = \"\"` (renamed because `class`\n * is a JS reserved word as a bare binding) and rewrite template references.\n *\n * Conservative: only transforms files that have BOTH a template\n * `$$props.class` reference AND an existing `$props()` destructuring.\n * Files using `$$props.class` without a `$props()` declaration are left\n * for the `exportLetToProps` codemod to handle in a prior pass.\n */\n// Note: lazy `[\\s\\S]*?` (not `[^}]*`) so default values containing braces\n// — `() => {}`, `{ foo: 1 }`, etc. — don't truncate the match early.\nconst PROPS_DESTRUCTURE = /let\\s*\\{([\\s\\S]*?)\\}(\\s*:\\s*\\{([\\s\\S]*?)\\})?\\s*=\\s*\\$props\\(\\)/;\n// Two regexes: a stateless one for \"does this string contain $$props.class?\"\n// existence checks, and the /g one for the iterating template rewrite. Mixing\n// .test() and .replace() on the same /g regex makes lastIndex management\n// fragile — easy to forget the reset on a future edit.\nconst HAS_DOLLAR_PROPS_CLASS = /\\$\\$props\\.class\\b/;\nconst DOLLAR_PROPS_CLASS_GLOBAL = /\\$\\$props\\.class\\b/g;\nconst DOLLAR_PROPS_ANY = /\\$\\$props\\b/;\nconst SCRIPT_BLOCK = /<script\\b[^>]*>[\\s\\S]*?<\\/script>/g;\nconst MIGRATION_TASK = /^<!--\\s*@migration-task[\\s\\S]*?-->\\s*\\n?/gm;\nconst IDENT = \"className\";\n\nfunction maskScripts(source: string): { masked: string; blocks: string[] } {\n const blocks: string[] = [];\n const masked = source.replace(SCRIPT_BLOCK, (m) => {\n blocks.push(m);\n return `__SCRIPT_${blocks.length - 1}__`;\n });\n return { masked, blocks };\n}\n\nfunction restoreScripts(masked: string, blocks: string[]): string {\n let out = masked;\n blocks.forEach((blk, i) => {\n out = out.replace(`__SCRIPT_${i}__`, blk);\n });\n return out;\n}\n\nexport function dollarPropsClass(source: string): string {\n // Bail early if the template doesn't reference $$props.class\n const { masked } = maskScripts(source);\n if (!HAS_DOLLAR_PROPS_CLASS.test(masked)) return source;\n\n // Bail if there's no $props() destructuring to extend\n if (!PROPS_DESTRUCTURE.test(source)) return source;\n\n let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {\n // Already migrated (someone added class manually)\n if (/\\bclass\\s*:/.test(body as string)) return full;\n\n const cleanBody = (body as string).trim().replace(/,\\s*$/, \"\").trim();\n const newBody = cleanBody ? `${cleanBody}, class: ${IDENT} = \"\"` : `class: ${IDENT} = \"\"`;\n\n if (typeAnno) {\n const cleanType = ((typeBody as string) ?? \"\").trim().replace(/;\\s*$/, \"\").trim();\n const newType = cleanType ? `${cleanType}; class?: string` : `class?: string`;\n return `let { ${newBody} }: { ${newType} } = $props()`;\n }\n return `let { ${newBody} } = $props()`;\n });\n\n // Replace $$props.class in template only (re-mask after destructuring update)\n const reMasked = maskScripts(updated);\n const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS_GLOBAL, IDENT);\n updated = restoreScripts(templateRewritten, reMasked.blocks);\n\n // Strip @migration-task comments if no $$props references remain anywhere\n // EXCEPT inside those very comments. Strip-then-check, restore if still dirty.\n const stripped = updated.replace(MIGRATION_TASK, \"\");\n if (!DOLLAR_PROPS_ANY.test(stripped)) {\n updated = stripped;\n }\n\n return updated;\n}\n","/**\n * Converts Svelte 4 `$:` reactive statements to Svelte 5 runes.\n *\n * - `$: var = expr;` → `let var = $derived(expr);`\n * - `$: { body }` → `$effect(() => { body });`\n *\n * Triggered by:\n * \"`$:` is not allowed in runes mode, use `$derived` or `$effect` instead\"\n * (svelte.dev/e/legacy_reactive_statement_invalid)\n *\n * Block patterns become `$effect` rather than per-variable `$derived` calls\n * because the block typically mutates multiple already-declared `let`\n * variables with conditional logic — too contextual for a safe automatic\n * decomposition into discrete derived values. The user can refine each\n * `$effect` into idiomatic `$derived` calls afterward if desired.\n *\n * Scoped to `<script>` content only — `$:` in template/style text is left\n * alone (it would only ever appear there as literal text anyway).\n */\nimport { findStringEnd } from \"../../../util/svelte-source.js\";\n\nconst SCRIPT_BLOCK = /<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/g;\nconst SIMPLE_REACTIVE = /^([ \\t]*)\\$:\\s*(\\w+)\\s*=\\s*([^;\\n]+);?[ \\t]*$/gm;\nconst BLOCK_REACTIVE_HEAD = /(^|\\n)([ \\t]*)\\$:\\s*\\{/g;\n\nfunction findMatchingClose(source: string, openIdx: number): number {\n let depth = 0;\n let i = openIdx;\n while (i < source.length) {\n const ch = source[i];\n // Skip over string literals so braces inside strings don't fool the counter.\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n const closeStr = findStringEnd(source, i);\n if (closeStr === -1) return -1;\n i = closeStr + 1;\n continue;\n }\n // Skip over comments so braces inside `// }` or `/* } */` don't fool the\n // counter. Regression: the old version silently corrupted source (depth\n // went off, real closing brace mis-matched) on inputs like `$: { // } ... }`.\n // The corrupted output still compiles in Svelte 5 — no parser to scream.\n if (ch === \"/\") {\n const next = source[i + 1];\n if (next === \"/\") {\n const eol = source.indexOf(\"\\n\", i + 2);\n i = eol === -1 ? source.length : eol; // step onto newline; outer loop handles it\n continue;\n }\n if (next === \"*\") {\n const end = source.indexOf(\"*/\", i + 2);\n if (end === -1) return -1; // unterminated block comment — bail rather than corrupt\n i = end + 2;\n continue;\n }\n }\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) return i;\n }\n i++;\n }\n return -1;\n}\n\n/** Flag each converted `$effect` block for manual review. The conversion is\n * syntactically safe (compiles), but if any of the locals the block mutates\n * was declared as plain `let` (not `$state`), the `$effect` runs once on\n * mount and never again — code silently loses its reactivity. We can't\n * detect that automatically (it would require scope analysis on the\n * declaration sites), so we leave a breadcrumb for the human reviewer. */\nconst MIGRATION_MARKER =\n \"// @migration-task: $effect won't trigger UI updates on plain `let` bindings — refine mutated locals to $state or split into per-variable $derived.\";\n\nfunction transformBlocks(body: string): string {\n const out: string[] = [];\n let last = 0;\n BLOCK_REACTIVE_HEAD.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = BLOCK_REACTIVE_HEAD.exec(body)) !== null) {\n const leadingNewline = m[1] ?? \"\";\n const indent = m[2] ?? \"\";\n const headEnd = m.index + m[0].length; // position just after `{`\n const openBraceIdx = headEnd - 1;\n const closeBraceIdx = findMatchingClose(body, openBraceIdx);\n if (closeBraceIdx === -1) continue;\n out.push(body.slice(last, m.index));\n out.push(leadingNewline);\n const blockBody = body.slice(openBraceIdx + 1, closeBraceIdx);\n out.push(`${indent}${MIGRATION_MARKER}\\n`);\n out.push(`${indent}$effect(() => {${blockBody}});`);\n last = closeBraceIdx + 1;\n BLOCK_REACTIVE_HEAD.lastIndex = last;\n }\n out.push(body.slice(last));\n return out.join(\"\");\n}\n\nfunction transformSimple(body: string): string {\n return body.replace(SIMPLE_REACTIVE, (_full, indent: string, name: string, expr: string) => {\n return `${indent}let ${name} = $derived(${expr.trim()});`;\n });\n}\n\nexport function legacyReactiveToRunes(source: string): string {\n return source.replace(SCRIPT_BLOCK, (full, _attrs: string, body: string) => {\n // Blocks first so an outer `$: { ... }` containing nothing matchable\n // for the simple pass still gets wrapped. Order doesn't matter for the\n // patterns currently in the fleet but keeps the codemod robust to future\n // shapes.\n let next = transformBlocks(body);\n next = transformSimple(next);\n if (next === body) return full;\n return full.replace(body, next);\n });\n}\n"],"mappings":"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","names":["SCRIPT_BLOCK","SCRIPT_BLOCK","SCRIPT_BLOCK","SCRIPT_BLOCK"]}
@@ -0,0 +1,52 @@
1
+ // src/util/pkg.ts
2
+ import { readFile, writeFile } from "fs/promises";
3
+ async function readPackageJson(path) {
4
+ const raw = await readFile(path, "utf-8");
5
+ return JSON.parse(raw);
6
+ }
7
+ function detectIndentFromContent(raw) {
8
+ const match = raw.match(/\n([ \t]+)"/);
9
+ return match ? match[1] ?? " " : " ";
10
+ }
11
+ async function writePackageJson(path, pkg) {
12
+ let indent = " ";
13
+ try {
14
+ const existing = await readFile(path, "utf-8");
15
+ indent = detectIndentFromContent(existing);
16
+ } catch {
17
+ }
18
+ const content = JSON.stringify(pkg, null, indent) + "\n";
19
+ await writeFile(path, content, "utf-8");
20
+ }
21
+ function bumpDep(pkg, name, version, opts = {}) {
22
+ const mode = opts.mode ?? "ensure";
23
+ const next = {
24
+ ...pkg
25
+ };
26
+ if (pkg.dependencies) {
27
+ next.dependencies = { ...pkg.dependencies };
28
+ }
29
+ if (pkg.devDependencies) {
30
+ next.devDependencies = { ...pkg.devDependencies };
31
+ }
32
+ if (next.dependencies && name in next.dependencies) {
33
+ if (next.dependencies[name] === version) return pkg;
34
+ next.dependencies[name] = version;
35
+ return next;
36
+ }
37
+ if (next.devDependencies && name in next.devDependencies) {
38
+ if (next.devDependencies[name] === version) return pkg;
39
+ next.devDependencies[name] = version;
40
+ return next;
41
+ }
42
+ if (mode === "bump-only") return pkg;
43
+ next.devDependencies = { ...next.devDependencies ?? {}, [name]: version };
44
+ return next;
45
+ }
46
+
47
+ export {
48
+ readPackageJson,
49
+ writePackageJson,
50
+ bumpDep
51
+ };
52
+ //# sourceMappingURL=chunk-OTGB6YQO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/pkg.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\n\nexport type PackageJsonLike = {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n [key: string]: unknown;\n};\n\nexport async function readPackageJson(path: string): Promise<PackageJsonLike> {\n const raw = await readFile(path, \"utf-8\");\n return JSON.parse(raw) as PackageJsonLike;\n}\n\n/** Sniff the indent style (tab vs 2 vs 4 vs N spaces) from existing package.json\n * content by looking at the first indented `\"key\"` line. Defaults to two spaces. */\nfunction detectIndentFromContent(raw: string): string {\n const match = raw.match(/\\n([ \\t]+)\"/);\n return match ? (match[1] ?? \" \") : \" \";\n}\n\nexport async function writePackageJson(path: string, pkg: PackageJsonLike): Promise<void> {\n let indent = \" \";\n try {\n const existing = await readFile(path, \"utf-8\");\n indent = detectIndentFromContent(existing);\n } catch {\n // file doesn't exist yet — first write — keep the 2-space default\n }\n const content = JSON.stringify(pkg, null, indent) + \"\\n\";\n await writeFile(path, content, \"utf-8\");\n}\n\nexport type BumpDepMode =\n | \"ensure\" // default: add to devDependencies if missing\n | \"bump-only\"; // never add; only update existing entries\n\nexport type BumpDepOptions = {\n mode?: BumpDepMode;\n};\n\nexport function bumpDep(\n pkg: PackageJsonLike,\n name: string,\n version: string,\n opts: BumpDepOptions = {},\n): PackageJsonLike {\n const mode = opts.mode ?? \"ensure\";\n\n const next: PackageJsonLike = {\n ...pkg,\n };\n\n if (pkg.dependencies) {\n next.dependencies = { ...pkg.dependencies };\n }\n if (pkg.devDependencies) {\n next.devDependencies = { ...pkg.devDependencies };\n }\n\n if (next.dependencies && name in next.dependencies) {\n if (next.dependencies[name] === version) return pkg;\n next.dependencies[name] = version;\n return next;\n }\n if (next.devDependencies && name in next.devDependencies) {\n if (next.devDependencies[name] === version) return pkg;\n next.devDependencies[name] = version;\n return next;\n }\n // Not present in either map. bump-only leaves the pkg alone so recipes\n // can express \"raise the floor on packages this site already uses\" without\n // also installing every related dep across the fleet.\n if (mode === \"bump-only\") return pkg;\n next.devDependencies = { ...(next.devDependencies ?? {}), [name]: version };\n return next;\n}\n"],"mappings":";AAAA,SAAS,UAAU,iBAAiB;AAUpC,eAAsB,gBAAgB,MAAwC;AAC5E,QAAM,MAAM,MAAM,SAAS,MAAM,OAAO;AACxC,SAAO,KAAK,MAAM,GAAG;AACvB;AAIA,SAAS,wBAAwB,KAAqB;AACpD,QAAM,QAAQ,IAAI,MAAM,aAAa;AACrC,SAAO,QAAS,MAAM,CAAC,KAAK,OAAQ;AACtC;AAEA,eAAsB,iBAAiB,MAAc,KAAqC;AACxF,MAAI,SAAS;AACb,MAAI;AACF,UAAM,WAAW,MAAM,SAAS,MAAM,OAAO;AAC7C,aAAS,wBAAwB,QAAQ;AAAA,EAC3C,QAAQ;AAAA,EAER;AACA,QAAM,UAAU,KAAK,UAAU,KAAK,MAAM,MAAM,IAAI;AACpD,QAAM,UAAU,MAAM,SAAS,OAAO;AACxC;AAUO,SAAS,QACd,KACA,MACA,SACA,OAAuB,CAAC,GACP;AACjB,QAAM,OAAO,KAAK,QAAQ;AAE1B,QAAM,OAAwB;AAAA,IAC5B,GAAG;AAAA,EACL;AAEA,MAAI,IAAI,cAAc;AACpB,SAAK,eAAe,EAAE,GAAG,IAAI,aAAa;AAAA,EAC5C;AACA,MAAI,IAAI,iBAAiB;AACvB,SAAK,kBAAkB,EAAE,GAAG,IAAI,gBAAgB;AAAA,EAClD;AAEA,MAAI,KAAK,gBAAgB,QAAQ,KAAK,cAAc;AAClD,QAAI,KAAK,aAAa,IAAI,MAAM,QAAS,QAAO;AAChD,SAAK,aAAa,IAAI,IAAI;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,KAAK,mBAAmB,QAAQ,KAAK,iBAAiB;AACxD,QAAI,KAAK,gBAAgB,IAAI,MAAM,QAAS,QAAO;AACnD,SAAK,gBAAgB,IAAI,IAAI;AAC7B,WAAO;AAAA,EACT;AAIA,MAAI,SAAS,YAAa,QAAO;AACjC,OAAK,kBAAkB,EAAE,GAAI,KAAK,mBAAmB,CAAC,GAAI,CAAC,IAAI,GAAG,QAAQ;AAC1E,SAAO;AACT;","names":[]}
@@ -0,0 +1,14 @@
1
+ // src/alerts/renovate.ts
2
+ var RENOVATE_HEAD_PREFIXES = ["renovate/", "renovate-"];
3
+ function isRenovatePR(pr) {
4
+ return RENOVATE_HEAD_PREFIXES.some((p) => pr.headRef.startsWith(p));
5
+ }
6
+ function isFailingRenovatePR(pr) {
7
+ return isRenovatePR(pr) && pr.ciState === "failing";
8
+ }
9
+
10
+ export {
11
+ isRenovatePR,
12
+ isFailingRenovatePR
13
+ };
14
+ //# sourceMappingURL=chunk-PZQ3WED2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/alerts/renovate.ts"],"sourcesContent":["import type { PullRequestSummary } from \"../github/gh.js\";\n\n/**\n * Renovate's default `branchPrefix` is `renovate/`, so every update branch on a\n * default-config repo is `renovate/<topic>` — grouped majors included (they're\n * `renovate/<group-slug>`, still slash-prefixed, not a separate shape). We also\n * match a bare `renovate-` prefix to defensively catch a repo that sets a custom\n * non-slash `branchPrefix`; the tradeoff is that a human branch named `renovate-foo`\n * is misclassified — an acceptable bias for an alerting tool that should prefer\n * over-matching to silently missing a broken update.\n */\nconst RENOVATE_HEAD_PREFIXES = [\"renovate/\", \"renovate-\"];\n\nexport function isRenovatePR(pr: Pick<PullRequestSummary, \"headRef\">): boolean {\n return RENOVATE_HEAD_PREFIXES.some((p) => pr.headRef.startsWith(p));\n}\n\nexport function isFailingRenovatePR(pr: PullRequestSummary): boolean {\n return isRenovatePR(pr) && pr.ciState === \"failing\";\n}\n"],"mappings":";AAWA,IAAM,yBAAyB,CAAC,aAAa,WAAW;AAEjD,SAAS,aAAa,IAAkD;AAC7E,SAAO,uBAAuB,KAAK,CAAC,MAAM,GAAG,QAAQ,WAAW,CAAC,CAAC;AACpE;AAEO,SAAS,oBAAoB,IAAiC;AACnE,SAAO,aAAa,EAAE,KAAK,GAAG,YAAY;AAC5C;","names":[]}
@@ -0,0 +1,13 @@
1
+ // src/forms/types.ts
2
+ var SUBMISSION_FORM_TYPES = [
3
+ "contact",
4
+ "inquiry",
5
+ "newsletter",
6
+ "rsvp",
7
+ "reserve"
8
+ ];
9
+
10
+ export {
11
+ SUBMISSION_FORM_TYPES
12
+ };
13
+ //# sourceMappingURL=chunk-QTHKOIFJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/forms/types.ts"],"sourcesContent":["/**\n * Form-type enum, kept in a leaf module (no Airtable/Resend imports) so it can\n * be shared with fleet sites via the `@reddoorla/maintenance/forms` subpath\n * without dragging server SDKs into a site bundle.\n */\nexport const SUBMISSION_FORM_TYPES = [\n \"contact\",\n \"inquiry\",\n \"newsletter\",\n \"rsvp\",\n \"reserve\",\n] as const;\nexport type FormType = (typeof SUBMISSION_FORM_TYPES)[number];\n"],"mappings":";AAKO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -0,0 +1,26 @@
1
+ // src/util/url.ts
2
+ function isHttpUrl(s) {
3
+ let parsed;
4
+ try {
5
+ parsed = new URL(s);
6
+ } catch {
7
+ return false;
8
+ }
9
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
10
+ }
11
+ function isNetlifyAppUrl(s) {
12
+ let parsed;
13
+ try {
14
+ parsed = new URL(s);
15
+ } catch {
16
+ return false;
17
+ }
18
+ const host = parsed.hostname.toLowerCase();
19
+ return host === "netlify.app" || host.endsWith(".netlify.app");
20
+ }
21
+
22
+ export {
23
+ isHttpUrl,
24
+ isNetlifyAppUrl
25
+ };
26
+ //# sourceMappingURL=chunk-U5SZ4FOP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/url.ts"],"sourcesContent":["/**\n * True when `s` parses as an absolute URL whose scheme is `http:` or `https:`.\n *\n * The single allowlist gate for any value we hand to Chrome/Lighthouse. A\n * deployed-audit URL flows in from Airtable's `url` column (or a JSON\n * inventory's `deployedUrl`), so a `file://`/`gopher://`/`data:` value — or a\n * value pointing at an internal host — would otherwise become a local-file read\n * or SSRF when lhci drives a headless browser at it. Restricting to http(s)\n * keeps the audit to the real, network-reachable site.\n */\nexport function isHttpUrl(s: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(s);\n } catch {\n return false;\n }\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\n}\n\n/**\n * True when `s` is a URL served from Netlify's default `*.netlify.app` host —\n * i.e. the site has no custom domain. Matches the apex `netlify.app` and any\n * subdomain of it (including deploy-preview hosts like `branch--site.netlify.app`),\n * but is not fooled by a look-alike such as `foo.netlify.app.evil.com` (the host\n * must END at `.netlify.app`). An unparseable/empty value is not a match.\n */\nexport function isNetlifyAppUrl(s: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(s);\n } catch {\n return false;\n }\n const host = parsed.hostname.toLowerCase();\n return host === \"netlify.app\" || host.endsWith(\".netlify.app\");\n}\n\n/**\n * True when `s` is an `https:` URL whose host is NOT an obviously-internal target\n * (loopback / private / link-local / unique-local / CGNAT). The newsletter\n * webhook URL is operator-set in Airtable but fires server-side, so this blocks\n * the SSRF vector of pointing it at `127.0.0.1` / `10.x` / `169.254.x` / `::1`.\n *\n * Best-effort by host literal — it does NOT resolve DNS, so a hostname that\n * resolves to a private IP is not caught. Defense-in-depth, not a complete SSRF\n * guard (the response is never returned to the caller either, so it's a log\n * oracle at most).\n */\nexport function isPublicHttpsUrl(s: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(s);\n } catch {\n return false;\n }\n if (parsed.protocol !== \"https:\") return false;\n let host = parsed.hostname.toLowerCase();\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n // Strip IPv6 brackets (`[::1]` → `::1`).\n if (host.startsWith(\"[\") && host.endsWith(\"]\")) host = host.slice(1, -1);\n if (host.includes(\":\")) {\n // IPv6 literal: block loopback (::1), unspecified (::), link-local (fe80::/10),\n // and unique-local (fc00::/7).\n if (host === \"::1\" || host === \"::\") return false;\n if (/^fe[89ab]/.test(host)) return false;\n if (/^f[cd]/.test(host)) return false;\n // IPv4-mapped (`::ffff:a.b.c.d`, normalized by URL to `::ffff:aabb:ccdd`) and\n // NAT64 (`64:ff9b::/96`) embed a v4 address the dotted-quad block below never\n // sees — refuse both wholesale (no legitimate webhook target uses these forms).\n if (host.startsWith(\"::ffff:\") || host.startsWith(\"64:ff9b:\")) return false;\n return true;\n }\n // IPv4 dotted-quad: block the private/loopback/link-local/CGNAT ranges.\n if (/^\\d{1,3}(\\.\\d{1,3}){3}$/.test(host)) {\n const [a, b] = host.split(\".\").map(Number);\n if (a === 0 || a === 10 || a === 127) return false;\n if (a === 169 && b === 254) return false; // link-local\n if (a === 172 && b !== undefined && b >= 16 && b <= 31) return false; // 172.16/12\n if (a === 192 && b === 168) return false; // 192.168/16\n if (a === 100 && b !== undefined && b >= 64 && b <= 127) return false; // CGNAT 100.64/10\n return true;\n }\n // A registered hostname (not a literal IP) — accept; DNS isn't resolved here.\n return true;\n}\n"],"mappings":";AAUO,SAAS,UAAU,GAAoB;AAC5C,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,CAAC;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,OAAO,aAAa,WAAW,OAAO,aAAa;AAC5D;AASO,SAAS,gBAAgB,GAAoB;AAClD,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,CAAC;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,SAAS,YAAY;AACzC,SAAO,SAAS,iBAAiB,KAAK,SAAS,cAAc;AAC/D;","names":[]}