@reddoorla/maintenance 0.6.6 → 0.6.8
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 +356 -468
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +12 -21
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.js +338 -391
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.js +89 -44
- package/dist/recipes/sync-configs.js.map +1 -1
- package/dist/util/git.js +2 -3
- package/dist/util/git.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -41,6 +41,11 @@ var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve9, reject) => {
|
|
|
41
41
|
import { readFile } from "fs/promises";
|
|
42
42
|
import { join } from "path";
|
|
43
43
|
|
|
44
|
+
// src/util/site.ts
|
|
45
|
+
function siteLabel(site) {
|
|
46
|
+
return site.name ?? site.path;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
// src/configs/baseline-versions.ts
|
|
45
50
|
var baselineVersions = {
|
|
46
51
|
// SvelteKit core
|
|
@@ -81,9 +86,6 @@ var baselineVersions = {
|
|
|
81
86
|
};
|
|
82
87
|
|
|
83
88
|
// src/audits/deps.ts
|
|
84
|
-
function siteLabel(site) {
|
|
85
|
-
return site.name ?? site.path;
|
|
86
|
-
}
|
|
87
89
|
function stripCaret(range) {
|
|
88
90
|
return range.replace(/^[\^~]/, "");
|
|
89
91
|
}
|
|
@@ -156,9 +158,6 @@ import { check as prettierCheck, resolveConfig as prettierResolveConfig } from "
|
|
|
156
158
|
import { glob } from "tinyglobby";
|
|
157
159
|
var TARGET_GLOBS = ["**/*.{ts,js,svelte}"];
|
|
158
160
|
var IGNORE = ["node_modules/**", "dist/**", ".svelte-kit/**", "build/**", ".netlify/**"];
|
|
159
|
-
function siteLabel2(site) {
|
|
160
|
-
return site.name ?? site.path;
|
|
161
|
-
}
|
|
162
161
|
async function listFiles(cwd) {
|
|
163
162
|
return glob(TARGET_GLOBS, { cwd, ignore: IGNORE, absolute: false });
|
|
164
163
|
}
|
|
@@ -168,7 +167,7 @@ async function lintAudit(ctx) {
|
|
|
168
167
|
if (!existsSync(configPath)) {
|
|
169
168
|
return {
|
|
170
169
|
audit: "lint",
|
|
171
|
-
site:
|
|
170
|
+
site: siteLabel(site),
|
|
172
171
|
status: "skip",
|
|
173
172
|
summary: "no eslint config at site root"
|
|
174
173
|
};
|
|
@@ -194,7 +193,7 @@ async function lintAudit(ctx) {
|
|
|
194
193
|
const summary = status === "pass" ? `lint clean across ${relFiles.length} files` : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;
|
|
195
194
|
return {
|
|
196
195
|
audit: "lint",
|
|
197
|
-
site:
|
|
196
|
+
site: siteLabel(site),
|
|
198
197
|
status,
|
|
199
198
|
summary,
|
|
200
199
|
details: {
|
|
@@ -207,9 +206,6 @@ async function lintAudit(ctx) {
|
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
// src/audits/security.ts
|
|
210
|
-
function siteLabel3(site) {
|
|
211
|
-
return site.name ?? site.path;
|
|
212
|
-
}
|
|
213
209
|
function classify(v) {
|
|
214
210
|
if (v.critical > 0 || v.high > 0) return "fail";
|
|
215
211
|
if (v.moderate > 0 || v.low > 0) return "warn";
|
|
@@ -295,7 +291,8 @@ async function runAuditTool(spawn2, cmd, args, cwd) {
|
|
|
295
291
|
if (errEnvelope && typeof errEnvelope === "object") {
|
|
296
292
|
return { kind: "error", reason: errEnvelope.code ?? "error envelope returned" };
|
|
297
293
|
}
|
|
298
|
-
|
|
294
|
+
const vulnsMeta = parsed.metadata?.vulnerabilities;
|
|
295
|
+
if (!vulnsMeta || Object.keys(vulnsMeta).length === 0) {
|
|
299
296
|
return { kind: "error", reason: "no metadata.vulnerabilities in output" };
|
|
300
297
|
}
|
|
301
298
|
return { kind: "ok", parsed };
|
|
@@ -303,7 +300,7 @@ async function runAuditTool(spawn2, cmd, args, cwd) {
|
|
|
303
300
|
async function securityAudit(ctx) {
|
|
304
301
|
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
305
302
|
const site = ctx.site;
|
|
306
|
-
const label =
|
|
303
|
+
const label = siteLabel(site);
|
|
307
304
|
let used = "pnpm audit";
|
|
308
305
|
let result = await runAuditTool(spawn2, "pnpm", ["audit", "--json", "--prod"], site.path);
|
|
309
306
|
if (result.kind !== "ok") {
|
|
@@ -384,9 +381,6 @@ var lighthouseConfig = {
|
|
|
384
381
|
};
|
|
385
382
|
|
|
386
383
|
// src/audits/lighthouse.ts
|
|
387
|
-
function siteLabel4(site) {
|
|
388
|
-
return site.name ?? site.path;
|
|
389
|
-
}
|
|
390
384
|
async function readJsonMaybe(path) {
|
|
391
385
|
try {
|
|
392
386
|
const raw = await readFile3(path, "utf-8");
|
|
@@ -424,7 +418,7 @@ function messageForAssertion(a) {
|
|
|
424
418
|
async function lighthouseAudit(ctx) {
|
|
425
419
|
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
426
420
|
const site = ctx.site;
|
|
427
|
-
const label =
|
|
421
|
+
const label = siteLabel(site);
|
|
428
422
|
const configDir = await mkdtemp(join3(tmpdir(), "reddoor-lhci-"));
|
|
429
423
|
const configPath = join3(configDir, "lighthouserc.json");
|
|
430
424
|
await writeFile(configPath, JSON.stringify(lighthouseConfig), "utf-8");
|
|
@@ -522,9 +516,6 @@ var playwrightA11yConfig = defineConfig({
|
|
|
522
516
|
|
|
523
517
|
// src/audits/a11y.ts
|
|
524
518
|
var RESULTS_REL = ".reddoor-a11y/results.json";
|
|
525
|
-
function siteLabel5(site) {
|
|
526
|
-
return site.name ?? site.path;
|
|
527
|
-
}
|
|
528
519
|
async function readJsonMaybe2(path) {
|
|
529
520
|
try {
|
|
530
521
|
const raw = await readFile4(path, "utf-8");
|
|
@@ -582,7 +573,7 @@ test("a11y across configured routes", async ({ page }) => {
|
|
|
582
573
|
async function a11yAudit(ctx) {
|
|
583
574
|
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
584
575
|
const site = ctx.site;
|
|
585
|
-
const label =
|
|
576
|
+
const label = siteLabel(site);
|
|
586
577
|
const specDir = await mkdtemp2(join4(tmpdir2(), "reddoor-a11y-spec-"));
|
|
587
578
|
const specPath = join4(specDir, "a11y.spec.ts");
|
|
588
579
|
await writeFile2(specPath, buildSpec(), "utf-8");
|
|
@@ -1007,9 +998,8 @@ async function git(cwd, args) {
|
|
|
1007
998
|
return exec("git", args, { cwd, env: process.env });
|
|
1008
999
|
}
|
|
1009
1000
|
function branchName(recipe, when = /* @__PURE__ */ new Date()) {
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
return `maint/${recipe}-${trimmed}`;
|
|
1001
|
+
const compact = when.toISOString().replace(/[-:.]/g, "");
|
|
1002
|
+
return `maint/${recipe}-${compact}`;
|
|
1013
1003
|
}
|
|
1014
1004
|
async function isWorkingTreeClean(cwd) {
|
|
1015
1005
|
const { stdout } = await git(cwd, ["status", "--porcelain"]);
|
|
@@ -1038,6 +1028,65 @@ async function commit(cwd, message) {
|
|
|
1038
1028
|
return sha.trim();
|
|
1039
1029
|
}
|
|
1040
1030
|
|
|
1031
|
+
// src/recipes/_with-recipe.ts
|
|
1032
|
+
async function withRecipe(body) {
|
|
1033
|
+
const label = siteLabel(body.site);
|
|
1034
|
+
if (body.checkTreeFirst && !await isWorkingTreeClean(body.site.path)) {
|
|
1035
|
+
throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);
|
|
1036
|
+
}
|
|
1037
|
+
const planned = await body.plan();
|
|
1038
|
+
if (planned.kind === "noop") {
|
|
1039
|
+
return {
|
|
1040
|
+
recipe: body.name,
|
|
1041
|
+
site: label,
|
|
1042
|
+
status: "noop",
|
|
1043
|
+
commits: [],
|
|
1044
|
+
...planned.notes ? { notes: planned.notes } : {}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
if (planned.kind === "failed") {
|
|
1048
|
+
return {
|
|
1049
|
+
recipe: body.name,
|
|
1050
|
+
site: label,
|
|
1051
|
+
status: "failed",
|
|
1052
|
+
commits: [],
|
|
1053
|
+
notes: planned.notes
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
if (!body.checkTreeFirst && !await isWorkingTreeClean(body.site.path)) {
|
|
1057
|
+
throw new Error(`refusing to run: working tree is not clean at ${body.site.path}`);
|
|
1058
|
+
}
|
|
1059
|
+
const branch = branchName(body.name);
|
|
1060
|
+
await createBranch(body.site.path, branch);
|
|
1061
|
+
const shas = [];
|
|
1062
|
+
const result = await body.apply(planned.plan, {
|
|
1063
|
+
cwd: body.site.path,
|
|
1064
|
+
branch,
|
|
1065
|
+
commit: async (msg) => {
|
|
1066
|
+
const sha = await commit(body.site.path, msg);
|
|
1067
|
+
if (sha) shas.push(sha);
|
|
1068
|
+
return sha;
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
if (result.kind === "failed") {
|
|
1072
|
+
return {
|
|
1073
|
+
recipe: body.name,
|
|
1074
|
+
site: label,
|
|
1075
|
+
status: "failed",
|
|
1076
|
+
commits: shas,
|
|
1077
|
+
notes: result.notes
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
const notes = result.notes ? `${result.notes}; branch: ${branch}` : `branch: ${branch}`;
|
|
1081
|
+
return {
|
|
1082
|
+
recipe: body.name,
|
|
1083
|
+
site: label,
|
|
1084
|
+
status: shas.length > 0 ? "applied" : "noop",
|
|
1085
|
+
commits: shas,
|
|
1086
|
+
notes
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1041
1090
|
// src/recipes/sync-configs.ts
|
|
1042
1091
|
var GITIGNORE_CONFIG = "gitignore";
|
|
1043
1092
|
var ALL_CONFIG_NAMES = [
|
|
@@ -1051,9 +1100,6 @@ var ALL_CONFIG_NAMES = [
|
|
|
1051
1100
|
function isConfigName(value) {
|
|
1052
1101
|
return ALL_CONFIG_NAMES.includes(value);
|
|
1053
1102
|
}
|
|
1054
|
-
function siteLabel6(site) {
|
|
1055
|
-
return site.name ?? site.path;
|
|
1056
|
-
}
|
|
1057
1103
|
async function readMaybe(path) {
|
|
1058
1104
|
try {
|
|
1059
1105
|
return await readFile6(path, "utf-8");
|
|
@@ -1084,48 +1130,33 @@ async function applyGitignore(cwd, plan) {
|
|
|
1084
1130
|
}
|
|
1085
1131
|
}
|
|
1086
1132
|
async function syncConfigs(site, opts = {}) {
|
|
1087
|
-
const label = siteLabel6(site);
|
|
1088
1133
|
const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);
|
|
1089
1134
|
const templateNames = requested.filter((c) => c !== GITIGNORE_CONFIG);
|
|
1090
1135
|
const templates = templatesByName(templateNames);
|
|
1091
1136
|
const includeGitignore = requested.includes(GITIGNORE_CONFIG);
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
site:
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
if (sha) shas.push(sha);
|
|
1116
|
-
}
|
|
1117
|
-
if (gitignorePlan.kind === "apply") {
|
|
1118
|
-
await applyGitignore(site.path, gitignorePlan);
|
|
1119
|
-
const sha = await commit(site.path, `chore: sync gitignore from @reddoorla/maintenance`);
|
|
1120
|
-
if (sha) shas.push(sha);
|
|
1121
|
-
}
|
|
1122
|
-
return {
|
|
1123
|
-
recipe: "sync-configs",
|
|
1124
|
-
site: label,
|
|
1125
|
-
status: "applied",
|
|
1126
|
-
commits: shas,
|
|
1127
|
-
notes: `branch: ${branch}`
|
|
1128
|
-
};
|
|
1137
|
+
return withRecipe({
|
|
1138
|
+
name: "sync-configs",
|
|
1139
|
+
site,
|
|
1140
|
+
plan: async () => {
|
|
1141
|
+
const templateDiffs = await planTemplateDiffs(site.path, templates);
|
|
1142
|
+
const gitignorePlan = includeGitignore ? await planGitignore(site.path) : { kind: "noop" };
|
|
1143
|
+
if (templateDiffs.length === 0 && gitignorePlan.kind === "noop") {
|
|
1144
|
+
return { kind: "noop", notes: "all targeted configs already match" };
|
|
1145
|
+
}
|
|
1146
|
+
return { kind: "apply", plan: { templateDiffs, gitignorePlan } };
|
|
1147
|
+
},
|
|
1148
|
+
apply: async ({ templateDiffs, gitignorePlan }, { commit: commit2 }) => {
|
|
1149
|
+
for (const t of templateDiffs) {
|
|
1150
|
+
await writeFile3(join6(site.path, t.path), t.contents, "utf-8");
|
|
1151
|
+
await commit2(`chore: sync ${t.config} config from @reddoorla/maintenance`);
|
|
1152
|
+
}
|
|
1153
|
+
if (gitignorePlan.kind === "apply") {
|
|
1154
|
+
await applyGitignore(site.path, gitignorePlan);
|
|
1155
|
+
await commit2(`chore: sync gitignore from @reddoorla/maintenance`);
|
|
1156
|
+
}
|
|
1157
|
+
return { kind: "ok" };
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1129
1160
|
}
|
|
1130
1161
|
|
|
1131
1162
|
// src/cli/commands/sync-configs.ts
|
|
@@ -1209,9 +1240,6 @@ import { resolve as resolve4 } from "path";
|
|
|
1209
1240
|
// src/recipes/bump-deps.ts
|
|
1210
1241
|
import { stat as stat2 } from "fs/promises";
|
|
1211
1242
|
import { join as join8 } from "path";
|
|
1212
|
-
function siteLabel7(site) {
|
|
1213
|
-
return site.name ?? site.path;
|
|
1214
|
-
}
|
|
1215
1243
|
async function exists(path) {
|
|
1216
1244
|
try {
|
|
1217
1245
|
await stat2(path);
|
|
@@ -1230,62 +1258,51 @@ function upFlagsForGroup(group) {
|
|
|
1230
1258
|
return [];
|
|
1231
1259
|
}
|
|
1232
1260
|
async function bumpDeps(site, opts = {}) {
|
|
1233
|
-
const label = siteLabel7(site);
|
|
1234
1261
|
const group = opts.group ?? "minor";
|
|
1235
1262
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1263
|
+
return withRecipe({
|
|
1264
|
+
name: "bump-deps",
|
|
1265
|
+
site,
|
|
1266
|
+
// pnpm install (in plan) mutates the lockfile, so the clean-tree check
|
|
1267
|
+
// MUST happen first — otherwise a desynced-lockfile resync would silently
|
|
1268
|
+
// land on top of whatever else was in the tree.
|
|
1269
|
+
checkTreeFirst: true,
|
|
1270
|
+
plan: async () => {
|
|
1271
|
+
const hasPnpmLock = await exists(join8(site.path, "pnpm-lock.yaml"));
|
|
1272
|
+
if (!hasPnpmLock) {
|
|
1273
|
+
const hasNpmLock = await exists(join8(site.path, "package-lock.json"));
|
|
1274
|
+
const hasYarnLock = await exists(join8(site.path, "yarn.lock"));
|
|
1275
|
+
if (hasNpmLock || hasYarnLock) {
|
|
1276
|
+
const competing = hasNpmLock ? "package-lock.json" : "yarn.lock";
|
|
1277
|
+
return {
|
|
1278
|
+
kind: "failed",
|
|
1279
|
+
notes: `site has ${competing} but no pnpm-lock.yaml \u2014 run convert-to-pnpm first`
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
1284
|
+
const outdated = await spawn2(
|
|
1285
|
+
"pnpm",
|
|
1286
|
+
["outdated", "--json", ...outdatedFlagsForGroup(group)],
|
|
1287
|
+
{ cwd: site.path }
|
|
1288
|
+
);
|
|
1289
|
+
let parsed;
|
|
1290
|
+
try {
|
|
1291
|
+
parsed = JSON.parse(outdated.stdout || "{}");
|
|
1292
|
+
} catch {
|
|
1293
|
+
parsed = {};
|
|
1294
|
+
}
|
|
1295
|
+
if (Object.keys(parsed).length === 0) {
|
|
1296
|
+
return { kind: "noop", notes: `pnpm outdated reported nothing for group=${group}` };
|
|
1297
|
+
}
|
|
1298
|
+
return { kind: "apply", plan: { group } };
|
|
1299
|
+
},
|
|
1300
|
+
apply: async ({ group: g }, { commit: commit2, cwd }) => {
|
|
1301
|
+
await spawn2("pnpm", ["up", ...upFlagsForGroup(g)], { cwd, streaming: true });
|
|
1302
|
+
await commit2(`chore(deps): bump dependencies (${g})`);
|
|
1303
|
+
return { kind: "ok" };
|
|
1249
1304
|
}
|
|
1250
|
-
}
|
|
1251
|
-
if (!await isWorkingTreeClean(site.path)) {
|
|
1252
|
-
throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
|
|
1253
|
-
}
|
|
1254
|
-
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
1255
|
-
const outdated = await spawn2("pnpm", ["outdated", "--json", ...outdatedFlagsForGroup(group)], {
|
|
1256
|
-
cwd: site.path
|
|
1257
1305
|
});
|
|
1258
|
-
let parsed;
|
|
1259
|
-
try {
|
|
1260
|
-
parsed = JSON.parse(outdated.stdout || "{}");
|
|
1261
|
-
} catch {
|
|
1262
|
-
parsed = {};
|
|
1263
|
-
}
|
|
1264
|
-
const nothingToDo = Object.keys(parsed).length === 0;
|
|
1265
|
-
if (nothingToDo) {
|
|
1266
|
-
return {
|
|
1267
|
-
recipe: "bump-deps",
|
|
1268
|
-
site: label,
|
|
1269
|
-
status: "noop",
|
|
1270
|
-
commits: [],
|
|
1271
|
-
notes: `pnpm outdated reported nothing for group=${group}`
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
const branch = branchName("bump-deps");
|
|
1275
|
-
await createBranch(site.path, branch);
|
|
1276
|
-
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
1277
|
-
cwd: site.path,
|
|
1278
|
-
streaming: true
|
|
1279
|
-
});
|
|
1280
|
-
const sha = await commit(site.path, `chore(deps): bump dependencies (${group})`);
|
|
1281
|
-
const shas = sha ? [sha] : [];
|
|
1282
|
-
return {
|
|
1283
|
-
recipe: "bump-deps",
|
|
1284
|
-
site: label,
|
|
1285
|
-
status: shas.length > 0 ? "applied" : "noop",
|
|
1286
|
-
commits: shas,
|
|
1287
|
-
notes: `branch: ${branch}`
|
|
1288
|
-
};
|
|
1289
1306
|
}
|
|
1290
1307
|
|
|
1291
1308
|
// src/cli/commands/bump-deps.ts
|
|
@@ -1507,6 +1524,34 @@ import { glob as glob2 } from "tinyglobby";
|
|
|
1507
1524
|
// src/recipes/svelte-5/codemods/on-event-to-handler.ts
|
|
1508
1525
|
var SCRIPT_BLOCK = /<script\b[^>]*>[\s\S]*?<\/script>/g;
|
|
1509
1526
|
var SIMPLE_ON_EVENT = /\bon:([a-z]+)(?=\s*=)/g;
|
|
1527
|
+
var MODIFIER_EVENT = /\bon:[a-z]+\|[a-zA-Z]+(?=\s*=)/g;
|
|
1528
|
+
function flagEventModifiers(source) {
|
|
1529
|
+
const insertions = [];
|
|
1530
|
+
let m;
|
|
1531
|
+
MODIFIER_EVENT.lastIndex = 0;
|
|
1532
|
+
while ((m = MODIFIER_EVENT.exec(source)) !== null) {
|
|
1533
|
+
const tagStart = source.lastIndexOf("<", m.index);
|
|
1534
|
+
if (tagStart === -1) continue;
|
|
1535
|
+
const prevLineEnd = tagStart - 1;
|
|
1536
|
+
if (prevLineEnd >= 0) {
|
|
1537
|
+
const prevLineStart = source.lastIndexOf("\n", prevLineEnd - 1) + 1;
|
|
1538
|
+
const prevLine = source.slice(prevLineStart, prevLineEnd + 1);
|
|
1539
|
+
if (/<!--\s*@migration-task/.test(prevLine)) continue;
|
|
1540
|
+
}
|
|
1541
|
+
const lineStart = source.lastIndexOf("\n", tagStart - 1) + 1;
|
|
1542
|
+
const indent = source.slice(lineStart, tagStart);
|
|
1543
|
+
const safeIndent = /^[ \t]*$/.test(indent) ? indent : "";
|
|
1544
|
+
insertions.push({ tagStart, indent: safeIndent, modifier: m[0] });
|
|
1545
|
+
}
|
|
1546
|
+
let out = source;
|
|
1547
|
+
for (let i = insertions.length - 1; i >= 0; i--) {
|
|
1548
|
+
const { tagStart, indent, modifier } = insertions[i];
|
|
1549
|
+
const comment = `<!-- @migration-task: Svelte 5 removed event modifier syntax (\`${modifier}\`). Rewrite inline, e.g. onclick={(e) => { e.preventDefault(); ... }}. -->
|
|
1550
|
+
${indent}`;
|
|
1551
|
+
out = out.slice(0, tagStart) + comment + out.slice(tagStart);
|
|
1552
|
+
}
|
|
1553
|
+
return out;
|
|
1554
|
+
}
|
|
1510
1555
|
function onEventToHandler(source) {
|
|
1511
1556
|
const masked = [];
|
|
1512
1557
|
const placeholder = (i) => ` SCRIPT_${i} `;
|
|
@@ -1514,8 +1559,9 @@ function onEventToHandler(source) {
|
|
|
1514
1559
|
masked.push(match);
|
|
1515
1560
|
return placeholder(masked.length - 1);
|
|
1516
1561
|
});
|
|
1517
|
-
|
|
1518
|
-
|
|
1562
|
+
let processed = intermediate.replace(SIMPLE_ON_EVENT, (_full, name) => `on${name}`);
|
|
1563
|
+
processed = flagEventModifiers(processed);
|
|
1564
|
+
let out = processed;
|
|
1519
1565
|
masked.forEach((blk, i) => {
|
|
1520
1566
|
out = out.replace(placeholder(i), blk);
|
|
1521
1567
|
});
|
|
@@ -1565,6 +1611,22 @@ function exportLetToProps(source) {
|
|
|
1565
1611
|
return source.replace(SCRIPT_BLOCK2, (full) => full.replace(inner, body));
|
|
1566
1612
|
}
|
|
1567
1613
|
|
|
1614
|
+
// src/util/svelte-source.ts
|
|
1615
|
+
function findStringEnd(source, openIdx) {
|
|
1616
|
+
const quote = source[openIdx];
|
|
1617
|
+
let i = openIdx + 1;
|
|
1618
|
+
while (i < source.length) {
|
|
1619
|
+
const ch = source[i];
|
|
1620
|
+
if (ch === "\\") {
|
|
1621
|
+
i += 2;
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
if (ch === quote) return i;
|
|
1625
|
+
i++;
|
|
1626
|
+
}
|
|
1627
|
+
return -1;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1568
1630
|
// src/recipes/svelte-5/codemods/dollar-restprops.ts
|
|
1569
1631
|
function removeInterfaceBlock(source) {
|
|
1570
1632
|
const re = /^\s*interface\s+\$\$Props\s*\{/m;
|
|
@@ -1588,20 +1650,6 @@ function removeInterfaceBlock(source) {
|
|
|
1588
1650
|
out = out.slice(0, match.index) + out.slice(endIdx);
|
|
1589
1651
|
}
|
|
1590
1652
|
}
|
|
1591
|
-
function findStringEnd(source, openIdx) {
|
|
1592
|
-
const quote = source[openIdx];
|
|
1593
|
-
let i = openIdx + 1;
|
|
1594
|
-
while (i < source.length) {
|
|
1595
|
-
const ch = source[i];
|
|
1596
|
-
if (ch === "\\") {
|
|
1597
|
-
i += 2;
|
|
1598
|
-
continue;
|
|
1599
|
-
}
|
|
1600
|
-
if (ch === quote) return i;
|
|
1601
|
-
i++;
|
|
1602
|
-
}
|
|
1603
|
-
return -1;
|
|
1604
|
-
}
|
|
1605
1653
|
function maskStringLiterals(source) {
|
|
1606
1654
|
const strings = [];
|
|
1607
1655
|
let out = "";
|
|
@@ -1678,7 +1726,8 @@ function stateEffectSyncToDerived(source) {
|
|
|
1678
1726
|
|
|
1679
1727
|
// src/recipes/svelte-5/codemods/dollar-props-class.ts
|
|
1680
1728
|
var PROPS_DESTRUCTURE = /let\s*\{([\s\S]*?)\}(\s*:\s*\{([\s\S]*?)\})?\s*=\s*\$props\(\)/;
|
|
1681
|
-
var
|
|
1729
|
+
var HAS_DOLLAR_PROPS_CLASS = /\$\$props\.class\b/;
|
|
1730
|
+
var DOLLAR_PROPS_CLASS_GLOBAL = /\$\$props\.class\b/g;
|
|
1682
1731
|
var DOLLAR_PROPS_ANY = /\$\$props\b/;
|
|
1683
1732
|
var SCRIPT_BLOCK4 = /<script\b[^>]*>[\s\S]*?<\/script>/g;
|
|
1684
1733
|
var MIGRATION_TASK = /^<!--\s*@migration-task[\s\S]*?-->\s*\n?/gm;
|
|
@@ -1700,8 +1749,7 @@ function restoreScripts(masked, blocks) {
|
|
|
1700
1749
|
}
|
|
1701
1750
|
function dollarPropsClass(source) {
|
|
1702
1751
|
const { masked } = maskScripts(source);
|
|
1703
|
-
if (!
|
|
1704
|
-
DOLLAR_PROPS_CLASS.lastIndex = 0;
|
|
1752
|
+
if (!HAS_DOLLAR_PROPS_CLASS.test(masked)) return source;
|
|
1705
1753
|
if (!PROPS_DESTRUCTURE.test(source)) return source;
|
|
1706
1754
|
let updated = source.replace(PROPS_DESTRUCTURE, (full, body, typeAnno, typeBody) => {
|
|
1707
1755
|
if (/\bclass\s*:/.test(body)) return full;
|
|
@@ -1715,7 +1763,7 @@ function dollarPropsClass(source) {
|
|
|
1715
1763
|
return `let { ${newBody} } = $props()`;
|
|
1716
1764
|
});
|
|
1717
1765
|
const reMasked = maskScripts(updated);
|
|
1718
|
-
const templateRewritten = reMasked.masked.replace(
|
|
1766
|
+
const templateRewritten = reMasked.masked.replace(DOLLAR_PROPS_CLASS_GLOBAL, IDENT);
|
|
1719
1767
|
updated = restoreScripts(templateRewritten, reMasked.blocks);
|
|
1720
1768
|
const stripped = updated.replace(MIGRATION_TASK, "");
|
|
1721
1769
|
if (!DOLLAR_PROPS_ANY.test(stripped)) {
|
|
@@ -1734,7 +1782,7 @@ function findMatchingClose(source, openIdx) {
|
|
|
1734
1782
|
while (i < source.length) {
|
|
1735
1783
|
const ch = source[i];
|
|
1736
1784
|
if (ch === '"' || ch === "'" || ch === "`") {
|
|
1737
|
-
const closeStr =
|
|
1785
|
+
const closeStr = findStringEnd(source, i);
|
|
1738
1786
|
if (closeStr === -1) return -1;
|
|
1739
1787
|
i = closeStr + 1;
|
|
1740
1788
|
continue;
|
|
@@ -1748,20 +1796,6 @@ function findMatchingClose(source, openIdx) {
|
|
|
1748
1796
|
}
|
|
1749
1797
|
return -1;
|
|
1750
1798
|
}
|
|
1751
|
-
function findStringClose(source, openIdx) {
|
|
1752
|
-
const quote = source[openIdx];
|
|
1753
|
-
let i = openIdx + 1;
|
|
1754
|
-
while (i < source.length) {
|
|
1755
|
-
const ch = source[i];
|
|
1756
|
-
if (ch === "\\") {
|
|
1757
|
-
i += 2;
|
|
1758
|
-
continue;
|
|
1759
|
-
}
|
|
1760
|
-
if (ch === quote) return i;
|
|
1761
|
-
i++;
|
|
1762
|
-
}
|
|
1763
|
-
return -1;
|
|
1764
|
-
}
|
|
1765
1799
|
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.";
|
|
1766
1800
|
function transformBlocks(body) {
|
|
1767
1801
|
const out = [];
|
|
@@ -1873,9 +1907,6 @@ async function writeMigrationSummary(input) {
|
|
|
1873
1907
|
}
|
|
1874
1908
|
|
|
1875
1909
|
// src/recipes/svelte-5/index.ts
|
|
1876
|
-
function siteLabel8(site) {
|
|
1877
|
-
return site.name ?? site.path;
|
|
1878
|
-
}
|
|
1879
1910
|
async function alreadyOnSvelte5(cwd) {
|
|
1880
1911
|
try {
|
|
1881
1912
|
const pkg = await readPackageJson(join14(cwd, "package.json"));
|
|
@@ -1886,72 +1917,49 @@ async function alreadyOnSvelte5(cwd) {
|
|
|
1886
1917
|
}
|
|
1887
1918
|
}
|
|
1888
1919
|
async function upgradeSvelte4to5(site, opts = {}) {
|
|
1889
|
-
const label = siteLabel8(site);
|
|
1890
1920
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
site.path,
|
|
1933
|
-
`refactor(svelte5): apply gotcha codemods (${codemods.filesChanged} files)`
|
|
1934
|
-
);
|
|
1935
|
-
if (sha) shas.push(sha);
|
|
1936
|
-
}
|
|
1937
|
-
await verifyMigration(site.path, spawn2);
|
|
1938
|
-
const verifySha = await commit(site.path, "chore(svelte5): pnpm install + check");
|
|
1939
|
-
if (verifySha) shas.push(verifySha);
|
|
1940
|
-
await writeMigrationSummary({
|
|
1941
|
-
cwd: site.path,
|
|
1942
|
-
filesChangedByCodemods: codemods.filesChanged,
|
|
1943
|
-
svelteMigrateRan: migrate.ran,
|
|
1944
|
-
tailwindUpgraded: tw.ran
|
|
1921
|
+
return withRecipe({
|
|
1922
|
+
name: "svelte-4-to-5",
|
|
1923
|
+
site,
|
|
1924
|
+
plan: async () => {
|
|
1925
|
+
if (await alreadyOnSvelte5(site.path)) {
|
|
1926
|
+
return { kind: "noop", notes: "site already declares svelte ^5.x" };
|
|
1927
|
+
}
|
|
1928
|
+
return { kind: "apply", plan: true };
|
|
1929
|
+
},
|
|
1930
|
+
apply: async (_plan, { commit: commit2, cwd }) => {
|
|
1931
|
+
const bumped = await bumpToSvelte5Versions(cwd);
|
|
1932
|
+
if (bumped) {
|
|
1933
|
+
await commit2("chore(svelte5): bump svelte/kit/vite/vite-plugin-svelte");
|
|
1934
|
+
}
|
|
1935
|
+
const configChanged = await migrateSvelteConfig(cwd);
|
|
1936
|
+
if (configChanged) {
|
|
1937
|
+
await commit2("refactor(svelte5): migrate svelte.config.js (drop vitePreprocess)");
|
|
1938
|
+
}
|
|
1939
|
+
const migrate = await runSvelteMigrate(cwd, spawn2);
|
|
1940
|
+
if (migrate.ran) {
|
|
1941
|
+
await commit2("refactor(svelte5): run official svelte-migrate codemod");
|
|
1942
|
+
}
|
|
1943
|
+
const tw = await upgradeTailwind(cwd, spawn2);
|
|
1944
|
+
if (tw.ran) {
|
|
1945
|
+
await commit2("chore(svelte5): tailwindcss 3 \u2192 4 upgrade");
|
|
1946
|
+
}
|
|
1947
|
+
const codemods = await applyGotchaCodemods(cwd);
|
|
1948
|
+
if (codemods.filesChanged > 0) {
|
|
1949
|
+
await commit2(`refactor(svelte5): apply gotcha codemods (${codemods.filesChanged} files)`);
|
|
1950
|
+
}
|
|
1951
|
+
await verifyMigration(cwd, spawn2);
|
|
1952
|
+
await commit2("chore(svelte5): pnpm install + check");
|
|
1953
|
+
await writeMigrationSummary({
|
|
1954
|
+
cwd,
|
|
1955
|
+
filesChangedByCodemods: codemods.filesChanged,
|
|
1956
|
+
svelteMigrateRan: migrate.ran,
|
|
1957
|
+
tailwindUpgraded: tw.ran
|
|
1958
|
+
});
|
|
1959
|
+
await commit2("docs(svelte5): add MIGRATION_SVELTE_5.md summary");
|
|
1960
|
+
return { kind: "ok" };
|
|
1961
|
+
}
|
|
1945
1962
|
});
|
|
1946
|
-
const summarySha = await commit(site.path, "docs(svelte5): add MIGRATION_SVELTE_5.md summary");
|
|
1947
|
-
if (summarySha) shas.push(summarySha);
|
|
1948
|
-
return {
|
|
1949
|
-
recipe: "svelte-4-to-5",
|
|
1950
|
-
site: label,
|
|
1951
|
-
status: shas.length > 0 ? "applied" : "noop",
|
|
1952
|
-
commits: shas,
|
|
1953
|
-
notes: `branch: ${branch}`
|
|
1954
|
-
};
|
|
1955
1963
|
}
|
|
1956
1964
|
|
|
1957
1965
|
// src/cli/commands/upgrade.ts
|
|
@@ -2026,84 +2034,56 @@ async function exists2(path) {
|
|
|
2026
2034
|
return false;
|
|
2027
2035
|
}
|
|
2028
2036
|
}
|
|
2029
|
-
function siteLabel9(site) {
|
|
2030
|
-
return site.name ?? site.path;
|
|
2031
|
-
}
|
|
2032
2037
|
async function convertToPnpm(site, opts = {}) {
|
|
2033
|
-
const label = siteLabel9(site);
|
|
2034
2038
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
2035
2039
|
const pnpmVersion = opts.pnpmVersion ?? DEFAULT_PNPM_VERSION;
|
|
2036
2040
|
const pnpmLockPath = join15(site.path, "pnpm-lock.yaml");
|
|
2037
2041
|
const npmLockPath = join15(site.path, "package-lock.json");
|
|
2038
2042
|
const yarnLockPath = join15(site.path, "yarn.lock");
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2043
|
+
return withRecipe({
|
|
2044
|
+
name: "convert-to-pnpm",
|
|
2045
|
+
site,
|
|
2046
|
+
plan: async () => {
|
|
2047
|
+
if (await exists2(pnpmLockPath)) {
|
|
2048
|
+
return { kind: "noop", notes: "site already has pnpm-lock.yaml" };
|
|
2049
|
+
}
|
|
2050
|
+
const hasNpmLock = await exists2(npmLockPath);
|
|
2051
|
+
const hasYarnLock = await exists2(yarnLockPath);
|
|
2052
|
+
if (!hasNpmLock && !hasYarnLock) {
|
|
2053
|
+
return {
|
|
2054
|
+
kind: "noop",
|
|
2055
|
+
notes: "no convertible lockfile (package-lock.json or yarn.lock) at site root"
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
return { kind: "apply", plan: { hasNpmLock, hasYarnLock } };
|
|
2059
|
+
},
|
|
2060
|
+
apply: async ({ hasNpmLock, hasYarnLock }, { commit: commit2, cwd }) => {
|
|
2061
|
+
if (hasNpmLock) await rm3(npmLockPath, { force: true });
|
|
2062
|
+
if (hasYarnLock) await rm3(yarnLockPath, { force: true });
|
|
2063
|
+
const sourceLock = hasNpmLock ? "package-lock.json" : "yarn.lock";
|
|
2064
|
+
await commit2(`chore(pnpm): remove ${sourceLock}`);
|
|
2065
|
+
const pkgPath = join15(cwd, "package.json");
|
|
2066
|
+
const pkg = await readPackageJson(pkgPath);
|
|
2067
|
+
const next = { ...pkg, packageManager: `pnpm@${pnpmVersion}` };
|
|
2068
|
+
if (pkg.scripts && typeof pkg.scripts === "object") {
|
|
2069
|
+
const { scripts: rewritten, changedCount } = rewriteScriptsForPnpm(
|
|
2070
|
+
pkg.scripts
|
|
2071
|
+
);
|
|
2072
|
+
if (changedCount > 0) {
|
|
2073
|
+
next.scripts = rewritten;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
await writePackageJson(pkgPath, next);
|
|
2077
|
+
await commit2("chore(pnpm): pin packageManager + rewrite npm scripts");
|
|
2078
|
+
await rm3(join15(cwd, "node_modules"), { recursive: true, force: true });
|
|
2079
|
+
const installResult = await spawn2("pnpm", ["install"], { cwd, streaming: true });
|
|
2080
|
+
if (installResult.code !== 0) {
|
|
2081
|
+
return { kind: "failed", notes: `pnpm install failed (exit ${installResult.code})` };
|
|
2082
|
+
}
|
|
2083
|
+
await commit2("chore(pnpm): add pnpm-lock.yaml");
|
|
2084
|
+
return { kind: "ok" };
|
|
2079
2085
|
}
|
|
2080
|
-
}
|
|
2081
|
-
await writePackageJson(pkgPath, next);
|
|
2082
|
-
const pkgSha = await commit(site.path, "chore(pnpm): pin packageManager + rewrite npm scripts");
|
|
2083
|
-
if (pkgSha) shas.push(pkgSha);
|
|
2084
|
-
await rm3(join15(site.path, "node_modules"), { recursive: true, force: true });
|
|
2085
|
-
const installResult = await spawn2("pnpm", ["install"], {
|
|
2086
|
-
cwd: site.path,
|
|
2087
|
-
streaming: true
|
|
2088
2086
|
});
|
|
2089
|
-
if (installResult.code !== 0) {
|
|
2090
|
-
return {
|
|
2091
|
-
recipe: "convert-to-pnpm",
|
|
2092
|
-
site: label,
|
|
2093
|
-
status: "failed",
|
|
2094
|
-
commits: shas,
|
|
2095
|
-
notes: `pnpm install failed (exit ${installResult.code}). branch ${branch} left for inspection.`
|
|
2096
|
-
};
|
|
2097
|
-
}
|
|
2098
|
-
const installSha = await commit(site.path, "chore(pnpm): add pnpm-lock.yaml");
|
|
2099
|
-
if (installSha) shas.push(installSha);
|
|
2100
|
-
return {
|
|
2101
|
-
recipe: "convert-to-pnpm",
|
|
2102
|
-
site: label,
|
|
2103
|
-
status: "applied",
|
|
2104
|
-
commits: shas,
|
|
2105
|
-
notes: `branch: ${branch}`
|
|
2106
|
-
};
|
|
2107
2087
|
}
|
|
2108
2088
|
|
|
2109
2089
|
// src/cli/commands/convert-to-pnpm.ts
|
|
@@ -2184,81 +2164,63 @@ async function exists3(path) {
|
|
|
2184
2164
|
return false;
|
|
2185
2165
|
}
|
|
2186
2166
|
}
|
|
2187
|
-
function siteLabel10(site) {
|
|
2188
|
-
return site.name ?? site.path;
|
|
2189
|
-
}
|
|
2190
2167
|
function isDeclared(pkg, name) {
|
|
2191
2168
|
return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
|
|
2192
2169
|
}
|
|
2193
2170
|
async function onboard(site, opts = {}) {
|
|
2194
|
-
const label = siteLabel10(site);
|
|
2195
2171
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
2196
2172
|
const audits = opts.audits ?? ["lighthouse", "a11y"];
|
|
2197
2173
|
const packageVersion = opts.packageVersion ?? selfCaretRange(import.meta.url);
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2174
|
+
return withRecipe({
|
|
2175
|
+
name: "onboard",
|
|
2176
|
+
site,
|
|
2177
|
+
plan: async () => {
|
|
2178
|
+
if (!await exists3(join17(site.path, "pnpm-lock.yaml"))) {
|
|
2179
|
+
return {
|
|
2180
|
+
kind: "failed",
|
|
2181
|
+
notes: "no pnpm-lock.yaml at site root \u2014 run convert-to-pnpm first"
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
const pkgPath = join17(site.path, "package.json");
|
|
2185
|
+
const pkg = await readPackageJson(pkgPath);
|
|
2186
|
+
const toAdd = [];
|
|
2187
|
+
if (!isDeclared(pkg, PACKAGE_NAME)) {
|
|
2188
|
+
toAdd.push({ name: PACKAGE_NAME, version: packageVersion });
|
|
2189
|
+
}
|
|
2190
|
+
for (const audit of audits) {
|
|
2191
|
+
for (const dep of AUDIT_DEPS[audit]) {
|
|
2192
|
+
if (!isDeclared(pkg, dep.name)) toAdd.push(dep);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
if (toAdd.length === 0) {
|
|
2196
|
+
return {
|
|
2197
|
+
kind: "noop",
|
|
2198
|
+
notes: `site already has ${PACKAGE_NAME} and audit deps (${audits.join("+")})`
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
return { kind: "apply", plan: { pkg, toAdd } };
|
|
2202
|
+
},
|
|
2203
|
+
apply: async ({ pkg, toAdd }, { commit: commit2, cwd }) => {
|
|
2204
|
+
const pkgPath = join17(cwd, "package.json");
|
|
2205
|
+
let next = pkg;
|
|
2206
|
+
for (const dep of toAdd) {
|
|
2207
|
+
next = bumpDep(next, dep.name, dep.version);
|
|
2208
|
+
}
|
|
2209
|
+
await writePackageJson(pkgPath, next);
|
|
2210
|
+
const installResult = await spawn2("pnpm", ["install"], { cwd, streaming: true });
|
|
2211
|
+
if (installResult.code !== 0) {
|
|
2212
|
+
return {
|
|
2213
|
+
kind: "failed",
|
|
2214
|
+
notes: `pnpm install failed (exit ${installResult.code})`
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
await commit2(`chore(reddoor): onboard with ${PACKAGE_NAME} ${packageVersion}`);
|
|
2218
|
+
return {
|
|
2219
|
+
kind: "ok",
|
|
2220
|
+
notes: `Added ${toAdd.length} dep(s): ${toAdd.map((d) => d.name).join(", ")}`
|
|
2221
|
+
};
|
|
2216
2222
|
}
|
|
2217
|
-
}
|
|
2218
|
-
if (toAdd.length === 0) {
|
|
2219
|
-
return {
|
|
2220
|
-
recipe: "onboard",
|
|
2221
|
-
site: label,
|
|
2222
|
-
status: "noop",
|
|
2223
|
-
commits: [],
|
|
2224
|
-
notes: `site already has ${PACKAGE_NAME} and audit deps (${audits.join("+")})`
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
if (!await isWorkingTreeClean(site.path)) {
|
|
2228
|
-
throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
|
|
2229
|
-
}
|
|
2230
|
-
const branch = branchName("onboard");
|
|
2231
|
-
await createBranch(site.path, branch);
|
|
2232
|
-
let next = pkg;
|
|
2233
|
-
for (const dep of toAdd) {
|
|
2234
|
-
next = bumpDep(next, dep.name, dep.version);
|
|
2235
|
-
}
|
|
2236
|
-
await writePackageJson(pkgPath, next);
|
|
2237
|
-
const installResult = await spawn2("pnpm", ["install"], {
|
|
2238
|
-
cwd: site.path,
|
|
2239
|
-
streaming: true
|
|
2240
2223
|
});
|
|
2241
|
-
if (installResult.code !== 0) {
|
|
2242
|
-
return {
|
|
2243
|
-
recipe: "onboard",
|
|
2244
|
-
site: label,
|
|
2245
|
-
status: "failed",
|
|
2246
|
-
commits: [],
|
|
2247
|
-
notes: `pnpm install failed (exit ${installResult.code}). branch ${branch} left for inspection.`
|
|
2248
|
-
};
|
|
2249
|
-
}
|
|
2250
|
-
const sha = await commit(
|
|
2251
|
-
site.path,
|
|
2252
|
-
`chore(reddoor): onboard with ${PACKAGE_NAME} ${packageVersion}`
|
|
2253
|
-
);
|
|
2254
|
-
const shas = sha ? [sha] : [];
|
|
2255
|
-
return {
|
|
2256
|
-
recipe: "onboard",
|
|
2257
|
-
site: label,
|
|
2258
|
-
status: "applied",
|
|
2259
|
-
commits: shas,
|
|
2260
|
-
notes: `branch: ${branch}. Added ${toAdd.length} dep(s): ${toAdd.map((d) => d.name).join(", ")}`
|
|
2261
|
-
};
|
|
2262
2224
|
}
|
|
2263
2225
|
|
|
2264
2226
|
// src/cli/commands/onboard.ts
|
|
@@ -2309,40 +2271,25 @@ import { resolve as resolve8 } from "path";
|
|
|
2309
2271
|
// src/recipes/svelte-codemods.ts
|
|
2310
2272
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
2311
2273
|
import { join as join18 } from "path";
|
|
2312
|
-
function siteLabel11(site) {
|
|
2313
|
-
return site.name ?? site.path;
|
|
2314
|
-
}
|
|
2315
2274
|
async function svelteCodemods(site) {
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
}
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
}
|
|
2335
|
-
const sha = await commit(
|
|
2336
|
-
site.path,
|
|
2337
|
-
`refactor(svelte5): apply codemods (${changes.length} files)`
|
|
2338
|
-
);
|
|
2339
|
-
return {
|
|
2340
|
-
recipe: "svelte-codemods",
|
|
2341
|
-
site: label,
|
|
2342
|
-
status: "applied",
|
|
2343
|
-
commits: sha ? [sha] : [],
|
|
2344
|
-
notes: `branch: ${branch}`
|
|
2345
|
-
};
|
|
2275
|
+
return withRecipe({
|
|
2276
|
+
name: "svelte-codemods",
|
|
2277
|
+
site,
|
|
2278
|
+
plan: async () => {
|
|
2279
|
+
const changes = await planGotchaCodemods(site.path);
|
|
2280
|
+
if (changes.length === 0) {
|
|
2281
|
+
return { kind: "noop", notes: "no codemod targets matched" };
|
|
2282
|
+
}
|
|
2283
|
+
return { kind: "apply", plan: changes };
|
|
2284
|
+
},
|
|
2285
|
+
apply: async (changes, { commit: commit2, cwd }) => {
|
|
2286
|
+
for (const c of changes) {
|
|
2287
|
+
await writeFile8(join18(cwd, c.rel), c.after, "utf-8");
|
|
2288
|
+
}
|
|
2289
|
+
await commit2(`refactor(svelte5): apply codemods (${changes.length} files)`);
|
|
2290
|
+
return { kind: "ok" };
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2346
2293
|
}
|
|
2347
2294
|
|
|
2348
2295
|
// src/cli/commands/svelte-codemods.ts
|
|
@@ -2401,6 +2348,17 @@ var RECIPE_DESCRIPTIONS = {
|
|
|
2401
2348
|
"convert-to-pnpm": "Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).",
|
|
2402
2349
|
onboard: "Install @reddoorla/maintenance + audit deps on a site (preferred first step)."
|
|
2403
2350
|
};
|
|
2351
|
+
async function runOrExit(fn, opts) {
|
|
2352
|
+
try {
|
|
2353
|
+
const { output, code } = await fn();
|
|
2354
|
+
console.log(output);
|
|
2355
|
+
process.exit(code);
|
|
2356
|
+
} catch (err) {
|
|
2357
|
+
const e = err;
|
|
2358
|
+
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2359
|
+
process.exit(e.exitCode ?? 1);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2404
2362
|
var cli = cac("reddoor-maint");
|
|
2405
2363
|
cli.option("--cwd <path>", "Override working directory (default: process.cwd())");
|
|
2406
2364
|
cli.option("--verbose", "Verbose output (full stack on errors)");
|
|
@@ -2415,101 +2373,31 @@ cli.command("list-recipes", "Print the available recipes.").action(() => {
|
|
|
2415
2373
|
}
|
|
2416
2374
|
});
|
|
2417
2375
|
cli.command("audit [site]", "Run audits against a site (default: cwd).").option("--only <names>", "Comma-separated audit names (e.g. deps,lighthouse)").option("--json", "Machine-readable JSON output").option("--fleet <inventory>", "Inventory file (.json or .mjs/.js); aggregates across sites").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
2418
|
-
async (site, opts) =>
|
|
2419
|
-
try {
|
|
2420
|
-
const { output, code } = await runAuditCommand(site, opts);
|
|
2421
|
-
console.log(output);
|
|
2422
|
-
process.exit(code);
|
|
2423
|
-
} catch (err) {
|
|
2424
|
-
const e = err;
|
|
2425
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2426
|
-
process.exit(e.exitCode ?? 1);
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2376
|
+
async (site, opts) => runOrExit(() => runAuditCommand(site, opts), opts)
|
|
2429
2377
|
);
|
|
2430
2378
|
cli.command("sync-configs [site]", "Sync canonical configs into a site.").option("--only <names>", "Comma-separated config names (e.g. eslint,prettier)").option("--dry", "Print diff without writing").option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
2431
|
-
async (site, opts) =>
|
|
2432
|
-
try {
|
|
2433
|
-
const { output, code } = await runSyncConfigsCommand(site, opts);
|
|
2434
|
-
console.log(output);
|
|
2435
|
-
process.exit(code);
|
|
2436
|
-
} catch (err) {
|
|
2437
|
-
const e = err;
|
|
2438
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2439
|
-
process.exit(e.exitCode ?? 1);
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2379
|
+
async (site, opts) => runOrExit(() => runSyncConfigsCommand(site, opts), opts)
|
|
2442
2380
|
);
|
|
2443
2381
|
cli.command("bump-deps [site]", "Bump dependencies.").option("--group <group>", "patch | minor | major", { default: "minor" }).option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
2444
|
-
async (site, opts) =>
|
|
2445
|
-
try {
|
|
2446
|
-
const { output, code } = await runBumpDepsCommand(site, opts);
|
|
2447
|
-
console.log(output);
|
|
2448
|
-
process.exit(code);
|
|
2449
|
-
} catch (err) {
|
|
2450
|
-
const e = err;
|
|
2451
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2452
|
-
process.exit(e.exitCode ?? 1);
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2382
|
+
async (site, opts) => runOrExit(() => runBumpDepsCommand(site, opts), opts)
|
|
2455
2383
|
);
|
|
2456
2384
|
cli.command("upgrade <upgrade> [site]", "Run a named upgrade recipe (svelte-4-to-5).").example("reddoor-maint upgrade svelte-4-to-5 ./my-site").option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
2457
|
-
async (upgrade, site, opts) =>
|
|
2458
|
-
try {
|
|
2459
|
-
const { output, code } = await runUpgradeCommand(upgrade, site, opts);
|
|
2460
|
-
console.log(output);
|
|
2461
|
-
process.exit(code);
|
|
2462
|
-
} catch (err) {
|
|
2463
|
-
const e = err;
|
|
2464
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2465
|
-
process.exit(e.exitCode ?? 1);
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2385
|
+
async (upgrade, site, opts) => runOrExit(() => runUpgradeCommand(upgrade, site, opts), opts)
|
|
2468
2386
|
);
|
|
2469
2387
|
cli.command(
|
|
2470
2388
|
"convert-to-pnpm [site]",
|
|
2471
2389
|
"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts)."
|
|
2472
2390
|
).option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
2473
|
-
async (site, opts) =>
|
|
2474
|
-
try {
|
|
2475
|
-
const { output, code } = await runConvertToPnpmCommand(site, opts);
|
|
2476
|
-
console.log(output);
|
|
2477
|
-
process.exit(code);
|
|
2478
|
-
} catch (err) {
|
|
2479
|
-
const e = err;
|
|
2480
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2481
|
-
process.exit(e.exitCode ?? 1);
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2391
|
+
async (site, opts) => runOrExit(() => runConvertToPnpmCommand(site, opts), opts)
|
|
2484
2392
|
);
|
|
2485
2393
|
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(
|
|
2486
|
-
async (site, opts) =>
|
|
2487
|
-
try {
|
|
2488
|
-
const { output, code } = await runSvelteCodemodsCommand(site, opts);
|
|
2489
|
-
console.log(output);
|
|
2490
|
-
process.exit(code);
|
|
2491
|
-
} catch (err) {
|
|
2492
|
-
const e = err;
|
|
2493
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2494
|
-
process.exit(e.exitCode ?? 1);
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2394
|
+
async (site, opts) => runOrExit(() => runSvelteCodemodsCommand(site, opts), opts)
|
|
2497
2395
|
);
|
|
2498
2396
|
cli.command(
|
|
2499
2397
|
"onboard [site]",
|
|
2500
2398
|
"Install @reddoorla/maintenance + audit deps on a site (run after convert-to-pnpm)."
|
|
2501
2399
|
).option("--audits <names>", "Comma-separated audit subset: lighthouse,a11y (default: both)").option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
2502
|
-
async (site, opts) =>
|
|
2503
|
-
try {
|
|
2504
|
-
const { output, code } = await runOnboardCommand(site, opts);
|
|
2505
|
-
console.log(output);
|
|
2506
|
-
process.exit(code);
|
|
2507
|
-
} catch (err) {
|
|
2508
|
-
const e = err;
|
|
2509
|
-
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
2510
|
-
process.exit(e.exitCode ?? 1);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2400
|
+
async (site, opts) => runOrExit(() => runOnboardCommand(site, opts), opts)
|
|
2513
2401
|
);
|
|
2514
2402
|
cli.help();
|
|
2515
2403
|
cli.version(version);
|