@reddoorla/maintenance 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,6 +31,11 @@ var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve, reject) => {
31
31
  import { readFile } from "fs/promises";
32
32
  import { join } from "path";
33
33
 
34
+ // src/util/site.ts
35
+ function siteLabel(site) {
36
+ return site.name ?? site.path;
37
+ }
38
+
34
39
  // src/configs/baseline-versions.ts
35
40
  var baselineVersions = {
36
41
  // SvelteKit core
@@ -71,9 +76,6 @@ var baselineVersions = {
71
76
  };
72
77
 
73
78
  // src/audits/deps.ts
74
- function siteLabel(site) {
75
- return site.name ?? site.path;
76
- }
77
79
  function stripCaret(range) {
78
80
  return range.replace(/^[\^~]/, "");
79
81
  }
@@ -146,9 +148,6 @@ import { check as prettierCheck, resolveConfig as prettierResolveConfig } from "
146
148
  import { glob } from "tinyglobby";
147
149
  var TARGET_GLOBS = ["**/*.{ts,js,svelte}"];
148
150
  var IGNORE = ["node_modules/**", "dist/**", ".svelte-kit/**", "build/**", ".netlify/**"];
149
- function siteLabel2(site) {
150
- return site.name ?? site.path;
151
- }
152
151
  async function listFiles(cwd) {
153
152
  return glob(TARGET_GLOBS, { cwd, ignore: IGNORE, absolute: false });
154
153
  }
@@ -158,7 +157,7 @@ async function lintAudit(ctx) {
158
157
  if (!existsSync(configPath)) {
159
158
  return {
160
159
  audit: "lint",
161
- site: siteLabel2(site),
160
+ site: siteLabel(site),
162
161
  status: "skip",
163
162
  summary: "no eslint config at site root"
164
163
  };
@@ -184,7 +183,7 @@ async function lintAudit(ctx) {
184
183
  const summary = status === "pass" ? `lint clean across ${relFiles.length} files` : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;
185
184
  return {
186
185
  audit: "lint",
187
- site: siteLabel2(site),
186
+ site: siteLabel(site),
188
187
  status,
189
188
  summary,
190
189
  details: {
@@ -197,9 +196,6 @@ async function lintAudit(ctx) {
197
196
  }
198
197
 
199
198
  // src/audits/security.ts
200
- function siteLabel3(site) {
201
- return site.name ?? site.path;
202
- }
203
199
  function classify(v) {
204
200
  if (v.critical > 0 || v.high > 0) return "fail";
205
201
  if (v.moderate > 0 || v.low > 0) return "warn";
@@ -285,7 +281,8 @@ async function runAuditTool(spawn2, cmd, args, cwd) {
285
281
  if (errEnvelope && typeof errEnvelope === "object") {
286
282
  return { kind: "error", reason: errEnvelope.code ?? "error envelope returned" };
287
283
  }
288
- if (!parsed.metadata?.vulnerabilities) {
284
+ const vulnsMeta = parsed.metadata?.vulnerabilities;
285
+ if (!vulnsMeta || Object.keys(vulnsMeta).length === 0) {
289
286
  return { kind: "error", reason: "no metadata.vulnerabilities in output" };
290
287
  }
291
288
  return { kind: "ok", parsed };
@@ -293,7 +290,7 @@ async function runAuditTool(spawn2, cmd, args, cwd) {
293
290
  async function securityAudit(ctx) {
294
291
  const spawn2 = ctx.spawn ?? defaultSpawn;
295
292
  const site = ctx.site;
296
- const label = siteLabel3(site);
293
+ const label = siteLabel(site);
297
294
  let used = "pnpm audit";
298
295
  let result = await runAuditTool(spawn2, "pnpm", ["audit", "--json", "--prod"], site.path);
299
296
  if (result.kind !== "ok") {
@@ -374,9 +371,6 @@ var lighthouseConfig = {
374
371
  };
375
372
 
376
373
  // src/audits/lighthouse.ts
377
- function siteLabel4(site) {
378
- return site.name ?? site.path;
379
- }
380
374
  async function readJsonMaybe(path) {
381
375
  try {
382
376
  const raw = await readFile3(path, "utf-8");
@@ -414,7 +408,7 @@ function messageForAssertion(a) {
414
408
  async function lighthouseAudit(ctx) {
415
409
  const spawn2 = ctx.spawn ?? defaultSpawn;
416
410
  const site = ctx.site;
417
- const label = siteLabel4(site);
411
+ const label = siteLabel(site);
418
412
  const configDir = await mkdtemp(join3(tmpdir(), "reddoor-lhci-"));
419
413
  const configPath = join3(configDir, "lighthouserc.json");
420
414
  await writeFile(configPath, JSON.stringify(lighthouseConfig), "utf-8");
@@ -512,9 +506,6 @@ var playwrightA11yConfig = defineConfig({
512
506
 
513
507
  // src/audits/a11y.ts
514
508
  var RESULTS_REL = ".reddoor-a11y/results.json";
515
- function siteLabel5(site) {
516
- return site.name ?? site.path;
517
- }
518
509
  async function readJsonMaybe2(path) {
519
510
  try {
520
511
  const raw = await readFile4(path, "utf-8");
@@ -572,7 +563,7 @@ test("a11y across configured routes", async ({ page }) => {
572
563
  async function a11yAudit(ctx) {
573
564
  const spawn2 = ctx.spawn ?? defaultSpawn;
574
565
  const site = ctx.site;
575
- const label = siteLabel5(site);
566
+ const label = siteLabel(site);
576
567
  const specDir = await mkdtemp2(join4(tmpdir2(), "reddoor-a11y-spec-"));
577
568
  const specPath = join4(specDir, "a11y.spec.ts");
578
569
  await writeFile2(specPath, buildSpec(), "utf-8");
@@ -820,9 +811,8 @@ async function git(cwd, args) {
820
811
  return exec("git", args, { cwd, env: process.env });
821
812
  }
822
813
  function branchName(recipe, when = /* @__PURE__ */ new Date()) {
823
- const iso = when.toISOString().replace(/[-:.]/g, "").replace(/Z$/, "Z");
824
- const trimmed = iso.replace(/(\d{8}T\d{6})\d+(Z)$/, "$1$2");
825
- return `maint/${recipe}-${trimmed}`;
814
+ const compact = when.toISOString().replace(/[-:.]/g, "");
815
+ return `maint/${recipe}-${compact}`;
826
816
  }
827
817
  async function isWorkingTreeClean(cwd) {
828
818
  const { stdout } = await git(cwd, ["status", "--porcelain"]);
@@ -853,9 +843,6 @@ async function commit(cwd, message) {
853
843
 
854
844
  // src/recipes/sync-configs.ts
855
845
  var GITIGNORE_CONFIG = "gitignore";
856
- function siteLabel6(site) {
857
- return site.name ?? site.path;
858
- }
859
846
  async function readMaybe(path) {
860
847
  try {
861
848
  return await readFile5(path, "utf-8");
@@ -886,7 +873,7 @@ async function applyGitignore(cwd, plan) {
886
873
  }
887
874
  }
888
875
  async function syncConfigs(site, opts = {}) {
889
- const label = siteLabel6(site);
876
+ const label = siteLabel(site);
890
877
  const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);
891
878
  const templateNames = requested.filter((c) => c !== GITIGNORE_CONFIG);
892
879
  const templates = templatesByName(templateNames);
@@ -931,8 +918,15 @@ async function syncConfigs(site, opts = {}) {
931
918
  }
932
919
 
933
920
  // src/recipes/bump-deps.ts
934
- function siteLabel7(site) {
935
- return site.name ?? site.path;
921
+ import { stat } from "fs/promises";
922
+ import { join as join6 } from "path";
923
+ async function exists(path) {
924
+ try {
925
+ await stat(path);
926
+ return true;
927
+ } catch {
928
+ return false;
929
+ }
936
930
  }
937
931
  function outdatedFlagsForGroup(group) {
938
932
  if (group === "major") return ["--latest"];
@@ -944,9 +938,27 @@ function upFlagsForGroup(group) {
944
938
  return [];
945
939
  }
946
940
  async function bumpDeps(site, opts = {}) {
947
- const label = siteLabel7(site);
941
+ const label = siteLabel(site);
948
942
  const group = opts.group ?? "minor";
949
943
  const spawn2 = opts.spawn ?? defaultSpawn;
944
+ const hasPnpmLock = await exists(join6(site.path, "pnpm-lock.yaml"));
945
+ if (!hasPnpmLock) {
946
+ const hasNpmLock = await exists(join6(site.path, "package-lock.json"));
947
+ const hasYarnLock = await exists(join6(site.path, "yarn.lock"));
948
+ if (hasNpmLock || hasYarnLock) {
949
+ const competing = hasNpmLock ? "package-lock.json" : "yarn.lock";
950
+ return {
951
+ recipe: "bump-deps",
952
+ site: label,
953
+ status: "failed",
954
+ commits: [],
955
+ notes: `site has ${competing} but no pnpm-lock.yaml \u2014 run convert-to-pnpm first`
956
+ };
957
+ }
958
+ }
959
+ if (!await isWorkingTreeClean(site.path)) {
960
+ throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
961
+ }
950
962
  await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
951
963
  const outdated = await spawn2("pnpm", ["outdated", "--json", ...outdatedFlagsForGroup(group)], {
952
964
  cwd: site.path
@@ -967,9 +979,6 @@ async function bumpDeps(site, opts = {}) {
967
979
  notes: `pnpm outdated reported nothing for group=${group}`
968
980
  };
969
981
  }
970
- if (!await isWorkingTreeClean(site.path)) {
971
- throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
972
- }
973
982
  const branch = branchName("bump-deps");
974
983
  await createBranch(site.path, branch);
975
984
  await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
@@ -988,7 +997,7 @@ async function bumpDeps(site, opts = {}) {
988
997
  }
989
998
 
990
999
  // src/recipes/svelte-5/index.ts
991
- import { join as join11 } from "path";
1000
+ import { join as join12 } from "path";
992
1001
 
993
1002
  // src/util/pkg.ts
994
1003
  import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
@@ -996,8 +1005,18 @@ async function readPackageJson(path) {
996
1005
  const raw = await readFile6(path, "utf-8");
997
1006
  return JSON.parse(raw);
998
1007
  }
1008
+ function detectIndentFromContent(raw) {
1009
+ const match = raw.match(/\n([ \t]+)"/);
1010
+ return match ? match[1] ?? " " : " ";
1011
+ }
999
1012
  async function writePackageJson(path, pkg) {
1000
- const content = JSON.stringify(pkg, null, 2) + "\n";
1013
+ let indent = " ";
1014
+ try {
1015
+ const existing = await readFile6(path, "utf-8");
1016
+ indent = detectIndentFromContent(existing);
1017
+ } catch {
1018
+ }
1019
+ const content = JSON.stringify(pkg, null, indent) + "\n";
1001
1020
  await writeFile4(path, content, "utf-8");
1002
1021
  }
1003
1022
  function bumpDep(pkg, name, version, opts = {}) {
@@ -1027,7 +1046,7 @@ function bumpDep(pkg, name, version, opts = {}) {
1027
1046
  }
1028
1047
 
1029
1048
  // src/recipes/svelte-5/step-bump-versions.ts
1030
- import { join as join6 } from "path";
1049
+ import { join as join7 } from "path";
1031
1050
  var SVELTE_5_VERSIONS = {
1032
1051
  svelte: "^5.55.5",
1033
1052
  "@sveltejs/kit": "^2.59.0",
@@ -1040,7 +1059,7 @@ var SVELTE_5_VERSIONS = {
1040
1059
  "typescript-svelte-plugin": "^0.3.52"
1041
1060
  };
1042
1061
  async function bumpToSvelte5Versions(cwd) {
1043
- const pkgPath = join6(cwd, "package.json");
1062
+ const pkgPath = join7(cwd, "package.json");
1044
1063
  const pkg = await readPackageJson(pkgPath);
1045
1064
  let next = pkg;
1046
1065
  for (const [name, version] of Object.entries(SVELTE_5_VERSIONS)) {
@@ -1053,7 +1072,7 @@ async function bumpToSvelte5Versions(cwd) {
1053
1072
 
1054
1073
  // src/recipes/svelte-5/step-svelte-config.ts
1055
1074
  import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
1056
- import { join as join7 } from "path";
1075
+ import { join as join8 } from "path";
1057
1076
  var VITE_PLUGIN_PKG = "@sveltejs/vite-plugin-svelte";
1058
1077
  var IMPORT_FROM_VITE_PLUGIN = new RegExp(
1059
1078
  String.raw`^import\s+\{\s*([^}]+?)\s*\}\s+from\s+["']` + VITE_PLUGIN_PKG.replace(/[/]/g, "\\/") + String.raw`["'];?[ \t]*\n`,
@@ -1094,7 +1113,7 @@ function dropPreprocessKey(source) {
1094
1113
  return source.slice(0, m.index) + source.slice(tailIdx).replace(new RegExp(`^${indent}\\n`), "");
1095
1114
  }
1096
1115
  async function migrateSvelteConfig(cwd) {
1097
- const path = join7(cwd, "svelte.config.js");
1116
+ const path = join8(cwd, "svelte.config.js");
1098
1117
  let src;
1099
1118
  try {
1100
1119
  src = await readFile7(path, "utf-8");
@@ -1131,9 +1150,9 @@ async function runSvelteMigrate(cwd, spawn2 = defaultSpawn) {
1131
1150
  }
1132
1151
 
1133
1152
  // src/recipes/svelte-5/step-tailwind-upgrade.ts
1134
- import { join as join8 } from "path";
1153
+ import { join as join9 } from "path";
1135
1154
  async function upgradeTailwind(cwd, spawn2 = defaultSpawn) {
1136
- const pkg = await readPackageJson(join8(cwd, "package.json"));
1155
+ const pkg = await readPackageJson(join9(cwd, "package.json"));
1137
1156
  const tailwindVersion = pkg.devDependencies?.tailwindcss ?? pkg.dependencies?.tailwindcss;
1138
1157
  if (!tailwindVersion) return { ran: false, reason: "tailwindcss not installed" };
1139
1158
  if (/^\^?4\./.test(tailwindVersion)) return { ran: false, reason: "already on tailwind 4.x" };
@@ -1155,12 +1174,40 @@ async function upgradeTailwind(cwd, spawn2 = defaultSpawn) {
1155
1174
 
1156
1175
  // src/recipes/svelte-5/step-gotchas.ts
1157
1176
  import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
1158
- import { join as join9 } from "path";
1177
+ import { join as join10 } from "path";
1159
1178
  import { glob as glob2 } from "tinyglobby";
1160
1179
 
1161
1180
  // src/recipes/svelte-5/codemods/on-event-to-handler.ts
1162
1181
  var SCRIPT_BLOCK = /<script\b[^>]*>[\s\S]*?<\/script>/g;
1163
1182
  var SIMPLE_ON_EVENT = /\bon:([a-z]+)(?=\s*=)/g;
1183
+ var MODIFIER_EVENT = /\bon:[a-z]+\|[a-zA-Z]+(?=\s*=)/g;
1184
+ function flagEventModifiers(source) {
1185
+ const insertions = [];
1186
+ let m;
1187
+ MODIFIER_EVENT.lastIndex = 0;
1188
+ while ((m = MODIFIER_EVENT.exec(source)) !== null) {
1189
+ const tagStart = source.lastIndexOf("<", m.index);
1190
+ if (tagStart === -1) continue;
1191
+ const prevLineEnd = tagStart - 1;
1192
+ if (prevLineEnd >= 0) {
1193
+ const prevLineStart = source.lastIndexOf("\n", prevLineEnd - 1) + 1;
1194
+ const prevLine = source.slice(prevLineStart, prevLineEnd + 1);
1195
+ if (/<!--\s*@migration-task/.test(prevLine)) continue;
1196
+ }
1197
+ const lineStart = source.lastIndexOf("\n", tagStart - 1) + 1;
1198
+ const indent = source.slice(lineStart, tagStart);
1199
+ const safeIndent = /^[ \t]*$/.test(indent) ? indent : "";
1200
+ insertions.push({ tagStart, indent: safeIndent, modifier: m[0] });
1201
+ }
1202
+ let out = source;
1203
+ for (let i = insertions.length - 1; i >= 0; i--) {
1204
+ const { tagStart, indent, modifier } = insertions[i];
1205
+ const comment = `<!-- @migration-task: Svelte 5 removed event modifier syntax (\`${modifier}\`). Rewrite inline, e.g. onclick={(e) => { e.preventDefault(); ... }}. -->
1206
+ ${indent}`;
1207
+ out = out.slice(0, tagStart) + comment + out.slice(tagStart);
1208
+ }
1209
+ return out;
1210
+ }
1164
1211
  function onEventToHandler(source) {
1165
1212
  const masked = [];
1166
1213
  const placeholder = (i) => ` SCRIPT_${i} `;
@@ -1168,8 +1215,9 @@ function onEventToHandler(source) {
1168
1215
  masked.push(match);
1169
1216
  return placeholder(masked.length - 1);
1170
1217
  });
1171
- const rewritten = intermediate.replace(SIMPLE_ON_EVENT, (_full, name) => `on${name}`);
1172
- let out = rewritten;
1218
+ let processed = intermediate.replace(SIMPLE_ON_EVENT, (_full, name) => `on${name}`);
1219
+ processed = flagEventModifiers(processed);
1220
+ let out = processed;
1173
1221
  masked.forEach((blk, i) => {
1174
1222
  out = out.replace(placeholder(i), blk);
1175
1223
  });
@@ -1219,6 +1267,22 @@ function exportLetToProps(source) {
1219
1267
  return source.replace(SCRIPT_BLOCK2, (full) => full.replace(inner, body));
1220
1268
  }
1221
1269
 
1270
+ // src/util/svelte-source.ts
1271
+ function findStringEnd(source, openIdx) {
1272
+ const quote = source[openIdx];
1273
+ let i = openIdx + 1;
1274
+ while (i < source.length) {
1275
+ const ch = source[i];
1276
+ if (ch === "\\") {
1277
+ i += 2;
1278
+ continue;
1279
+ }
1280
+ if (ch === quote) return i;
1281
+ i++;
1282
+ }
1283
+ return -1;
1284
+ }
1285
+
1222
1286
  // src/recipes/svelte-5/codemods/dollar-restprops.ts
1223
1287
  function removeInterfaceBlock(source) {
1224
1288
  const re = /^\s*interface\s+\$\$Props\s*\{/m;
@@ -1242,20 +1306,6 @@ function removeInterfaceBlock(source) {
1242
1306
  out = out.slice(0, match.index) + out.slice(endIdx);
1243
1307
  }
1244
1308
  }
1245
- function findStringEnd(source, openIdx) {
1246
- const quote = source[openIdx];
1247
- let i = openIdx + 1;
1248
- while (i < source.length) {
1249
- const ch = source[i];
1250
- if (ch === "\\") {
1251
- i += 2;
1252
- continue;
1253
- }
1254
- if (ch === quote) return i;
1255
- i++;
1256
- }
1257
- return -1;
1258
- }
1259
1309
  function maskStringLiterals(source) {
1260
1310
  const strings = [];
1261
1311
  let out = "";
@@ -1332,7 +1382,8 @@ function stateEffectSyncToDerived(source) {
1332
1382
 
1333
1383
  // src/recipes/svelte-5/codemods/dollar-props-class.ts
1334
1384
  var PROPS_DESTRUCTURE = /let\s*\{([\s\S]*?)\}(\s*:\s*\{([\s\S]*?)\})?\s*=\s*\$props\(\)/;
1335
- var DOLLAR_PROPS_CLASS = /\$\$props\.class\b/g;
1385
+ var HAS_DOLLAR_PROPS_CLASS = /\$\$props\.class\b/;
1386
+ var DOLLAR_PROPS_CLASS_GLOBAL = /\$\$props\.class\b/g;
1336
1387
  var DOLLAR_PROPS_ANY = /\$\$props\b/;
1337
1388
  var SCRIPT_BLOCK4 = /<script\b[^>]*>[\s\S]*?<\/script>/g;
1338
1389
  var MIGRATION_TASK = /^<!--\s*@migration-task[\s\S]*?-->\s*\n?/gm;
@@ -1354,8 +1405,7 @@ function restoreScripts(masked, blocks) {
1354
1405
  }
1355
1406
  function dollarPropsClass(source) {
1356
1407
  const { masked } = maskScripts(source);
1357
- if (!DOLLAR_PROPS_CLASS.test(masked)) return source;
1358
- DOLLAR_PROPS_CLASS.lastIndex = 0;
1408
+ if (!HAS_DOLLAR_PROPS_CLASS.test(masked)) return source;
1359
1409
  if (!PROPS_DESTRUCTURE.test(source)) return source;
1360
1410
  let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {
1361
1411
  if (/\bclass\s*:/.test(body)) return full;
@@ -1369,7 +1419,7 @@ function dollarPropsClass(source) {
1369
1419
  return `let { ${newBody} } = $props()`;
1370
1420
  });
1371
1421
  const reMasked = maskScripts(updated);
1372
- const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS, IDENT);
1422
+ const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS_GLOBAL, IDENT);
1373
1423
  updated = restoreScripts(templateRewritten, reMasked.blocks);
1374
1424
  const stripped = updated.replace(MIGRATION_TASK, "");
1375
1425
  if (!DOLLAR_PROPS_ANY.test(stripped)) {
@@ -1388,7 +1438,7 @@ function findMatchingClose(source, openIdx) {
1388
1438
  while (i < source.length) {
1389
1439
  const ch = source[i];
1390
1440
  if (ch === '"' || ch === "'" || ch === "`") {
1391
- const closeStr = findStringClose(source, i);
1441
+ const closeStr = findStringEnd(source, i);
1392
1442
  if (closeStr === -1) return -1;
1393
1443
  i = closeStr + 1;
1394
1444
  continue;
@@ -1402,20 +1452,6 @@ function findMatchingClose(source, openIdx) {
1402
1452
  }
1403
1453
  return -1;
1404
1454
  }
1405
- function findStringClose(source, openIdx) {
1406
- const quote = source[openIdx];
1407
- let i = openIdx + 1;
1408
- while (i < source.length) {
1409
- const ch = source[i];
1410
- if (ch === "\\") {
1411
- i += 2;
1412
- continue;
1413
- }
1414
- if (ch === quote) return i;
1415
- i++;
1416
- }
1417
- return -1;
1418
- }
1419
1455
  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.";
1420
1456
  function transformBlocks(body) {
1421
1457
  const out = [];
@@ -1470,7 +1506,7 @@ async function planGotchaCodemods(cwd) {
1470
1506
  const changes = [];
1471
1507
  const relPaths = await glob2(SVELTE_GLOBS, { cwd, ignore: IGNORE2, absolute: false });
1472
1508
  for (const rel of relPaths) {
1473
- const path = join9(cwd, rel);
1509
+ const path = join10(cwd, rel);
1474
1510
  const before = await readFile8(path, "utf-8");
1475
1511
  const after = CODEMODS.reduce((s, fn) => fn(s), before);
1476
1512
  if (after !== before) changes.push({ rel, after });
@@ -1480,7 +1516,7 @@ async function planGotchaCodemods(cwd) {
1480
1516
  async function applyGotchaCodemods(cwd) {
1481
1517
  const changes = await planGotchaCodemods(cwd);
1482
1518
  for (const c of changes) {
1483
- await writeFile6(join9(cwd, c.rel), c.after, "utf-8");
1519
+ await writeFile6(join10(cwd, c.rel), c.after, "utf-8");
1484
1520
  }
1485
1521
  return { filesChanged: changes.length };
1486
1522
  }
@@ -1504,7 +1540,7 @@ async function verifyMigration(cwd, spawn2 = defaultSpawn) {
1504
1540
 
1505
1541
  // src/recipes/svelte-5/step-summary.ts
1506
1542
  import { writeFile as writeFile7 } from "fs/promises";
1507
- import { join as join10 } from "path";
1543
+ import { join as join11 } from "path";
1508
1544
  async function writeMigrationSummary(input) {
1509
1545
  const lines = [
1510
1546
  `# Svelte 4 \u2192 5 migration summary`,
@@ -1521,18 +1557,15 @@ async function writeMigrationSummary(input) {
1521
1557
  `- Verify Playwright a11y tests still pass.`
1522
1558
  ];
1523
1559
  const content = lines.join("\n") + "\n";
1524
- const path = join10(input.cwd, "MIGRATION_SVELTE_5.md");
1560
+ const path = join11(input.cwd, "MIGRATION_SVELTE_5.md");
1525
1561
  await writeFile7(path, content, "utf-8");
1526
1562
  return path;
1527
1563
  }
1528
1564
 
1529
1565
  // src/recipes/svelte-5/index.ts
1530
- function siteLabel8(site) {
1531
- return site.name ?? site.path;
1532
- }
1533
1566
  async function alreadyOnSvelte5(cwd) {
1534
1567
  try {
1535
- const pkg = await readPackageJson(join11(cwd, "package.json"));
1568
+ const pkg = await readPackageJson(join12(cwd, "package.json"));
1536
1569
  const v = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte;
1537
1570
  return !!v && /^\^?5\./.test(v);
1538
1571
  } catch {
@@ -1540,7 +1573,7 @@ async function alreadyOnSvelte5(cwd) {
1540
1573
  }
1541
1574
  }
1542
1575
  async function upgradeSvelte4to5(site, opts = {}) {
1543
- const label = siteLabel8(site);
1576
+ const label = siteLabel(site);
1544
1577
  const spawn2 = opts.spawn ?? defaultSpawn;
1545
1578
  if (await alreadyOnSvelte5(site.path)) {
1546
1579
  return {
@@ -1610,12 +1643,9 @@ async function upgradeSvelte4to5(site, opts = {}) {
1610
1643
 
1611
1644
  // src/recipes/svelte-codemods.ts
1612
1645
  import { writeFile as writeFile8 } from "fs/promises";
1613
- import { join as join12 } from "path";
1614
- function siteLabel9(site) {
1615
- return site.name ?? site.path;
1616
- }
1646
+ import { join as join13 } from "path";
1617
1647
  async function svelteCodemods(site) {
1618
- const label = siteLabel9(site);
1648
+ const label = siteLabel(site);
1619
1649
  const changes = await planGotchaCodemods(site.path);
1620
1650
  if (changes.length === 0) {
1621
1651
  return {
@@ -1632,7 +1662,7 @@ async function svelteCodemods(site) {
1632
1662
  const branch = branchName("svelte-codemods");
1633
1663
  await createBranch(site.path, branch);
1634
1664
  for (const c of changes) {
1635
- await writeFile8(join12(site.path, c.rel), c.after, "utf-8");
1665
+ await writeFile8(join13(site.path, c.rel), c.after, "utf-8");
1636
1666
  }
1637
1667
  const sha = await commit(
1638
1668
  site.path,
@@ -1648,8 +1678,8 @@ async function svelteCodemods(site) {
1648
1678
  }
1649
1679
 
1650
1680
  // src/recipes/convert-to-pnpm.ts
1651
- import { rm as rm3, stat } from "fs/promises";
1652
- import { join as join13 } from "path";
1681
+ import { rm as rm3, stat as stat2 } from "fs/promises";
1682
+ import { join as join14 } from "path";
1653
1683
 
1654
1684
  // src/recipes/convert-to-pnpm/script-rewrites.ts
1655
1685
  function rewriteScriptForPnpm(script) {
@@ -1671,25 +1701,22 @@ function rewriteScriptsForPnpm(scripts) {
1671
1701
 
1672
1702
  // src/recipes/convert-to-pnpm.ts
1673
1703
  var DEFAULT_PNPM_VERSION = "10.33.1";
1674
- async function exists(path) {
1704
+ async function exists2(path) {
1675
1705
  try {
1676
- await stat(path);
1706
+ await stat2(path);
1677
1707
  return true;
1678
1708
  } catch {
1679
1709
  return false;
1680
1710
  }
1681
1711
  }
1682
- function siteLabel10(site) {
1683
- return site.name ?? site.path;
1684
- }
1685
1712
  async function convertToPnpm(site, opts = {}) {
1686
- const label = siteLabel10(site);
1713
+ const label = siteLabel(site);
1687
1714
  const spawn2 = opts.spawn ?? defaultSpawn;
1688
1715
  const pnpmVersion = opts.pnpmVersion ?? DEFAULT_PNPM_VERSION;
1689
- const pnpmLockPath = join13(site.path, "pnpm-lock.yaml");
1690
- const npmLockPath = join13(site.path, "package-lock.json");
1691
- const yarnLockPath = join13(site.path, "yarn.lock");
1692
- if (await exists(pnpmLockPath)) {
1716
+ const pnpmLockPath = join14(site.path, "pnpm-lock.yaml");
1717
+ const npmLockPath = join14(site.path, "package-lock.json");
1718
+ const yarnLockPath = join14(site.path, "yarn.lock");
1719
+ if (await exists2(pnpmLockPath)) {
1693
1720
  return {
1694
1721
  recipe: "convert-to-pnpm",
1695
1722
  site: label,
@@ -1698,8 +1725,8 @@ async function convertToPnpm(site, opts = {}) {
1698
1725
  notes: "site already has pnpm-lock.yaml"
1699
1726
  };
1700
1727
  }
1701
- const hasNpmLock = await exists(npmLockPath);
1702
- const hasYarnLock = await exists(yarnLockPath);
1728
+ const hasNpmLock = await exists2(npmLockPath);
1729
+ const hasYarnLock = await exists2(yarnLockPath);
1703
1730
  if (!hasNpmLock && !hasYarnLock) {
1704
1731
  return {
1705
1732
  recipe: "convert-to-pnpm",
@@ -1720,7 +1747,7 @@ async function convertToPnpm(site, opts = {}) {
1720
1747
  const sourceLock = hasNpmLock ? "package-lock.json" : "yarn.lock";
1721
1748
  const lockSha = await commit(site.path, `chore(pnpm): remove ${sourceLock}`);
1722
1749
  if (lockSha) shas.push(lockSha);
1723
- const pkgPath = join13(site.path, "package.json");
1750
+ const pkgPath = join14(site.path, "package.json");
1724
1751
  const pkg = await readPackageJson(pkgPath);
1725
1752
  const next = { ...pkg, packageManager: `pnpm@${pnpmVersion}` };
1726
1753
  if (pkg.scripts && typeof pkg.scripts === "object") {
@@ -1734,7 +1761,7 @@ async function convertToPnpm(site, opts = {}) {
1734
1761
  await writePackageJson(pkgPath, next);
1735
1762
  const pkgSha = await commit(site.path, "chore(pnpm): pin packageManager + rewrite npm scripts");
1736
1763
  if (pkgSha) shas.push(pkgSha);
1737
- await rm3(join13(site.path, "node_modules"), { recursive: true, force: true });
1764
+ await rm3(join14(site.path, "node_modules"), { recursive: true, force: true });
1738
1765
  const installResult = await spawn2("pnpm", ["install"], {
1739
1766
  cwd: site.path,
1740
1767
  streaming: true
@@ -1760,17 +1787,17 @@ async function convertToPnpm(site, opts = {}) {
1760
1787
  }
1761
1788
 
1762
1789
  // src/recipes/onboard.ts
1763
- import { stat as stat2 } from "fs/promises";
1764
- import { join as join15 } from "path";
1790
+ import { stat as stat3 } from "fs/promises";
1791
+ import { join as join16 } from "path";
1765
1792
 
1766
1793
  // src/util/self-version.ts
1767
1794
  import { readFileSync } from "fs";
1768
1795
  import { fileURLToPath } from "url";
1769
- import { dirname, join as join14 } from "path";
1796
+ import { dirname, join as join15 } from "path";
1770
1797
  function selfPackageVersion(callerImportMetaUrl) {
1771
1798
  try {
1772
1799
  const here = dirname(fileURLToPath(callerImportMetaUrl));
1773
- const raw = readFileSync(join14(here, "..", "..", "package.json"), "utf-8");
1800
+ const raw = readFileSync(join15(here, "..", "..", "package.json"), "utf-8");
1774
1801
  const pkg = JSON.parse(raw);
1775
1802
  return pkg.version ?? "0.0.0";
1776
1803
  } catch {
@@ -1783,33 +1810,41 @@ function selfCaretRange(callerImportMetaUrl) {
1783
1810
 
1784
1811
  // src/recipes/onboard.ts
1785
1812
  var PACKAGE_NAME = "@reddoorla/maintenance";
1786
- var AUDIT_DEPS = {
1787
- lighthouse: [{ name: "@lhci/cli", version: "^0.15.1" }],
1788
- a11y: [
1789
- { name: "@playwright/test", version: "^1.59.1" },
1790
- { name: "@axe-core/playwright", version: "^4.11.3" }
1791
- ]
1813
+ var AUDIT_DEP_NAMES = {
1814
+ lighthouse: ["@lhci/cli"],
1815
+ a11y: ["@playwright/test", "@axe-core/playwright"]
1792
1816
  };
1793
- async function exists2(path) {
1817
+ var AUDIT_DEPS = Object.fromEntries(
1818
+ Object.entries(AUDIT_DEP_NAMES).map(([audit, names]) => [
1819
+ audit,
1820
+ names.map((name) => {
1821
+ const version = baselineVersions[name];
1822
+ if (!version) {
1823
+ throw new Error(
1824
+ `baseline-versions is missing audit dep "${name}" \u2014 add it to src/configs/baseline-versions.ts`
1825
+ );
1826
+ }
1827
+ return { name, version };
1828
+ })
1829
+ ])
1830
+ );
1831
+ async function exists3(path) {
1794
1832
  try {
1795
- await stat2(path);
1833
+ await stat3(path);
1796
1834
  return true;
1797
1835
  } catch {
1798
1836
  return false;
1799
1837
  }
1800
1838
  }
1801
- function siteLabel11(site) {
1802
- return site.name ?? site.path;
1803
- }
1804
1839
  function isDeclared(pkg, name) {
1805
1840
  return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
1806
1841
  }
1807
1842
  async function onboard(site, opts = {}) {
1808
- const label = siteLabel11(site);
1843
+ const label = siteLabel(site);
1809
1844
  const spawn2 = opts.spawn ?? defaultSpawn;
1810
1845
  const audits = opts.audits ?? ["lighthouse", "a11y"];
1811
1846
  const packageVersion = opts.packageVersion ?? selfCaretRange(import.meta.url);
1812
- if (!await exists2(join15(site.path, "pnpm-lock.yaml"))) {
1847
+ if (!await exists3(join16(site.path, "pnpm-lock.yaml"))) {
1813
1848
  return {
1814
1849
  recipe: "onboard",
1815
1850
  site: label,
@@ -1818,7 +1853,7 @@ async function onboard(site, opts = {}) {
1818
1853
  notes: "no pnpm-lock.yaml at site root \u2014 run convert-to-pnpm first"
1819
1854
  };
1820
1855
  }
1821
- const pkgPath = join15(site.path, "package.json");
1856
+ const pkgPath = join16(site.path, "package.json");
1822
1857
  const pkg = await readPackageJson(pkgPath);
1823
1858
  const toAdd = [];
1824
1859
  if (!isDeclared(pkg, PACKAGE_NAME)) {