@reddoorla/maintenance 0.1.1 → 0.1.3
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 +228 -109
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +116 -73
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +176 -81
- package/dist/index.js.map +1 -1
- package/dist/util/pkg.d.ts +6 -2
- package/dist/util/pkg.js +3 -1
- package/dist/util/pkg.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/bin.ts
|
|
4
|
-
import {
|
|
5
|
-
import { dirname, join as join14 } from "path";
|
|
4
|
+
import { dirname } from "path";
|
|
6
5
|
import { fileURLToPath } from "url";
|
|
7
6
|
import { cac } from "cac";
|
|
8
7
|
|
|
@@ -12,15 +11,18 @@ import { resolve as resolve2 } from "path";
|
|
|
12
11
|
// src/audits/util/spawn.ts
|
|
13
12
|
import { spawn } from "child_process";
|
|
14
13
|
var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve6, reject) => {
|
|
14
|
+
const streaming = opts.streaming === true;
|
|
15
15
|
const child = spawn(cmd, [...args], {
|
|
16
16
|
cwd: opts.cwd,
|
|
17
17
|
env: opts.env ?? process.env,
|
|
18
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
18
|
+
stdio: streaming ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
19
19
|
});
|
|
20
20
|
let stdout = "";
|
|
21
21
|
let stderr = "";
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if (!streaming) {
|
|
23
|
+
child.stdout?.on("data", (chunk) => stdout += String(chunk));
|
|
24
|
+
child.stderr?.on("data", (chunk) => stderr += String(chunk));
|
|
25
|
+
}
|
|
24
26
|
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
25
27
|
child.kill("SIGTERM");
|
|
26
28
|
reject(new Error(`spawn timeout after ${opts.timeoutMs}ms: ${cmd}`));
|
|
@@ -115,10 +117,10 @@ async function depsAudit(ctx) {
|
|
|
115
117
|
details: { error: String(err) }
|
|
116
118
|
};
|
|
117
119
|
}
|
|
118
|
-
const
|
|
120
|
+
const pkg = JSON.parse(pkgRaw);
|
|
119
121
|
const installed = {
|
|
120
|
-
...
|
|
121
|
-
...
|
|
122
|
+
...pkg.dependencies ?? {},
|
|
123
|
+
...pkg.devDependencies ?? {}
|
|
122
124
|
};
|
|
123
125
|
const details = [];
|
|
124
126
|
for (const [name, baseline] of Object.entries(baselineVersions)) {
|
|
@@ -148,7 +150,7 @@ async function depsAudit(ctx) {
|
|
|
148
150
|
// src/audits/lint.ts
|
|
149
151
|
import { existsSync } from "fs";
|
|
150
152
|
import { readFile as readFile2 } from "fs/promises";
|
|
151
|
-
import { join as join2
|
|
153
|
+
import { join as join2 } from "path";
|
|
152
154
|
import { ESLint } from "eslint";
|
|
153
155
|
import { check as prettierCheck, resolveConfig as prettierResolveConfig } from "prettier";
|
|
154
156
|
import { glob } from "tinyglobby";
|
|
@@ -177,19 +179,19 @@ async function lintAudit(ctx) {
|
|
|
177
179
|
errorOnUnmatchedPattern: false
|
|
178
180
|
});
|
|
179
181
|
const relFiles = await listFiles(site.path);
|
|
180
|
-
const
|
|
181
|
-
const eslintResults = await eslint2.lintFiles(filesToLint);
|
|
182
|
+
const eslintResults = await eslint2.lintFiles(relFiles);
|
|
182
183
|
const eslintErrors = eslintResults.reduce((n, r) => n + r.errorCount, 0);
|
|
183
184
|
const eslintWarnings = eslintResults.reduce((n, r) => n + r.warningCount, 0);
|
|
184
185
|
const prettierUnformatted = [];
|
|
185
|
-
for (const
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
186
|
+
for (const rel of relFiles) {
|
|
187
|
+
const absForResolve = join2(site.path, rel);
|
|
188
|
+
const source = await readFile2(absForResolve, "utf-8");
|
|
189
|
+
const options = await prettierResolveConfig(absForResolve) ?? {};
|
|
190
|
+
const ok = await prettierCheck(source, { ...options, filepath: absForResolve });
|
|
191
|
+
if (!ok) prettierUnformatted.push(rel);
|
|
190
192
|
}
|
|
191
193
|
const status = eslintErrors > 0 || prettierUnformatted.length > 0 ? "fail" : eslintWarnings > 0 ? "warn" : "pass";
|
|
192
|
-
const summary = status === "pass" ? `lint clean across ${
|
|
194
|
+
const summary = status === "pass" ? `lint clean across ${relFiles.length} files` : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;
|
|
193
195
|
return {
|
|
194
196
|
audit: "lint",
|
|
195
197
|
site: siteLabel2(site),
|
|
@@ -199,7 +201,7 @@ async function lintAudit(ctx) {
|
|
|
199
201
|
eslintErrors,
|
|
200
202
|
eslintWarnings,
|
|
201
203
|
prettierUnformatted,
|
|
202
|
-
files:
|
|
204
|
+
files: relFiles.length
|
|
203
205
|
}
|
|
204
206
|
};
|
|
205
207
|
}
|
|
@@ -215,7 +217,7 @@ function classify(v) {
|
|
|
215
217
|
}
|
|
216
218
|
function normalizeSeverity(s) {
|
|
217
219
|
if (s === "low" || s === "moderate" || s === "high" || s === "critical") return s;
|
|
218
|
-
return "
|
|
220
|
+
return "low";
|
|
219
221
|
}
|
|
220
222
|
function extractAdvisoriesFromPnpm(parsed) {
|
|
221
223
|
const out = [];
|
|
@@ -231,83 +233,101 @@ function extractAdvisoriesFromPnpm(parsed) {
|
|
|
231
233
|
}
|
|
232
234
|
return out;
|
|
233
235
|
}
|
|
236
|
+
function resolveNpmAdvisoryRoot(startName, vulnerabilities) {
|
|
237
|
+
const seen = /* @__PURE__ */ new Set();
|
|
238
|
+
let current = startName;
|
|
239
|
+
while (!seen.has(current)) {
|
|
240
|
+
seen.add(current);
|
|
241
|
+
const entry = vulnerabilities[current];
|
|
242
|
+
if (!entry || !Array.isArray(entry.via)) return { rootName: current };
|
|
243
|
+
const detailed = entry.via.find(
|
|
244
|
+
(e) => typeof e === "object" && e !== null
|
|
245
|
+
);
|
|
246
|
+
if (detailed) return { rootName: current, detail: detailed };
|
|
247
|
+
const next = entry.via.find((e) => typeof e === "string");
|
|
248
|
+
if (!next || next === current) return { rootName: current };
|
|
249
|
+
current = next;
|
|
250
|
+
}
|
|
251
|
+
return { rootName: current };
|
|
252
|
+
}
|
|
234
253
|
function extractAdvisoriesFromNpm(parsed) {
|
|
235
|
-
const
|
|
236
|
-
|
|
254
|
+
const vulnerabilities = parsed.vulnerabilities ?? {};
|
|
255
|
+
const roots = /* @__PURE__ */ new Map();
|
|
256
|
+
for (const [name, v] of Object.entries(vulnerabilities)) {
|
|
237
257
|
if (!v) continue;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
out.push({
|
|
250
|
-
module: v.name ?? name,
|
|
251
|
-
severity: normalizeSeverity(v.severity),
|
|
258
|
+
const { rootName, detail } = resolveNpmAdvisoryRoot(name, vulnerabilities);
|
|
259
|
+
if (roots.has(rootName)) continue;
|
|
260
|
+
const rootEntry = vulnerabilities[rootName];
|
|
261
|
+
const severity = normalizeSeverity(rootEntry?.severity ?? v.severity);
|
|
262
|
+
const title = detail?.title ?? rootName;
|
|
263
|
+
const url = detail?.url;
|
|
264
|
+
roots.set(rootName, {
|
|
265
|
+
module: rootEntry?.name ?? rootName,
|
|
266
|
+
severity,
|
|
252
267
|
title,
|
|
253
268
|
...url ? { url } : {}
|
|
254
269
|
});
|
|
255
270
|
}
|
|
256
|
-
return
|
|
271
|
+
return [...roots.values()];
|
|
257
272
|
}
|
|
258
|
-
async function
|
|
273
|
+
async function runAuditTool(spawn2, cmd, args, cwd) {
|
|
274
|
+
let raw;
|
|
259
275
|
try {
|
|
260
|
-
|
|
276
|
+
raw = await spawn2(cmd, args, { cwd });
|
|
261
277
|
} catch (err) {
|
|
262
278
|
const e = err;
|
|
263
|
-
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) return {
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
async function securityAudit(ctx) {
|
|
268
|
-
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
269
|
-
const site = ctx.site;
|
|
270
|
-
const label = siteLabel3(site);
|
|
271
|
-
let used = "pnpm audit";
|
|
272
|
-
let raw = await tryRun(
|
|
273
|
-
spawn2,
|
|
274
|
-
"pnpm",
|
|
275
|
-
["audit", "--json", "--prod"],
|
|
276
|
-
site.path
|
|
277
|
-
);
|
|
278
|
-
if ("missing" in raw) {
|
|
279
|
-
used = "npm audit";
|
|
280
|
-
raw = await tryRun(spawn2, "npm", ["audit", "--json", "--omit=dev"], site.path);
|
|
281
|
-
}
|
|
282
|
-
if ("missing" in raw) {
|
|
283
|
-
return {
|
|
284
|
-
audit: "security",
|
|
285
|
-
site: label,
|
|
286
|
-
status: "skip",
|
|
287
|
-
summary: "neither pnpm nor npm is available on PATH"
|
|
288
|
-
};
|
|
279
|
+
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) return { kind: "missing" };
|
|
280
|
+
return { kind: "error", reason: `spawn failed: ${String(err).slice(0, 200)}` };
|
|
289
281
|
}
|
|
290
282
|
if (raw.code !== 0 && raw.code !== 1) {
|
|
291
283
|
return {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
status: "skip",
|
|
295
|
-
summary: `${used} exited with code ${raw.code}`,
|
|
296
|
-
details: { stderr: raw.stderr }
|
|
284
|
+
kind: "error",
|
|
285
|
+
reason: `exit ${raw.code}${raw.stderr ? `: ${raw.stderr.slice(0, 150)}` : ""}`
|
|
297
286
|
};
|
|
298
287
|
}
|
|
299
288
|
let parsed;
|
|
300
289
|
try {
|
|
301
|
-
parsed = JSON.parse(raw.stdout);
|
|
290
|
+
parsed = JSON.parse(raw.stdout || "{}");
|
|
302
291
|
} catch (err) {
|
|
303
|
-
return {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
292
|
+
return { kind: "error", reason: `unparseable JSON: ${String(err).slice(0, 100)}` };
|
|
293
|
+
}
|
|
294
|
+
const errEnvelope = parsed.error;
|
|
295
|
+
if (errEnvelope && typeof errEnvelope === "object") {
|
|
296
|
+
return { kind: "error", reason: errEnvelope.code ?? "error envelope returned" };
|
|
297
|
+
}
|
|
298
|
+
if (!parsed.metadata?.vulnerabilities) {
|
|
299
|
+
return { kind: "error", reason: "no metadata.vulnerabilities in output" };
|
|
300
|
+
}
|
|
301
|
+
return { kind: "ok", parsed };
|
|
302
|
+
}
|
|
303
|
+
async function securityAudit(ctx) {
|
|
304
|
+
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
305
|
+
const site = ctx.site;
|
|
306
|
+
const label = siteLabel3(site);
|
|
307
|
+
let used = "pnpm audit";
|
|
308
|
+
let result = await runAuditTool(spawn2, "pnpm", ["audit", "--json", "--prod"], site.path);
|
|
309
|
+
if (result.kind !== "ok") {
|
|
310
|
+
const pnpmReason = result.kind === "missing" ? "not installed" : result.reason;
|
|
311
|
+
const npmResult = await runAuditTool(
|
|
312
|
+
spawn2,
|
|
313
|
+
"npm",
|
|
314
|
+
["audit", "--json", "--omit=dev"],
|
|
315
|
+
site.path
|
|
316
|
+
);
|
|
317
|
+
if (npmResult.kind === "ok") {
|
|
318
|
+
result = npmResult;
|
|
319
|
+
used = "npm audit";
|
|
320
|
+
} else {
|
|
321
|
+
const npmReason = npmResult.kind === "missing" ? "not installed" : npmResult.reason;
|
|
322
|
+
return {
|
|
323
|
+
audit: "security",
|
|
324
|
+
site: label,
|
|
325
|
+
status: "skip",
|
|
326
|
+
summary: `cannot run audit \u2014 pnpm: ${pnpmReason}; npm: ${npmReason}`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
310
329
|
}
|
|
330
|
+
const parsed = result.parsed;
|
|
311
331
|
const counts = {
|
|
312
332
|
low: parsed.metadata?.vulnerabilities?.low ?? 0,
|
|
313
333
|
moderate: parsed.metadata?.vulnerabilities?.moderate ?? 0,
|
|
@@ -518,6 +538,10 @@ import { dirname } from "node:path";
|
|
|
518
538
|
const pages = ${JSON.stringify(a11yRoutes)};
|
|
519
539
|
const OUTPUT = process.env.REDDOOR_A11Y_OUTPUT;
|
|
520
540
|
|
|
541
|
+
// Playwright's default per-test timeout is 30s. We loop through every
|
|
542
|
+
// configured route in a single test, so the budget needs to scale.
|
|
543
|
+
test.setTimeout(5 * 60_000);
|
|
544
|
+
|
|
521
545
|
test("a11y across configured routes", async ({ page }) => {
|
|
522
546
|
const violations = [];
|
|
523
547
|
for (const { path, name } of pages) {
|
|
@@ -649,6 +673,7 @@ function localPath(path, opts = {}) {
|
|
|
649
673
|
|
|
650
674
|
// src/inventory/json.ts
|
|
651
675
|
import { readFile as readFile5 } from "fs/promises";
|
|
676
|
+
import { isAbsolute } from "path";
|
|
652
677
|
function validate(raw) {
|
|
653
678
|
if (!Array.isArray(raw)) {
|
|
654
679
|
throw new Error("inventory JSON must be an array of sites");
|
|
@@ -661,6 +686,11 @@ function validate(raw) {
|
|
|
661
686
|
if (typeof e.path !== "string" || e.path.length === 0) {
|
|
662
687
|
throw new Error(`inventory entry ${i} is missing required field: path`);
|
|
663
688
|
}
|
|
689
|
+
if (!isAbsolute(e.path)) {
|
|
690
|
+
throw new Error(
|
|
691
|
+
`inventory entry ${i}: path must be absolute (got "${e.path}"). Relative paths are rejected so cwd at invocation can't change which site is targeted.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
664
694
|
const site = { path: e.path };
|
|
665
695
|
if (typeof e.name === "string") site.name = e.name;
|
|
666
696
|
if (typeof e.repoUrl === "string") site.repoUrl = e.repoUrl;
|
|
@@ -711,11 +741,22 @@ async function resolveSites(input) {
|
|
|
711
741
|
|
|
712
742
|
// src/cli/fleet/clone-if-needed.ts
|
|
713
743
|
import { stat, readdir, mkdir } from "fs/promises";
|
|
714
|
-
import { join as join5 } from "path";
|
|
744
|
+
import { isAbsolute as isAbsolute2, join as join5 } from "path";
|
|
715
745
|
function deriveNameFromRepoUrl(repoUrl) {
|
|
716
746
|
const slash = repoUrl.split("/").pop() ?? repoUrl;
|
|
717
747
|
return slash.replace(/\.git$/, "");
|
|
718
748
|
}
|
|
749
|
+
function assertSafeName(name) {
|
|
750
|
+
if (isAbsolute2(name)) {
|
|
751
|
+
throw new Error(`unsafe site name (absolute path not allowed): ${name}`);
|
|
752
|
+
}
|
|
753
|
+
if (name.includes("/") || name.includes("\\")) {
|
|
754
|
+
throw new Error(`unsafe site name (path separator not allowed): ${name}`);
|
|
755
|
+
}
|
|
756
|
+
if (name.split(/[\\/]/).some((seg) => seg === "..")) {
|
|
757
|
+
throw new Error(`unsafe site name (traversal segment not allowed): ${name}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
719
760
|
async function isNonEmptyDir(path) {
|
|
720
761
|
try {
|
|
721
762
|
const s = await stat(path);
|
|
@@ -732,6 +773,7 @@ async function cloneIfNeeded(site, opts) {
|
|
|
732
773
|
throw new Error(`site path does not exist (${site.path}) and no repoUrl is set \u2014 cannot clone`);
|
|
733
774
|
}
|
|
734
775
|
const name = site.name ?? deriveNameFromRepoUrl(site.repoUrl);
|
|
776
|
+
assertSafeName(name);
|
|
735
777
|
const target = join5(opts.workdir, name);
|
|
736
778
|
await mkdir(opts.workdir, { recursive: true });
|
|
737
779
|
if (await isNonEmptyDir(target)) {
|
|
@@ -995,6 +1037,7 @@ async function bumpDeps(site, opts = {}) {
|
|
|
995
1037
|
const label = siteLabel7(site);
|
|
996
1038
|
const group = opts.group ?? "minor";
|
|
997
1039
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1040
|
+
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
998
1041
|
const outdated = await spawn2("pnpm", ["outdated", "--json", ...outdatedFlagsForGroup(group)], {
|
|
999
1042
|
cwd: site.path
|
|
1000
1043
|
});
|
|
@@ -1019,7 +1062,10 @@ async function bumpDeps(site, opts = {}) {
|
|
|
1019
1062
|
}
|
|
1020
1063
|
const branch = branchName("bump-deps");
|
|
1021
1064
|
await createBranch(site.path, branch);
|
|
1022
|
-
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
1065
|
+
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
1066
|
+
cwd: site.path,
|
|
1067
|
+
streaming: true
|
|
1068
|
+
});
|
|
1023
1069
|
const sha = await commit(site.path, `chore(deps): bump dependencies (${group})`);
|
|
1024
1070
|
const shas = sha ? [sha] : [];
|
|
1025
1071
|
return {
|
|
@@ -1075,31 +1121,33 @@ async function readPackageJson(path) {
|
|
|
1075
1121
|
const raw = await readFile8(path, "utf-8");
|
|
1076
1122
|
return JSON.parse(raw);
|
|
1077
1123
|
}
|
|
1078
|
-
async function writePackageJson(path,
|
|
1079
|
-
const content = JSON.stringify(
|
|
1124
|
+
async function writePackageJson(path, pkg) {
|
|
1125
|
+
const content = JSON.stringify(pkg, null, 2) + "\n";
|
|
1080
1126
|
await writeFile4(path, content, "utf-8");
|
|
1081
1127
|
}
|
|
1082
|
-
function bumpDep(
|
|
1128
|
+
function bumpDep(pkg, name, version2, opts = {}) {
|
|
1129
|
+
const mode = opts.mode ?? "ensure";
|
|
1083
1130
|
const next = {
|
|
1084
|
-
...
|
|
1131
|
+
...pkg
|
|
1085
1132
|
};
|
|
1086
|
-
if (
|
|
1087
|
-
next.dependencies = { ...
|
|
1133
|
+
if (pkg.dependencies) {
|
|
1134
|
+
next.dependencies = { ...pkg.dependencies };
|
|
1088
1135
|
}
|
|
1089
|
-
if (
|
|
1090
|
-
next.devDependencies = { ...
|
|
1136
|
+
if (pkg.devDependencies) {
|
|
1137
|
+
next.devDependencies = { ...pkg.devDependencies };
|
|
1091
1138
|
}
|
|
1092
1139
|
if (next.dependencies && name in next.dependencies) {
|
|
1093
|
-
if (next.dependencies[name] ===
|
|
1094
|
-
next.dependencies[name] =
|
|
1140
|
+
if (next.dependencies[name] === version2) return pkg;
|
|
1141
|
+
next.dependencies[name] = version2;
|
|
1095
1142
|
return next;
|
|
1096
1143
|
}
|
|
1097
1144
|
if (next.devDependencies && name in next.devDependencies) {
|
|
1098
|
-
if (next.devDependencies[name] ===
|
|
1099
|
-
next.devDependencies[name] =
|
|
1145
|
+
if (next.devDependencies[name] === version2) return pkg;
|
|
1146
|
+
next.devDependencies[name] = version2;
|
|
1100
1147
|
return next;
|
|
1101
1148
|
}
|
|
1102
|
-
|
|
1149
|
+
if (mode === "bump-only") return pkg;
|
|
1150
|
+
next.devDependencies = { ...next.devDependencies ?? {}, [name]: version2 };
|
|
1103
1151
|
return next;
|
|
1104
1152
|
}
|
|
1105
1153
|
|
|
@@ -1118,12 +1166,12 @@ var SVELTE_5_VERSIONS = {
|
|
|
1118
1166
|
};
|
|
1119
1167
|
async function bumpToSvelte5Versions(cwd) {
|
|
1120
1168
|
const pkgPath = join8(cwd, "package.json");
|
|
1121
|
-
const
|
|
1122
|
-
let next =
|
|
1123
|
-
for (const [name,
|
|
1124
|
-
next = bumpDep(next, name,
|
|
1169
|
+
const pkg = await readPackageJson(pkgPath);
|
|
1170
|
+
let next = pkg;
|
|
1171
|
+
for (const [name, version2] of Object.entries(SVELTE_5_VERSIONS)) {
|
|
1172
|
+
next = bumpDep(next, name, version2, { mode: "bump-only" });
|
|
1125
1173
|
}
|
|
1126
|
-
if (next ===
|
|
1174
|
+
if (next === pkg) return false;
|
|
1127
1175
|
await writePackageJson(pkgPath, next);
|
|
1128
1176
|
return true;
|
|
1129
1177
|
}
|
|
@@ -1131,6 +1179,45 @@ async function bumpToSvelte5Versions(cwd) {
|
|
|
1131
1179
|
// src/recipes/svelte-5/step-svelte-config.ts
|
|
1132
1180
|
import { readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
1133
1181
|
import { join as join9 } from "path";
|
|
1182
|
+
var VITE_PLUGIN_PKG = "@sveltejs/vite-plugin-svelte";
|
|
1183
|
+
var IMPORT_FROM_VITE_PLUGIN = new RegExp(
|
|
1184
|
+
String.raw`^import\s+\{\s*([^}]+?)\s*\}\s+from\s+["']` + VITE_PLUGIN_PKG.replace(/[/]/g, "\\/") + String.raw`["'];?[ \t]*\n`,
|
|
1185
|
+
"m"
|
|
1186
|
+
);
|
|
1187
|
+
function dropVitePreprocessImport(source) {
|
|
1188
|
+
return source.replace(IMPORT_FROM_VITE_PLUGIN, (full, names) => {
|
|
1189
|
+
const remaining = names.split(",").map((n) => n.trim()).filter((n) => n.length > 0 && n !== "vitePreprocess");
|
|
1190
|
+
if (remaining.length === 0) return "";
|
|
1191
|
+
return `import { ${remaining.join(", ")} } from "${VITE_PLUGIN_PKG}";
|
|
1192
|
+
`;
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
function findMatchingParen(source, openIdx) {
|
|
1196
|
+
if (source[openIdx] !== "(") return -1;
|
|
1197
|
+
let depth = 0;
|
|
1198
|
+
for (let i = openIdx; i < source.length; i++) {
|
|
1199
|
+
const ch = source[i];
|
|
1200
|
+
if (ch === "(") depth++;
|
|
1201
|
+
else if (ch === ")") {
|
|
1202
|
+
depth--;
|
|
1203
|
+
if (depth === 0) return i;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return -1;
|
|
1207
|
+
}
|
|
1208
|
+
function dropPreprocessKey(source) {
|
|
1209
|
+
const startRe = /^(\s*)preprocess:\s*vitePreprocess\(/m;
|
|
1210
|
+
const m = startRe.exec(source);
|
|
1211
|
+
if (!m) return source;
|
|
1212
|
+
const indent = m[1] ?? "";
|
|
1213
|
+
const parenOpenAbs = m.index + m[0].length - 1;
|
|
1214
|
+
const parenCloseAbs = findMatchingParen(source, parenOpenAbs);
|
|
1215
|
+
if (parenCloseAbs < 0) return source;
|
|
1216
|
+
let tailIdx = parenCloseAbs + 1;
|
|
1217
|
+
while (tailIdx < source.length && /[ \t,]/.test(source[tailIdx] ?? "")) tailIdx++;
|
|
1218
|
+
if (source[tailIdx] === "\n") tailIdx++;
|
|
1219
|
+
return source.slice(0, m.index) + source.slice(tailIdx).replace(new RegExp(`^${indent}\\n`), "");
|
|
1220
|
+
}
|
|
1134
1221
|
async function migrateSvelteConfig(cwd) {
|
|
1135
1222
|
const path = join9(cwd, "svelte.config.js");
|
|
1136
1223
|
let src;
|
|
@@ -1140,11 +1227,8 @@ async function migrateSvelteConfig(cwd) {
|
|
|
1140
1227
|
return false;
|
|
1141
1228
|
}
|
|
1142
1229
|
let next = src;
|
|
1143
|
-
next = next
|
|
1144
|
-
|
|
1145
|
-
""
|
|
1146
|
-
);
|
|
1147
|
-
next = next.replace(/^\s*preprocess:\s*vitePreprocess\(\)\s*,?\s*\n/m, "");
|
|
1230
|
+
next = dropPreprocessKey(next);
|
|
1231
|
+
next = dropVitePreprocessImport(next);
|
|
1148
1232
|
if (next === src) return false;
|
|
1149
1233
|
await writeFile5(path, next, "utf-8");
|
|
1150
1234
|
return true;
|
|
@@ -1174,8 +1258,8 @@ async function runSvelteMigrate(cwd, spawn2 = defaultSpawn) {
|
|
|
1174
1258
|
// src/recipes/svelte-5/step-tailwind-upgrade.ts
|
|
1175
1259
|
import { join as join10 } from "path";
|
|
1176
1260
|
async function upgradeTailwind(cwd, spawn2 = defaultSpawn) {
|
|
1177
|
-
const
|
|
1178
|
-
const tailwindVersion =
|
|
1261
|
+
const pkg = await readPackageJson(join10(cwd, "package.json"));
|
|
1262
|
+
const tailwindVersion = pkg.devDependencies?.tailwindcss ?? pkg.dependencies?.tailwindcss;
|
|
1179
1263
|
if (!tailwindVersion) return { ran: false, reason: "tailwindcss not installed" };
|
|
1180
1264
|
if (/^\^?4\./.test(tailwindVersion)) return { ran: false, reason: "already on tailwind 4.x" };
|
|
1181
1265
|
try {
|
|
@@ -1254,10 +1338,32 @@ function exportLetToProps(source) {
|
|
|
1254
1338
|
}
|
|
1255
1339
|
|
|
1256
1340
|
// src/recipes/svelte-5/codemods/dollar-restprops.ts
|
|
1341
|
+
function removeInterfaceBlock(source) {
|
|
1342
|
+
const re = /^\s*interface\s+\$\$Props\s*\{/m;
|
|
1343
|
+
let out = source;
|
|
1344
|
+
while (true) {
|
|
1345
|
+
const match = re.exec(out);
|
|
1346
|
+
if (!match) return out;
|
|
1347
|
+
const openBraceIdx = match.index + match[0].length - 1;
|
|
1348
|
+
let depth = 1;
|
|
1349
|
+
let i = openBraceIdx + 1;
|
|
1350
|
+
while (i < out.length && depth > 0) {
|
|
1351
|
+
const ch = out[i];
|
|
1352
|
+
if (ch === "{") depth++;
|
|
1353
|
+
else if (ch === "}") depth--;
|
|
1354
|
+
i++;
|
|
1355
|
+
}
|
|
1356
|
+
if (depth !== 0) return out;
|
|
1357
|
+
let endIdx = i;
|
|
1358
|
+
while (endIdx < out.length && /[ \t]/.test(out[endIdx] ?? "")) endIdx++;
|
|
1359
|
+
if (out[endIdx] === "\n") endIdx++;
|
|
1360
|
+
out = out.slice(0, match.index) + out.slice(endIdx);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1257
1363
|
function removeDollarRestProps(source) {
|
|
1258
1364
|
let next = source;
|
|
1259
1365
|
next = next.replace(/\$\$restProps/g, "rest");
|
|
1260
|
-
next = next
|
|
1366
|
+
next = removeInterfaceBlock(next);
|
|
1261
1367
|
return next;
|
|
1262
1368
|
}
|
|
1263
1369
|
|
|
@@ -1327,8 +1433,8 @@ function siteLabel8(site) {
|
|
|
1327
1433
|
}
|
|
1328
1434
|
async function alreadyOnSvelte5(cwd) {
|
|
1329
1435
|
try {
|
|
1330
|
-
const
|
|
1331
|
-
const v =
|
|
1436
|
+
const pkg = await readPackageJson(join13(cwd, "package.json"));
|
|
1437
|
+
const v = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte;
|
|
1332
1438
|
return !!v && /^\^?5\./.test(v);
|
|
1333
1439
|
} catch {
|
|
1334
1440
|
return false;
|
|
@@ -1440,9 +1546,22 @@ async function runUpgradeCommand(upgradeName, site, opts = {}) {
|
|
|
1440
1546
|
return { output, code };
|
|
1441
1547
|
}
|
|
1442
1548
|
|
|
1549
|
+
// src/cli/version.ts
|
|
1550
|
+
import { readFileSync } from "fs";
|
|
1551
|
+
import { join as join14 } from "path";
|
|
1552
|
+
function resolvePackageVersion(fromDir) {
|
|
1553
|
+
try {
|
|
1554
|
+
const raw = readFileSync(join14(fromDir, "..", "..", "package.json"), "utf-8");
|
|
1555
|
+
const pkg = JSON.parse(raw);
|
|
1556
|
+
return pkg.version ?? "unknown";
|
|
1557
|
+
} catch {
|
|
1558
|
+
return "unknown";
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1443
1562
|
// src/cli/bin.ts
|
|
1444
1563
|
var here = dirname(fileURLToPath(import.meta.url));
|
|
1445
|
-
var
|
|
1564
|
+
var version = resolvePackageVersion(here);
|
|
1446
1565
|
var AUDIT_DESCRIPTIONS = {
|
|
1447
1566
|
deps: "Diff site package.json against the bundled baseline version map.",
|
|
1448
1567
|
lighthouse: "Run @lhci/cli autorun using the canonical lighthouserc.",
|
|
@@ -1521,6 +1640,6 @@ cli.command("upgrade <upgrade> [site]", "Run a named upgrade recipe (svelte-4-to
|
|
|
1521
1640
|
}
|
|
1522
1641
|
);
|
|
1523
1642
|
cli.help();
|
|
1524
|
-
cli.version(
|
|
1643
|
+
cli.version(version);
|
|
1525
1644
|
cli.parse();
|
|
1526
1645
|
//# sourceMappingURL=bin.js.map
|