@reddoorla/maintenance 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/bin.js CHANGED
@@ -10,7 +10,7 @@ import { resolve as resolve2 } from "path";
10
10
 
11
11
  // src/audits/util/spawn.ts
12
12
  import { spawn } from "child_process";
13
- var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve8, reject) => {
13
+ var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve9, reject) => {
14
14
  const streaming = opts.streaming === true;
15
15
  const child = spawn(cmd, [...args], {
16
16
  cwd: opts.cwd,
@@ -33,7 +33,7 @@ var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve8, reject) => {
33
33
  });
34
34
  child.on("close", (code) => {
35
35
  if (timer) clearTimeout(timer);
36
- resolve8({ code: code ?? -1, stdout, stderr });
36
+ resolve9({ code: code ?? -1, stdout, stderr });
37
37
  });
38
38
  });
39
39
 
@@ -1443,9 +1443,9 @@ function onEventToHandler(source) {
1443
1443
  }
1444
1444
 
1445
1445
  // src/recipes/svelte-5/codemods/dollar-props.ts
1446
- var SCRIPT_TS = /<script\b[^>]*lang=["']ts["'][^>]*>([\s\S]*?)<\/script>/;
1446
+ var SCRIPT_BLOCK2 = /<script\b([^>]*)>([\s\S]*?)<\/script>/;
1447
1447
  var EXPORT_LET = /^\s*export\s+let\s+(\w+)\s*(?::\s*([^=;\n]+))?\s*(?:=\s*([^;\n]+))?;?\s*$/gm;
1448
- function transformScript(scriptBody) {
1448
+ function transformScript(scriptBody, isTs) {
1449
1449
  const props = [];
1450
1450
  const cleaned = scriptBody.replace(
1451
1451
  EXPORT_LET,
@@ -1459,23 +1459,30 @@ function transformScript(scriptBody) {
1459
1459
  }
1460
1460
  );
1461
1461
  if (props.length === 0) return { body: scriptBody, changed: false };
1462
- const typeSig = props.map((p) => {
1463
- const optional = p.defaultExpr ? "?" : "";
1464
- return `${p.name}${optional}: ${p.type ?? "unknown"}`;
1465
- }).join("; ");
1466
1462
  const destructured = props.map((p) => p.defaultExpr ? `${p.name} = ${p.defaultExpr}` : p.name).join(", ");
1467
- const decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;
1463
+ let decl;
1464
+ if (isTs) {
1465
+ const typeSig = props.map((p) => {
1466
+ const optional = p.defaultExpr ? "?" : "";
1467
+ return `${p.name}${optional}: ${p.type ?? "unknown"}`;
1468
+ }).join("; ");
1469
+ decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;
1470
+ } else {
1471
+ decl = ` let { ${destructured} } = $props();`;
1472
+ }
1468
1473
  const next = cleaned.replace(/^(\s*)/, (m) => `${m}${decl}
1469
1474
  `);
1470
1475
  return { body: next, changed: true };
1471
1476
  }
1472
1477
  function exportLetToProps(source) {
1473
- const match = source.match(SCRIPT_TS);
1478
+ const match = source.match(SCRIPT_BLOCK2);
1474
1479
  if (!match) return source;
1475
- const inner = match[1] ?? "";
1476
- const { body, changed } = transformScript(inner);
1480
+ const attrs = match[1] ?? "";
1481
+ const inner = match[2] ?? "";
1482
+ const isTs = /\blang=["']ts["']/.test(attrs);
1483
+ const { body, changed } = transformScript(inner, isTs);
1477
1484
  if (!changed) return source;
1478
- return source.replace(SCRIPT_TS, (full) => full.replace(inner, body));
1485
+ return source.replace(SCRIPT_BLOCK2, (full) => full.replace(inner, body));
1479
1486
  }
1480
1487
 
1481
1488
  // src/recipes/svelte-5/codemods/dollar-restprops.ts
@@ -1508,23 +1515,90 @@ function removeDollarRestProps(source) {
1508
1515
  return next;
1509
1516
  }
1510
1517
 
1518
+ // src/recipes/svelte-5/codemods/state-effect-sync.ts
1519
+ 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*;?/g;
1520
+ function stateEffectSyncToDerived(source) {
1521
+ return source.replace(PATTERN, (full, name, initExpr, effectExpr) => {
1522
+ if (initExpr.trim() !== effectExpr.trim()) return full;
1523
+ return `let ${name} = $derived(${initExpr.trim()});`;
1524
+ });
1525
+ }
1526
+
1527
+ // src/recipes/svelte-5/codemods/dollar-props-class.ts
1528
+ var PROPS_DESTRUCTURE = /let\s*\{([\s\S]*?)\}(\s*:\s*\{([\s\S]*?)\})?\s*=\s*\$props\(\)/;
1529
+ var DOLLAR_PROPS_CLASS = /\$\$props\.class\b/g;
1530
+ var DOLLAR_PROPS_ANY = /\$\$props\b/;
1531
+ var SCRIPT_BLOCK3 = /<script\b[^>]*>[\s\S]*?<\/script>/g;
1532
+ var MIGRATION_TASK = /^<!--\s*@migration-task[\s\S]*?-->\s*\n?/gm;
1533
+ var IDENT = "className";
1534
+ function maskScripts(source) {
1535
+ const blocks = [];
1536
+ const masked = source.replace(SCRIPT_BLOCK3, (m) => {
1537
+ blocks.push(m);
1538
+ return `__SCRIPT_${blocks.length - 1}__`;
1539
+ });
1540
+ return { masked, blocks };
1541
+ }
1542
+ function restoreScripts(masked, blocks) {
1543
+ let out = masked;
1544
+ blocks.forEach((blk, i) => {
1545
+ out = out.replace(`__SCRIPT_${i}__`, blk);
1546
+ });
1547
+ return out;
1548
+ }
1549
+ function dollarPropsClass(source) {
1550
+ const { masked } = maskScripts(source);
1551
+ if (!DOLLAR_PROPS_CLASS.test(masked)) return source;
1552
+ DOLLAR_PROPS_CLASS.lastIndex = 0;
1553
+ if (!PROPS_DESTRUCTURE.test(source)) return source;
1554
+ let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {
1555
+ if (/\bclass\s*:/.test(body)) return full;
1556
+ const cleanBody = body.trim().replace(/,\s*$/, "").trim();
1557
+ const newBody = cleanBody ? `${cleanBody}, class: ${IDENT} = ""` : `class: ${IDENT} = ""`;
1558
+ if (typeAnno) {
1559
+ const cleanType = (typeBody ?? "").trim().replace(/;\s*$/, "").trim();
1560
+ const newType = cleanType ? `${cleanType}; class?: string` : `class?: string`;
1561
+ return `let { ${newBody} }: { ${newType} } = $props()`;
1562
+ }
1563
+ return `let { ${newBody} } = $props()`;
1564
+ });
1565
+ const reMasked = maskScripts(updated);
1566
+ const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS, IDENT);
1567
+ updated = restoreScripts(templateRewritten, reMasked.blocks);
1568
+ const stripped = updated.replace(MIGRATION_TASK, "");
1569
+ if (!DOLLAR_PROPS_ANY.test(stripped)) {
1570
+ updated = stripped;
1571
+ }
1572
+ return updated;
1573
+ }
1574
+
1511
1575
  // src/recipes/svelte-5/step-gotchas.ts
1512
1576
  var SVELTE_GLOBS = ["src/**/*.svelte"];
1513
1577
  var IGNORE2 = ["node_modules/**", ".svelte-kit/**", "build/**"];
1514
- var CODEMODS = [onEventToHandler, exportLetToProps, removeDollarRestProps];
1515
- async function applyGotchaCodemods(cwd) {
1516
- let filesChanged = 0;
1578
+ var CODEMODS = [
1579
+ onEventToHandler,
1580
+ exportLetToProps,
1581
+ removeDollarRestProps,
1582
+ stateEffectSyncToDerived,
1583
+ dollarPropsClass
1584
+ ];
1585
+ async function planGotchaCodemods(cwd) {
1586
+ const changes = [];
1517
1587
  const relPaths = await glob2(SVELTE_GLOBS, { cwd, ignore: IGNORE2, absolute: false });
1518
1588
  for (const rel of relPaths) {
1519
1589
  const path = join11(cwd, rel);
1520
1590
  const before = await readFile10(path, "utf-8");
1521
1591
  const after = CODEMODS.reduce((s, fn) => fn(s), before);
1522
- if (after !== before) {
1523
- await writeFile6(path, after, "utf-8");
1524
- filesChanged += 1;
1525
- }
1592
+ if (after !== before) changes.push({ rel, after });
1593
+ }
1594
+ return changes;
1595
+ }
1596
+ async function applyGotchaCodemods(cwd) {
1597
+ const changes = await planGotchaCodemods(cwd);
1598
+ for (const c of changes) {
1599
+ await writeFile6(join11(cwd, c.rel), c.after, "utf-8");
1526
1600
  }
1527
- return { filesChanged };
1601
+ return { filesChanged: changes.length };
1528
1602
  }
1529
1603
 
1530
1604
  // src/recipes/svelte-5/step-verify.ts
@@ -1968,12 +2042,79 @@ async function runOnboardCommand(site, opts) {
1968
2042
  return { output, code };
1969
2043
  }
1970
2044
 
2045
+ // src/cli/commands/svelte-codemods.ts
2046
+ import { resolve as resolve8 } from "path";
2047
+
2048
+ // src/recipes/svelte-codemods.ts
2049
+ import { writeFile as writeFile8 } from "fs/promises";
2050
+ import { join as join16 } from "path";
2051
+ function siteLabel11(site) {
2052
+ return site.name ?? site.path;
2053
+ }
2054
+ async function svelteCodemods(site) {
2055
+ const label = siteLabel11(site);
2056
+ const changes = await planGotchaCodemods(site.path);
2057
+ if (changes.length === 0) {
2058
+ return {
2059
+ recipe: "svelte-codemods",
2060
+ site: label,
2061
+ status: "noop",
2062
+ commits: [],
2063
+ notes: "no codemod targets matched"
2064
+ };
2065
+ }
2066
+ if (!await isWorkingTreeClean(site.path)) {
2067
+ throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
2068
+ }
2069
+ const branch = branchName("svelte-codemods");
2070
+ await createBranch(site.path, branch);
2071
+ for (const c of changes) {
2072
+ await writeFile8(join16(site.path, c.rel), c.after, "utf-8");
2073
+ }
2074
+ const sha = await commit(
2075
+ site.path,
2076
+ `refactor(svelte5): apply codemods (${changes.length} files)`
2077
+ );
2078
+ return {
2079
+ recipe: "svelte-codemods",
2080
+ site: label,
2081
+ status: "applied",
2082
+ commits: sha ? [sha] : [],
2083
+ notes: `branch: ${branch}`
2084
+ };
2085
+ }
2086
+
2087
+ // src/cli/commands/svelte-codemods.ts
2088
+ function formatResult6(r) {
2089
+ if (r.status === "noop") return `[${r.site}] noop: ${r.notes ?? ""}`;
2090
+ if (r.status === "failed") return `[${r.site}] failed: ${r.notes ?? ""}`;
2091
+ return `[${r.site}] applied: ${r.commits.length} commit(s)
2092
+ ${r.notes ?? ""}`;
2093
+ }
2094
+ async function runSvelteCodemodsCommand(site, opts) {
2095
+ const cwd = opts.cwd ? resolve8(opts.cwd) : process.cwd();
2096
+ let sites = await resolveSites({
2097
+ ...site !== void 0 ? { site } : {},
2098
+ ...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
2099
+ cwd
2100
+ });
2101
+ if (opts.fleet) {
2102
+ const workdir = opts.workdir ?? `${process.env.HOME ?? ""}/.reddoor-maint/sites`;
2103
+ sites = await Promise.all(sites.map((s) => cloneIfNeeded(s, { workdir })));
2104
+ }
2105
+ const results = [];
2106
+ for (const s of sites) results.push(await svelteCodemods(s));
2107
+ const output = results.map(formatResult6).join("\n");
2108
+ const code = results.some((r) => r.status === "failed") ? 1 : 0;
2109
+ return { output, code };
2110
+ }
2111
+
1971
2112
  // src/cli/version.ts
1972
2113
  import { readFileSync } from "fs";
1973
- import { join as join16 } from "path";
2114
+ import { join as join17 } from "path";
1974
2115
  function resolvePackageVersion(fromDir) {
1975
2116
  try {
1976
- const raw = readFileSync(join16(fromDir, "..", "..", "package.json"), "utf-8");
2117
+ const raw = readFileSync(join17(fromDir, "..", "..", "package.json"), "utf-8");
1977
2118
  const pkg = JSON.parse(raw);
1978
2119
  return pkg.version ?? "unknown";
1979
2120
  } catch {
@@ -1995,6 +2136,7 @@ var RECIPE_DESCRIPTIONS = {
1995
2136
  "sync-configs": "Overwrite a site's canonical configs to match @reddoorla/maintenance.",
1996
2137
  "bump-deps": "Bump dependencies and commit the lockfile change.",
1997
2138
  "svelte-4-to-5": "Run the 7-commit Svelte 4 \u2192 5 upgrade recipe.",
2139
+ "svelte-codemods": "Apply Svelte 5 gotcha codemods to an already-migrated site (state_referenced_locally, etc.).",
1998
2140
  "convert-to-pnpm": "Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).",
1999
2141
  onboard: "Install @reddoorla/maintenance + audit deps on a site (preferred first step)."
2000
2142
  };
@@ -2079,6 +2221,19 @@ cli.command(
2079
2221
  }
2080
2222
  }
2081
2223
  );
2224
+ cli.command("svelte-codemods [site]", "Apply Svelte 5 gotcha codemods to an already-migrated site.").option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
2225
+ async (site, opts) => {
2226
+ try {
2227
+ const { output, code } = await runSvelteCodemodsCommand(site, opts);
2228
+ console.log(output);
2229
+ process.exit(code);
2230
+ } catch (err) {
2231
+ const e = err;
2232
+ console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
2233
+ process.exit(e.exitCode ?? 1);
2234
+ }
2235
+ }
2236
+ );
2082
2237
  cli.command(
2083
2238
  "onboard [site]",
2084
2239
  "Install @reddoorla/maintenance + audit deps on a site (run after convert-to-pnpm)."