@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 +178 -23
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +93 -19
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/{sync-configs-CW3nKS_N.d.ts → sync-configs-DRmSAnfV.d.ts} +1 -1
- package/package.json +1 -1
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((
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
1478
|
+
const match = source.match(SCRIPT_BLOCK2);
|
|
1474
1479
|
if (!match) return source;
|
|
1475
|
-
const
|
|
1476
|
-
const
|
|
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(
|
|
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 = [
|
|
1515
|
-
|
|
1516
|
-
|
|
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
|
-
|
|
1524
|
-
|
|
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
|
|
2114
|
+
import { join as join17 } from "path";
|
|
1974
2115
|
function resolvePackageVersion(fromDir) {
|
|
1975
2116
|
try {
|
|
1976
|
-
const raw = readFileSync(
|
|
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)."
|