@reddoorla/maintenance 0.6.0 → 0.6.2

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
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/bin.ts
4
- import { dirname } from "path";
5
- import { fileURLToPath } from "url";
4
+ import { dirname as dirname2 } from "path";
5
+ import { fileURLToPath as fileURLToPath2 } from "url";
6
6
  import { cac } from "cac";
7
7
 
8
8
  // src/cli/commands/audit.ts
@@ -761,6 +761,13 @@ function assertSafeName(name) {
761
761
  throw new Error(`unsafe site name (traversal segment not allowed): ${name}`);
762
762
  }
763
763
  }
764
+ function assertSafeRepoUrl(repoUrl) {
765
+ if (!/^(https?|ssh|git|file):\/\//.test(repoUrl) && !/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:/.test(repoUrl)) {
766
+ throw new Error(
767
+ `unsafe repoUrl: must start with a scheme (https://, ssh://, git://, file://) or use scp-style "user@host:path" (got: ${JSON.stringify(repoUrl)})`
768
+ );
769
+ }
770
+ }
764
771
  async function isNonEmptyDir(path) {
765
772
  try {
766
773
  const s = await stat(path);
@@ -778,13 +785,14 @@ async function cloneIfNeeded(site, opts) {
778
785
  }
779
786
  const name = site.name ?? deriveNameFromRepoUrl(site.repoUrl);
780
787
  assertSafeName(name);
788
+ assertSafeRepoUrl(site.repoUrl);
781
789
  const target = join5(opts.workdir, name);
782
790
  await mkdir(opts.workdir, { recursive: true });
783
791
  if (await isNonEmptyDir(target)) {
784
792
  return { ...site, name, path: target };
785
793
  }
786
794
  const spawn2 = opts.spawn ?? defaultSpawn;
787
- const result = await spawn2("git", ["clone", site.repoUrl, target], {
795
+ const result = await spawn2("git", ["clone", "--", site.repoUrl, target], {
788
796
  cwd: opts.workdir,
789
797
  timeoutMs: 5 * 6e4
790
798
  });
@@ -1443,9 +1451,9 @@ function onEventToHandler(source) {
1443
1451
  }
1444
1452
 
1445
1453
  // src/recipes/svelte-5/codemods/dollar-props.ts
1446
- var SCRIPT_TS = /<script\b[^>]*lang=["']ts["'][^>]*>([\s\S]*?)<\/script>/;
1454
+ var SCRIPT_BLOCK2 = /<script\b([^>]*)>([\s\S]*?)<\/script>/;
1447
1455
  var EXPORT_LET = /^\s*export\s+let\s+(\w+)\s*(?::\s*([^=;\n]+))?\s*(?:=\s*([^;\n]+))?;?\s*$/gm;
1448
- function transformScript(scriptBody) {
1456
+ function transformScript(scriptBody, isTs) {
1449
1457
  const props = [];
1450
1458
  const cleaned = scriptBody.replace(
1451
1459
  EXPORT_LET,
@@ -1459,23 +1467,30 @@ function transformScript(scriptBody) {
1459
1467
  }
1460
1468
  );
1461
1469
  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
1470
  const destructured = props.map((p) => p.defaultExpr ? `${p.name} = ${p.defaultExpr}` : p.name).join(", ");
1467
- const decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;
1471
+ let decl;
1472
+ if (isTs) {
1473
+ const typeSig = props.map((p) => {
1474
+ const optional = p.defaultExpr ? "?" : "";
1475
+ return `${p.name}${optional}: ${p.type ?? "unknown"}`;
1476
+ }).join("; ");
1477
+ decl = ` let { ${destructured} }: { ${typeSig} } = $props();`;
1478
+ } else {
1479
+ decl = ` let { ${destructured} } = $props();`;
1480
+ }
1468
1481
  const next = cleaned.replace(/^(\s*)/, (m) => `${m}${decl}
1469
1482
  `);
1470
1483
  return { body: next, changed: true };
1471
1484
  }
1472
1485
  function exportLetToProps(source) {
1473
- const match = source.match(SCRIPT_TS);
1486
+ const match = source.match(SCRIPT_BLOCK2);
1474
1487
  if (!match) return source;
1475
- const inner = match[1] ?? "";
1476
- const { body, changed } = transformScript(inner);
1488
+ const attrs = match[1] ?? "";
1489
+ const inner = match[2] ?? "";
1490
+ const isTs = /\blang=["']ts["']/.test(attrs);
1491
+ const { body, changed } = transformScript(inner, isTs);
1477
1492
  if (!changed) return source;
1478
- return source.replace(SCRIPT_TS, (full) => full.replace(inner, body));
1493
+ return source.replace(SCRIPT_BLOCK2, (full) => full.replace(inner, body));
1479
1494
  }
1480
1495
 
1481
1496
  // src/recipes/svelte-5/codemods/dollar-restprops.ts
@@ -1517,6 +1532,128 @@ function stateEffectSyncToDerived(source) {
1517
1532
  });
1518
1533
  }
1519
1534
 
1535
+ // src/recipes/svelte-5/codemods/dollar-props-class.ts
1536
+ var PROPS_DESTRUCTURE = /let\s*\{([\s\S]*?)\}(\s*:\s*\{([\s\S]*?)\})?\s*=\s*\$props\(\)/;
1537
+ var DOLLAR_PROPS_CLASS = /\$\$props\.class\b/g;
1538
+ var DOLLAR_PROPS_ANY = /\$\$props\b/;
1539
+ var SCRIPT_BLOCK3 = /<script\b[^>]*>[\s\S]*?<\/script>/g;
1540
+ var MIGRATION_TASK = /^<!--\s*@migration-task[\s\S]*?-->\s*\n?/gm;
1541
+ var IDENT = "className";
1542
+ function maskScripts(source) {
1543
+ const blocks = [];
1544
+ const masked = source.replace(SCRIPT_BLOCK3, (m) => {
1545
+ blocks.push(m);
1546
+ return `__SCRIPT_${blocks.length - 1}__`;
1547
+ });
1548
+ return { masked, blocks };
1549
+ }
1550
+ function restoreScripts(masked, blocks) {
1551
+ let out = masked;
1552
+ blocks.forEach((blk, i) => {
1553
+ out = out.replace(`__SCRIPT_${i}__`, blk);
1554
+ });
1555
+ return out;
1556
+ }
1557
+ function dollarPropsClass(source) {
1558
+ const { masked } = maskScripts(source);
1559
+ if (!DOLLAR_PROPS_CLASS.test(masked)) return source;
1560
+ DOLLAR_PROPS_CLASS.lastIndex = 0;
1561
+ if (!PROPS_DESTRUCTURE.test(source)) return source;
1562
+ let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {
1563
+ if (/\bclass\s*:/.test(body)) return full;
1564
+ const cleanBody = body.trim().replace(/,\s*$/, "").trim();
1565
+ const newBody = cleanBody ? `${cleanBody}, class: ${IDENT} = ""` : `class: ${IDENT} = ""`;
1566
+ if (typeAnno) {
1567
+ const cleanType = (typeBody ?? "").trim().replace(/;\s*$/, "").trim();
1568
+ const newType = cleanType ? `${cleanType}; class?: string` : `class?: string`;
1569
+ return `let { ${newBody} }: { ${newType} } = $props()`;
1570
+ }
1571
+ return `let { ${newBody} } = $props()`;
1572
+ });
1573
+ const reMasked = maskScripts(updated);
1574
+ const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS, IDENT);
1575
+ updated = restoreScripts(templateRewritten, reMasked.blocks);
1576
+ const stripped = updated.replace(MIGRATION_TASK, "");
1577
+ if (!DOLLAR_PROPS_ANY.test(stripped)) {
1578
+ updated = stripped;
1579
+ }
1580
+ return updated;
1581
+ }
1582
+
1583
+ // src/recipes/svelte-5/codemods/legacy-reactive.ts
1584
+ var SCRIPT_BLOCK4 = /<script\b([^>]*)>([\s\S]*?)<\/script>/g;
1585
+ var SIMPLE_REACTIVE = /^([ \t]*)\$:\s*(\w+)\s*=\s*([^;\n]+);?[ \t]*$/gm;
1586
+ var BLOCK_REACTIVE_HEAD = /(^|\n)([ \t]*)\$:\s*\{/g;
1587
+ function findMatchingClose(source, openIdx) {
1588
+ let depth = 0;
1589
+ let i = openIdx;
1590
+ while (i < source.length) {
1591
+ const ch = source[i];
1592
+ if (ch === '"' || ch === "'" || ch === "`") {
1593
+ const closeStr = findStringClose(source, i);
1594
+ if (closeStr === -1) return -1;
1595
+ i = closeStr + 1;
1596
+ continue;
1597
+ }
1598
+ if (ch === "{") depth++;
1599
+ else if (ch === "}") {
1600
+ depth--;
1601
+ if (depth === 0) return i;
1602
+ }
1603
+ i++;
1604
+ }
1605
+ return -1;
1606
+ }
1607
+ function findStringClose(source, openIdx) {
1608
+ const quote = source[openIdx];
1609
+ let i = openIdx + 1;
1610
+ while (i < source.length) {
1611
+ const ch = source[i];
1612
+ if (ch === "\\") {
1613
+ i += 2;
1614
+ continue;
1615
+ }
1616
+ if (ch === quote) return i;
1617
+ i++;
1618
+ }
1619
+ return -1;
1620
+ }
1621
+ function transformBlocks(body) {
1622
+ const out = [];
1623
+ let last = 0;
1624
+ BLOCK_REACTIVE_HEAD.lastIndex = 0;
1625
+ let m;
1626
+ while ((m = BLOCK_REACTIVE_HEAD.exec(body)) !== null) {
1627
+ const leadingNewline = m[1] ?? "";
1628
+ const indent = m[2] ?? "";
1629
+ const headEnd = m.index + m[0].length;
1630
+ const openBraceIdx = headEnd - 1;
1631
+ const closeBraceIdx = findMatchingClose(body, openBraceIdx);
1632
+ if (closeBraceIdx === -1) continue;
1633
+ out.push(body.slice(last, m.index));
1634
+ out.push(leadingNewline);
1635
+ const blockBody = body.slice(openBraceIdx + 1, closeBraceIdx);
1636
+ out.push(`${indent}$effect(() => {${blockBody}});`);
1637
+ last = closeBraceIdx + 1;
1638
+ BLOCK_REACTIVE_HEAD.lastIndex = last;
1639
+ }
1640
+ out.push(body.slice(last));
1641
+ return out.join("");
1642
+ }
1643
+ function transformSimple(body) {
1644
+ return body.replace(SIMPLE_REACTIVE, (_full, indent, name, expr) => {
1645
+ return `${indent}let ${name} = $derived(${expr.trim()});`;
1646
+ });
1647
+ }
1648
+ function legacyReactiveToRunes(source) {
1649
+ return source.replace(SCRIPT_BLOCK4, (full, _attrs, body) => {
1650
+ let next = transformBlocks(body);
1651
+ next = transformSimple(next);
1652
+ if (next === body) return full;
1653
+ return full.replace(body, next);
1654
+ });
1655
+ }
1656
+
1520
1657
  // src/recipes/svelte-5/step-gotchas.ts
1521
1658
  var SVELTE_GLOBS = ["src/**/*.svelte"];
1522
1659
  var IGNORE2 = ["node_modules/**", ".svelte-kit/**", "build/**"];
@@ -1524,7 +1661,9 @@ var CODEMODS = [
1524
1661
  onEventToHandler,
1525
1662
  exportLetToProps,
1526
1663
  removeDollarRestProps,
1527
- stateEffectSyncToDerived
1664
+ stateEffectSyncToDerived,
1665
+ dollarPropsClass,
1666
+ legacyReactiveToRunes
1528
1667
  ];
1529
1668
  async function planGotchaCodemods(cwd) {
1530
1669
  const changes = [];
@@ -1849,9 +1988,28 @@ import { resolve as resolve7 } from "path";
1849
1988
 
1850
1989
  // src/recipes/onboard.ts
1851
1990
  import { stat as stat3 } from "fs/promises";
1852
- import { join as join15 } from "path";
1991
+ import { join as join16 } from "path";
1992
+
1993
+ // src/util/self-version.ts
1994
+ import { readFileSync } from "fs";
1995
+ import { fileURLToPath } from "url";
1996
+ import { dirname, join as join15 } from "path";
1997
+ function selfPackageVersion(callerImportMetaUrl) {
1998
+ try {
1999
+ const here2 = dirname(fileURLToPath(callerImportMetaUrl));
2000
+ const raw = readFileSync(join15(here2, "..", "..", "package.json"), "utf-8");
2001
+ const pkg = JSON.parse(raw);
2002
+ return pkg.version ?? "0.0.0";
2003
+ } catch {
2004
+ return "0.0.0";
2005
+ }
2006
+ }
2007
+ function selfCaretRange(callerImportMetaUrl) {
2008
+ return `^${selfPackageVersion(callerImportMetaUrl)}`;
2009
+ }
2010
+
2011
+ // src/recipes/onboard.ts
1853
2012
  var PACKAGE_NAME = "@reddoorla/maintenance";
1854
- var DEFAULT_PACKAGE_VERSION = "^0.2.0";
1855
2013
  var AUDIT_DEPS = {
1856
2014
  lighthouse: [{ name: "@lhci/cli", version: "^0.15.1" }],
1857
2015
  a11y: [
@@ -1877,8 +2035,8 @@ async function onboard(site, opts = {}) {
1877
2035
  const label = siteLabel10(site);
1878
2036
  const spawn2 = opts.spawn ?? defaultSpawn;
1879
2037
  const audits = opts.audits ?? ["lighthouse", "a11y"];
1880
- const packageVersion = opts.packageVersion ?? DEFAULT_PACKAGE_VERSION;
1881
- if (!await exists2(join15(site.path, "pnpm-lock.yaml"))) {
2038
+ const packageVersion = opts.packageVersion ?? selfCaretRange(import.meta.url);
2039
+ if (!await exists2(join16(site.path, "pnpm-lock.yaml"))) {
1882
2040
  return {
1883
2041
  recipe: "onboard",
1884
2042
  site: label,
@@ -1887,7 +2045,7 @@ async function onboard(site, opts = {}) {
1887
2045
  notes: "no pnpm-lock.yaml at site root \u2014 run convert-to-pnpm first"
1888
2046
  };
1889
2047
  }
1890
- const pkgPath = join15(site.path, "package.json");
2048
+ const pkgPath = join16(site.path, "package.json");
1891
2049
  const pkg = await readPackageJson(pkgPath);
1892
2050
  const toAdd = [];
1893
2051
  if (!isDeclared(pkg, PACKAGE_NAME)) {
@@ -1991,7 +2149,7 @@ import { resolve as resolve8 } from "path";
1991
2149
 
1992
2150
  // src/recipes/svelte-codemods.ts
1993
2151
  import { writeFile as writeFile8 } from "fs/promises";
1994
- import { join as join16 } from "path";
2152
+ import { join as join17 } from "path";
1995
2153
  function siteLabel11(site) {
1996
2154
  return site.name ?? site.path;
1997
2155
  }
@@ -2013,7 +2171,7 @@ async function svelteCodemods(site) {
2013
2171
  const branch = branchName("svelte-codemods");
2014
2172
  await createBranch(site.path, branch);
2015
2173
  for (const c of changes) {
2016
- await writeFile8(join16(site.path, c.rel), c.after, "utf-8");
2174
+ await writeFile8(join17(site.path, c.rel), c.after, "utf-8");
2017
2175
  }
2018
2176
  const sha = await commit(
2019
2177
  site.path,
@@ -2054,11 +2212,11 @@ async function runSvelteCodemodsCommand(site, opts) {
2054
2212
  }
2055
2213
 
2056
2214
  // src/cli/version.ts
2057
- import { readFileSync } from "fs";
2058
- import { join as join17 } from "path";
2215
+ import { readFileSync as readFileSync2 } from "fs";
2216
+ import { join as join18 } from "path";
2059
2217
  function resolvePackageVersion(fromDir) {
2060
2218
  try {
2061
- const raw = readFileSync(join17(fromDir, "..", "..", "package.json"), "utf-8");
2219
+ const raw = readFileSync2(join18(fromDir, "..", "..", "package.json"), "utf-8");
2062
2220
  const pkg = JSON.parse(raw);
2063
2221
  return pkg.version ?? "unknown";
2064
2222
  } catch {
@@ -2067,7 +2225,7 @@ function resolvePackageVersion(fromDir) {
2067
2225
  }
2068
2226
 
2069
2227
  // src/cli/bin.ts
2070
- var here = dirname(fileURLToPath(import.meta.url));
2228
+ var here = dirname2(fileURLToPath2(import.meta.url));
2071
2229
  var version = resolvePackageVersion(here);
2072
2230
  var AUDIT_DESCRIPTIONS = {
2073
2231
  deps: "Diff site package.json against the bundled baseline version map.",