@reddoorla/maintenance 0.4.0 → 0.6.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.
- package/dist/cli/bin.js +235 -17
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +150 -13
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/recipes/sync-configs.js +124 -5
- package/dist/recipes/sync-configs.js.map +1 -1
- package/dist/{sync-configs-C56tK7Go.d.ts → sync-configs-DRmSAnfV.d.ts} +2 -2
- package/dist/util/git.d.ts +3 -1
- package/dist/util/git.js +10 -0
- package/dist/util/git.js.map +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
|
|
|
@@ -904,6 +904,93 @@ function templatesByName(which) {
|
|
|
904
904
|
return ALL_TEMPLATES.filter((t) => which.includes(t.config));
|
|
905
905
|
}
|
|
906
906
|
|
|
907
|
+
// src/recipes/sync-configs/gitignore.ts
|
|
908
|
+
var MANAGED_MARKER = "# canonical entries from @reddoorla/maintenance sync-configs";
|
|
909
|
+
var CANONICAL_GITIGNORE_ENTRIES = [
|
|
910
|
+
"node_modules/",
|
|
911
|
+
"build/",
|
|
912
|
+
"dist/",
|
|
913
|
+
".svelte-kit/",
|
|
914
|
+
"coverage/",
|
|
915
|
+
".vitest-cache/",
|
|
916
|
+
"playwright-report/",
|
|
917
|
+
"test-results/",
|
|
918
|
+
".lighthouseci/",
|
|
919
|
+
".tsbuildinfo",
|
|
920
|
+
".env",
|
|
921
|
+
".env.*",
|
|
922
|
+
"!.env.example",
|
|
923
|
+
".DS_Store",
|
|
924
|
+
"*.log",
|
|
925
|
+
".vercel/",
|
|
926
|
+
".netlify/"
|
|
927
|
+
];
|
|
928
|
+
function stripLeadingSlash(s) {
|
|
929
|
+
return s.startsWith("/") ? s.slice(1) : s;
|
|
930
|
+
}
|
|
931
|
+
function stripTrailingSlash(s) {
|
|
932
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
933
|
+
}
|
|
934
|
+
function normalizePresence(line) {
|
|
935
|
+
return stripTrailingSlash(stripLeadingSlash(line.trim()));
|
|
936
|
+
}
|
|
937
|
+
function presentSet(existing) {
|
|
938
|
+
const set = /* @__PURE__ */ new Set();
|
|
939
|
+
for (const raw of existing.split(/\r?\n/)) {
|
|
940
|
+
const trimmed = raw.trim();
|
|
941
|
+
if (!trimmed) continue;
|
|
942
|
+
if (trimmed.startsWith("#")) continue;
|
|
943
|
+
set.add(normalizePresence(trimmed));
|
|
944
|
+
}
|
|
945
|
+
return set;
|
|
946
|
+
}
|
|
947
|
+
function mergeGitignore(existing, canonical) {
|
|
948
|
+
if (existing === null) {
|
|
949
|
+
const body = [MANAGED_MARKER, ...canonical].join("\n") + "\n";
|
|
950
|
+
return { content: body, added: [...canonical] };
|
|
951
|
+
}
|
|
952
|
+
const present = presentSet(existing);
|
|
953
|
+
const added = [];
|
|
954
|
+
for (const entry of canonical) {
|
|
955
|
+
const norm = normalizePresence(entry);
|
|
956
|
+
if (!present.has(norm)) {
|
|
957
|
+
added.push(entry);
|
|
958
|
+
present.add(norm);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (added.length === 0) {
|
|
962
|
+
return { content: existing, added: [] };
|
|
963
|
+
}
|
|
964
|
+
let base = existing;
|
|
965
|
+
if (!base.endsWith("\n")) base += "\n";
|
|
966
|
+
const block = ["", MANAGED_MARKER, ...added].join("\n") + "\n";
|
|
967
|
+
return { content: base + block, added };
|
|
968
|
+
}
|
|
969
|
+
function findTrackedArtifacts(tracked, canonical) {
|
|
970
|
+
const dirEntries = [];
|
|
971
|
+
for (const raw of canonical) {
|
|
972
|
+
const t = raw.trim();
|
|
973
|
+
if (!t) continue;
|
|
974
|
+
if (t.startsWith("!")) continue;
|
|
975
|
+
if (/[*?[]/.test(t)) continue;
|
|
976
|
+
const noLead = stripLeadingSlash(t);
|
|
977
|
+
if (!noLead.endsWith("/")) continue;
|
|
978
|
+
const name = stripTrailingSlash(noLead);
|
|
979
|
+
if (!name) continue;
|
|
980
|
+
dirEntries.push(name);
|
|
981
|
+
}
|
|
982
|
+
const matched = [];
|
|
983
|
+
for (const path of tracked) {
|
|
984
|
+
for (const dir of dirEntries) {
|
|
985
|
+
if (path === dir || path.startsWith(dir + "/")) {
|
|
986
|
+
matched.push(path);
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return matched;
|
|
992
|
+
}
|
|
993
|
+
|
|
907
994
|
// src/util/git.ts
|
|
908
995
|
import { execFile } from "child_process";
|
|
909
996
|
import { promisify } from "util";
|
|
@@ -926,6 +1013,14 @@ async function createBranch(cwd, name) {
|
|
|
926
1013
|
async function stageAll(cwd) {
|
|
927
1014
|
await git(cwd, ["add", "-A"]);
|
|
928
1015
|
}
|
|
1016
|
+
async function listTrackedFiles(cwd) {
|
|
1017
|
+
const { stdout } = await git(cwd, ["ls-files"]);
|
|
1018
|
+
return stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
1019
|
+
}
|
|
1020
|
+
async function removeFromIndex(cwd, paths) {
|
|
1021
|
+
if (paths.length === 0) return;
|
|
1022
|
+
await git(cwd, ["rm", "-r", "--cached", "--", ...paths]);
|
|
1023
|
+
}
|
|
929
1024
|
async function commit(cwd, message) {
|
|
930
1025
|
await stageAll(cwd);
|
|
931
1026
|
const { stdout: status } = await git(cwd, ["status", "--porcelain"]);
|
|
@@ -936,6 +1031,7 @@ async function commit(cwd, message) {
|
|
|
936
1031
|
}
|
|
937
1032
|
|
|
938
1033
|
// src/recipes/sync-configs.ts
|
|
1034
|
+
var GITIGNORE_CONFIG = "gitignore";
|
|
939
1035
|
function siteLabel6(site) {
|
|
940
1036
|
return site.name ?? site.path;
|
|
941
1037
|
}
|
|
@@ -946,7 +1042,7 @@ async function readMaybe(path) {
|
|
|
946
1042
|
return null;
|
|
947
1043
|
}
|
|
948
1044
|
}
|
|
949
|
-
async function
|
|
1045
|
+
async function planTemplateDiffs(cwd, templates) {
|
|
950
1046
|
const diffs = [];
|
|
951
1047
|
for (const t of templates) {
|
|
952
1048
|
const existing = await readMaybe(join6(cwd, t.path));
|
|
@@ -954,11 +1050,29 @@ async function planDiffs(cwd, templates) {
|
|
|
954
1050
|
}
|
|
955
1051
|
return diffs;
|
|
956
1052
|
}
|
|
1053
|
+
async function planGitignore(cwd) {
|
|
1054
|
+
const existing = await readMaybe(join6(cwd, ".gitignore"));
|
|
1055
|
+
const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);
|
|
1056
|
+
const tracked = await listTrackedFiles(cwd);
|
|
1057
|
+
const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);
|
|
1058
|
+
if (merge.added.length === 0 && toUntrack.length === 0) return { kind: "noop" };
|
|
1059
|
+
return { kind: "apply", content: merge.content, toUntrack, added: merge.added };
|
|
1060
|
+
}
|
|
1061
|
+
async function applyGitignore(cwd, plan) {
|
|
1062
|
+
await writeFile3(join6(cwd, ".gitignore"), plan.content, "utf-8");
|
|
1063
|
+
if (plan.toUntrack.length > 0) {
|
|
1064
|
+
await removeFromIndex(cwd, plan.toUntrack);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
957
1067
|
async function syncConfigs(site, opts = {}) {
|
|
958
1068
|
const label = siteLabel6(site);
|
|
959
|
-
const
|
|
960
|
-
const
|
|
961
|
-
|
|
1069
|
+
const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);
|
|
1070
|
+
const templateNames = requested.filter((c) => c !== GITIGNORE_CONFIG);
|
|
1071
|
+
const templates = templatesByName(templateNames);
|
|
1072
|
+
const includeGitignore = requested.includes(GITIGNORE_CONFIG);
|
|
1073
|
+
const templateDiffs = await planTemplateDiffs(site.path, templates);
|
|
1074
|
+
const gitignorePlan = includeGitignore ? await planGitignore(site.path) : { kind: "noop" };
|
|
1075
|
+
if (templateDiffs.length === 0 && gitignorePlan.kind === "noop") {
|
|
962
1076
|
return {
|
|
963
1077
|
recipe: "sync-configs",
|
|
964
1078
|
site: label,
|
|
@@ -973,7 +1087,7 @@ async function syncConfigs(site, opts = {}) {
|
|
|
973
1087
|
const branch = branchName("sync-configs");
|
|
974
1088
|
await createBranch(site.path, branch);
|
|
975
1089
|
const shas = [];
|
|
976
|
-
for (const t of
|
|
1090
|
+
for (const t of templateDiffs) {
|
|
977
1091
|
await writeFile3(join6(site.path, t.path), t.contents, "utf-8");
|
|
978
1092
|
const sha = await commit(
|
|
979
1093
|
site.path,
|
|
@@ -981,6 +1095,11 @@ async function syncConfigs(site, opts = {}) {
|
|
|
981
1095
|
);
|
|
982
1096
|
if (sha) shas.push(sha);
|
|
983
1097
|
}
|
|
1098
|
+
if (gitignorePlan.kind === "apply") {
|
|
1099
|
+
await applyGitignore(site.path, gitignorePlan);
|
|
1100
|
+
const sha = await commit(site.path, `chore: sync gitignore from @reddoorla/maintenance`);
|
|
1101
|
+
if (sha) shas.push(sha);
|
|
1102
|
+
}
|
|
984
1103
|
return {
|
|
985
1104
|
recipe: "sync-configs",
|
|
986
1105
|
site: label,
|
|
@@ -1389,23 +1508,41 @@ function removeDollarRestProps(source) {
|
|
|
1389
1508
|
return next;
|
|
1390
1509
|
}
|
|
1391
1510
|
|
|
1511
|
+
// src/recipes/svelte-5/codemods/state-effect-sync.ts
|
|
1512
|
+
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;
|
|
1513
|
+
function stateEffectSyncToDerived(source) {
|
|
1514
|
+
return source.replace(PATTERN, (full, name, initExpr, effectExpr) => {
|
|
1515
|
+
if (initExpr.trim() !== effectExpr.trim()) return full;
|
|
1516
|
+
return `let ${name} = $derived(${initExpr.trim()});`;
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1392
1520
|
// src/recipes/svelte-5/step-gotchas.ts
|
|
1393
1521
|
var SVELTE_GLOBS = ["src/**/*.svelte"];
|
|
1394
1522
|
var IGNORE2 = ["node_modules/**", ".svelte-kit/**", "build/**"];
|
|
1395
|
-
var CODEMODS = [
|
|
1396
|
-
|
|
1397
|
-
|
|
1523
|
+
var CODEMODS = [
|
|
1524
|
+
onEventToHandler,
|
|
1525
|
+
exportLetToProps,
|
|
1526
|
+
removeDollarRestProps,
|
|
1527
|
+
stateEffectSyncToDerived
|
|
1528
|
+
];
|
|
1529
|
+
async function planGotchaCodemods(cwd) {
|
|
1530
|
+
const changes = [];
|
|
1398
1531
|
const relPaths = await glob2(SVELTE_GLOBS, { cwd, ignore: IGNORE2, absolute: false });
|
|
1399
1532
|
for (const rel of relPaths) {
|
|
1400
1533
|
const path = join11(cwd, rel);
|
|
1401
1534
|
const before = await readFile10(path, "utf-8");
|
|
1402
1535
|
const after = CODEMODS.reduce((s, fn) => fn(s), before);
|
|
1403
|
-
if (after !== before) {
|
|
1404
|
-
await writeFile6(path, after, "utf-8");
|
|
1405
|
-
filesChanged += 1;
|
|
1406
|
-
}
|
|
1536
|
+
if (after !== before) changes.push({ rel, after });
|
|
1407
1537
|
}
|
|
1408
|
-
return
|
|
1538
|
+
return changes;
|
|
1539
|
+
}
|
|
1540
|
+
async function applyGotchaCodemods(cwd) {
|
|
1541
|
+
const changes = await planGotchaCodemods(cwd);
|
|
1542
|
+
for (const c of changes) {
|
|
1543
|
+
await writeFile6(join11(cwd, c.rel), c.after, "utf-8");
|
|
1544
|
+
}
|
|
1545
|
+
return { filesChanged: changes.length };
|
|
1409
1546
|
}
|
|
1410
1547
|
|
|
1411
1548
|
// src/recipes/svelte-5/step-verify.ts
|
|
@@ -1849,12 +1986,79 @@ async function runOnboardCommand(site, opts) {
|
|
|
1849
1986
|
return { output, code };
|
|
1850
1987
|
}
|
|
1851
1988
|
|
|
1989
|
+
// src/cli/commands/svelte-codemods.ts
|
|
1990
|
+
import { resolve as resolve8 } from "path";
|
|
1991
|
+
|
|
1992
|
+
// src/recipes/svelte-codemods.ts
|
|
1993
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
1994
|
+
import { join as join16 } from "path";
|
|
1995
|
+
function siteLabel11(site) {
|
|
1996
|
+
return site.name ?? site.path;
|
|
1997
|
+
}
|
|
1998
|
+
async function svelteCodemods(site) {
|
|
1999
|
+
const label = siteLabel11(site);
|
|
2000
|
+
const changes = await planGotchaCodemods(site.path);
|
|
2001
|
+
if (changes.length === 0) {
|
|
2002
|
+
return {
|
|
2003
|
+
recipe: "svelte-codemods",
|
|
2004
|
+
site: label,
|
|
2005
|
+
status: "noop",
|
|
2006
|
+
commits: [],
|
|
2007
|
+
notes: "no codemod targets matched"
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
if (!await isWorkingTreeClean(site.path)) {
|
|
2011
|
+
throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
|
|
2012
|
+
}
|
|
2013
|
+
const branch = branchName("svelte-codemods");
|
|
2014
|
+
await createBranch(site.path, branch);
|
|
2015
|
+
for (const c of changes) {
|
|
2016
|
+
await writeFile8(join16(site.path, c.rel), c.after, "utf-8");
|
|
2017
|
+
}
|
|
2018
|
+
const sha = await commit(
|
|
2019
|
+
site.path,
|
|
2020
|
+
`refactor(svelte5): apply codemods (${changes.length} files)`
|
|
2021
|
+
);
|
|
2022
|
+
return {
|
|
2023
|
+
recipe: "svelte-codemods",
|
|
2024
|
+
site: label,
|
|
2025
|
+
status: "applied",
|
|
2026
|
+
commits: sha ? [sha] : [],
|
|
2027
|
+
notes: `branch: ${branch}`
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// src/cli/commands/svelte-codemods.ts
|
|
2032
|
+
function formatResult6(r) {
|
|
2033
|
+
if (r.status === "noop") return `[${r.site}] noop: ${r.notes ?? ""}`;
|
|
2034
|
+
if (r.status === "failed") return `[${r.site}] failed: ${r.notes ?? ""}`;
|
|
2035
|
+
return `[${r.site}] applied: ${r.commits.length} commit(s)
|
|
2036
|
+
${r.notes ?? ""}`;
|
|
2037
|
+
}
|
|
2038
|
+
async function runSvelteCodemodsCommand(site, opts) {
|
|
2039
|
+
const cwd = opts.cwd ? resolve8(opts.cwd) : process.cwd();
|
|
2040
|
+
let sites = await resolveSites({
|
|
2041
|
+
...site !== void 0 ? { site } : {},
|
|
2042
|
+
...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
|
|
2043
|
+
cwd
|
|
2044
|
+
});
|
|
2045
|
+
if (opts.fleet) {
|
|
2046
|
+
const workdir = opts.workdir ?? `${process.env.HOME ?? ""}/.reddoor-maint/sites`;
|
|
2047
|
+
sites = await Promise.all(sites.map((s) => cloneIfNeeded(s, { workdir })));
|
|
2048
|
+
}
|
|
2049
|
+
const results = [];
|
|
2050
|
+
for (const s of sites) results.push(await svelteCodemods(s));
|
|
2051
|
+
const output = results.map(formatResult6).join("\n");
|
|
2052
|
+
const code = results.some((r) => r.status === "failed") ? 1 : 0;
|
|
2053
|
+
return { output, code };
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1852
2056
|
// src/cli/version.ts
|
|
1853
2057
|
import { readFileSync } from "fs";
|
|
1854
|
-
import { join as
|
|
2058
|
+
import { join as join17 } from "path";
|
|
1855
2059
|
function resolvePackageVersion(fromDir) {
|
|
1856
2060
|
try {
|
|
1857
|
-
const raw = readFileSync(
|
|
2061
|
+
const raw = readFileSync(join17(fromDir, "..", "..", "package.json"), "utf-8");
|
|
1858
2062
|
const pkg = JSON.parse(raw);
|
|
1859
2063
|
return pkg.version ?? "unknown";
|
|
1860
2064
|
} catch {
|
|
@@ -1876,6 +2080,7 @@ var RECIPE_DESCRIPTIONS = {
|
|
|
1876
2080
|
"sync-configs": "Overwrite a site's canonical configs to match @reddoorla/maintenance.",
|
|
1877
2081
|
"bump-deps": "Bump dependencies and commit the lockfile change.",
|
|
1878
2082
|
"svelte-4-to-5": "Run the 7-commit Svelte 4 \u2192 5 upgrade recipe.",
|
|
2083
|
+
"svelte-codemods": "Apply Svelte 5 gotcha codemods to an already-migrated site (state_referenced_locally, etc.).",
|
|
1879
2084
|
"convert-to-pnpm": "Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).",
|
|
1880
2085
|
onboard: "Install @reddoorla/maintenance + audit deps on a site (preferred first step)."
|
|
1881
2086
|
};
|
|
@@ -1960,6 +2165,19 @@ cli.command(
|
|
|
1960
2165
|
}
|
|
1961
2166
|
}
|
|
1962
2167
|
);
|
|
2168
|
+
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(
|
|
2169
|
+
async (site, opts) => {
|
|
2170
|
+
try {
|
|
2171
|
+
const { output, code } = await runSvelteCodemodsCommand(site, opts);
|
|
2172
|
+
console.log(output);
|
|
2173
|
+
process.exit(code);
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
const e = err;
|
|
2176
|
+
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2177
|
+
process.exit(e.exitCode ?? 1);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
);
|
|
1963
2181
|
cli.command(
|
|
1964
2182
|
"onboard [site]",
|
|
1965
2183
|
"Install @reddoorla/maintenance + audit deps on a site (run after convert-to-pnpm)."
|