@reddoorla/maintenance 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/bin.js +182 -68
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +70 -32
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +130 -40
- 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/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
// src/audits/util/spawn.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve, reject) => {
|
|
4
|
+
const streaming = opts.streaming === true;
|
|
4
5
|
const child = spawn(cmd, [...args], {
|
|
5
6
|
cwd: opts.cwd,
|
|
6
7
|
env: opts.env ?? process.env,
|
|
7
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
8
|
+
stdio: streaming ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
8
9
|
});
|
|
9
10
|
let stdout = "";
|
|
10
11
|
let stderr = "";
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
if (!streaming) {
|
|
13
|
+
child.stdout?.on("data", (chunk) => stdout += String(chunk));
|
|
14
|
+
child.stderr?.on("data", (chunk) => stderr += String(chunk));
|
|
15
|
+
}
|
|
13
16
|
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
14
17
|
child.kill("SIGTERM");
|
|
15
18
|
reject(new Error(`spawn timeout after ${opts.timeoutMs}ms: ${cmd}`));
|
|
@@ -137,7 +140,7 @@ async function depsAudit(ctx) {
|
|
|
137
140
|
// src/audits/lint.ts
|
|
138
141
|
import { existsSync } from "fs";
|
|
139
142
|
import { readFile as readFile2 } from "fs/promises";
|
|
140
|
-
import { join as join2
|
|
143
|
+
import { join as join2 } from "path";
|
|
141
144
|
import { ESLint } from "eslint";
|
|
142
145
|
import { check as prettierCheck, resolveConfig as prettierResolveConfig } from "prettier";
|
|
143
146
|
import { glob } from "tinyglobby";
|
|
@@ -166,19 +169,19 @@ async function lintAudit(ctx) {
|
|
|
166
169
|
errorOnUnmatchedPattern: false
|
|
167
170
|
});
|
|
168
171
|
const relFiles = await listFiles(site.path);
|
|
169
|
-
const
|
|
170
|
-
const eslintResults = await eslint2.lintFiles(filesToLint);
|
|
172
|
+
const eslintResults = await eslint2.lintFiles(relFiles);
|
|
171
173
|
const eslintErrors = eslintResults.reduce((n, r) => n + r.errorCount, 0);
|
|
172
174
|
const eslintWarnings = eslintResults.reduce((n, r) => n + r.warningCount, 0);
|
|
173
175
|
const prettierUnformatted = [];
|
|
174
|
-
for (const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
176
|
+
for (const rel of relFiles) {
|
|
177
|
+
const absForResolve = join2(site.path, rel);
|
|
178
|
+
const source = await readFile2(absForResolve, "utf-8");
|
|
179
|
+
const options = await prettierResolveConfig(absForResolve) ?? {};
|
|
180
|
+
const ok = await prettierCheck(source, { ...options, filepath: absForResolve });
|
|
181
|
+
if (!ok) prettierUnformatted.push(rel);
|
|
179
182
|
}
|
|
180
183
|
const status = eslintErrors > 0 || prettierUnformatted.length > 0 ? "fail" : eslintWarnings > 0 ? "warn" : "pass";
|
|
181
|
-
const summary = status === "pass" ? `lint clean across ${
|
|
184
|
+
const summary = status === "pass" ? `lint clean across ${relFiles.length} files` : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;
|
|
182
185
|
return {
|
|
183
186
|
audit: "lint",
|
|
184
187
|
site: siteLabel2(site),
|
|
@@ -188,7 +191,7 @@ async function lintAudit(ctx) {
|
|
|
188
191
|
eslintErrors,
|
|
189
192
|
eslintWarnings,
|
|
190
193
|
prettierUnformatted,
|
|
191
|
-
files:
|
|
194
|
+
files: relFiles.length
|
|
192
195
|
}
|
|
193
196
|
};
|
|
194
197
|
}
|
|
@@ -204,7 +207,7 @@ function classify(v) {
|
|
|
204
207
|
}
|
|
205
208
|
function normalizeSeverity(s) {
|
|
206
209
|
if (s === "low" || s === "moderate" || s === "high" || s === "critical") return s;
|
|
207
|
-
return "
|
|
210
|
+
return "low";
|
|
208
211
|
}
|
|
209
212
|
function extractAdvisoriesFromPnpm(parsed) {
|
|
210
213
|
const out = [];
|
|
@@ -220,29 +223,42 @@ function extractAdvisoriesFromPnpm(parsed) {
|
|
|
220
223
|
}
|
|
221
224
|
return out;
|
|
222
225
|
}
|
|
226
|
+
function resolveNpmAdvisoryRoot(startName, vulnerabilities) {
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
let current = startName;
|
|
229
|
+
while (!seen.has(current)) {
|
|
230
|
+
seen.add(current);
|
|
231
|
+
const entry = vulnerabilities[current];
|
|
232
|
+
if (!entry || !Array.isArray(entry.via)) return { rootName: current };
|
|
233
|
+
const detailed = entry.via.find(
|
|
234
|
+
(e) => typeof e === "object" && e !== null
|
|
235
|
+
);
|
|
236
|
+
if (detailed) return { rootName: current, detail: detailed };
|
|
237
|
+
const next = entry.via.find((e) => typeof e === "string");
|
|
238
|
+
if (!next || next === current) return { rootName: current };
|
|
239
|
+
current = next;
|
|
240
|
+
}
|
|
241
|
+
return { rootName: current };
|
|
242
|
+
}
|
|
223
243
|
function extractAdvisoriesFromNpm(parsed) {
|
|
224
|
-
const
|
|
225
|
-
|
|
244
|
+
const vulnerabilities = parsed.vulnerabilities ?? {};
|
|
245
|
+
const roots = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const [name, v] of Object.entries(vulnerabilities)) {
|
|
226
247
|
if (!v) continue;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
out.push({
|
|
239
|
-
module: v.name ?? name,
|
|
240
|
-
severity: normalizeSeverity(v.severity),
|
|
248
|
+
const { rootName, detail } = resolveNpmAdvisoryRoot(name, vulnerabilities);
|
|
249
|
+
if (roots.has(rootName)) continue;
|
|
250
|
+
const rootEntry = vulnerabilities[rootName];
|
|
251
|
+
const severity = normalizeSeverity(rootEntry?.severity ?? v.severity);
|
|
252
|
+
const title = detail?.title ?? rootName;
|
|
253
|
+
const url = detail?.url;
|
|
254
|
+
roots.set(rootName, {
|
|
255
|
+
module: rootEntry?.name ?? rootName,
|
|
256
|
+
severity,
|
|
241
257
|
title,
|
|
242
258
|
...url ? { url } : {}
|
|
243
259
|
});
|
|
244
260
|
}
|
|
245
|
-
return
|
|
261
|
+
return [...roots.values()];
|
|
246
262
|
}
|
|
247
263
|
async function tryRun(spawn2, cmd, args, cwd) {
|
|
248
264
|
try {
|
|
@@ -507,6 +523,10 @@ import { dirname } from "node:path";
|
|
|
507
523
|
const pages = ${JSON.stringify(a11yRoutes)};
|
|
508
524
|
const OUTPUT = process.env.REDDOOR_A11Y_OUTPUT;
|
|
509
525
|
|
|
526
|
+
// Playwright's default per-test timeout is 30s. We loop through every
|
|
527
|
+
// configured route in a single test, so the budget needs to scale.
|
|
528
|
+
test.setTimeout(5 * 60_000);
|
|
529
|
+
|
|
510
530
|
test("a11y across configured routes", async ({ page }) => {
|
|
511
531
|
const violations = [];
|
|
512
532
|
for (const { path, name } of pages) {
|
|
@@ -781,6 +801,7 @@ async function bumpDeps(site, opts = {}) {
|
|
|
781
801
|
const label = siteLabel7(site);
|
|
782
802
|
const group = opts.group ?? "minor";
|
|
783
803
|
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
804
|
+
await spawn2("pnpm", ["install"], { cwd: site.path, streaming: true });
|
|
784
805
|
const outdated = await spawn2("pnpm", ["outdated", "--json", ...outdatedFlagsForGroup(group)], {
|
|
785
806
|
cwd: site.path
|
|
786
807
|
});
|
|
@@ -805,7 +826,10 @@ async function bumpDeps(site, opts = {}) {
|
|
|
805
826
|
}
|
|
806
827
|
const branch = branchName("bump-deps");
|
|
807
828
|
await createBranch(site.path, branch);
|
|
808
|
-
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
829
|
+
await spawn2("pnpm", ["up", ...upFlagsForGroup(group)], {
|
|
830
|
+
cwd: site.path,
|
|
831
|
+
streaming: true
|
|
832
|
+
});
|
|
809
833
|
const sha = await commit(site.path, `chore(deps): bump dependencies (${group})`);
|
|
810
834
|
const shas = sha ? [sha] : [];
|
|
811
835
|
return {
|
|
@@ -830,7 +854,8 @@ async function writePackageJson(path, pkg) {
|
|
|
830
854
|
const content = JSON.stringify(pkg, null, 2) + "\n";
|
|
831
855
|
await writeFile4(path, content, "utf-8");
|
|
832
856
|
}
|
|
833
|
-
function bumpDep(pkg, name, version) {
|
|
857
|
+
function bumpDep(pkg, name, version, opts = {}) {
|
|
858
|
+
const mode = opts.mode ?? "ensure";
|
|
834
859
|
const next = {
|
|
835
860
|
...pkg
|
|
836
861
|
};
|
|
@@ -850,6 +875,7 @@ function bumpDep(pkg, name, version) {
|
|
|
850
875
|
next.devDependencies[name] = version;
|
|
851
876
|
return next;
|
|
852
877
|
}
|
|
878
|
+
if (mode === "bump-only") return pkg;
|
|
853
879
|
next.devDependencies = { ...next.devDependencies ?? {}, [name]: version };
|
|
854
880
|
return next;
|
|
855
881
|
}
|
|
@@ -872,7 +898,7 @@ async function bumpToSvelte5Versions(cwd) {
|
|
|
872
898
|
const pkg = await readPackageJson(pkgPath);
|
|
873
899
|
let next = pkg;
|
|
874
900
|
for (const [name, version] of Object.entries(SVELTE_5_VERSIONS)) {
|
|
875
|
-
next = bumpDep(next, name, version);
|
|
901
|
+
next = bumpDep(next, name, version, { mode: "bump-only" });
|
|
876
902
|
}
|
|
877
903
|
if (next === pkg) return false;
|
|
878
904
|
await writePackageJson(pkgPath, next);
|
|
@@ -882,6 +908,45 @@ async function bumpToSvelte5Versions(cwd) {
|
|
|
882
908
|
// src/recipes/svelte-5/step-svelte-config.ts
|
|
883
909
|
import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
884
910
|
import { join as join7 } from "path";
|
|
911
|
+
var VITE_PLUGIN_PKG = "@sveltejs/vite-plugin-svelte";
|
|
912
|
+
var IMPORT_FROM_VITE_PLUGIN = new RegExp(
|
|
913
|
+
String.raw`^import\s+\{\s*([^}]+?)\s*\}\s+from\s+["']` + VITE_PLUGIN_PKG.replace(/[/]/g, "\\/") + String.raw`["'];?[ \t]*\n`,
|
|
914
|
+
"m"
|
|
915
|
+
);
|
|
916
|
+
function dropVitePreprocessImport(source) {
|
|
917
|
+
return source.replace(IMPORT_FROM_VITE_PLUGIN, (full, names) => {
|
|
918
|
+
const remaining = names.split(",").map((n) => n.trim()).filter((n) => n.length > 0 && n !== "vitePreprocess");
|
|
919
|
+
if (remaining.length === 0) return "";
|
|
920
|
+
return `import { ${remaining.join(", ")} } from "${VITE_PLUGIN_PKG}";
|
|
921
|
+
`;
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
function findMatchingParen(source, openIdx) {
|
|
925
|
+
if (source[openIdx] !== "(") return -1;
|
|
926
|
+
let depth = 0;
|
|
927
|
+
for (let i = openIdx; i < source.length; i++) {
|
|
928
|
+
const ch = source[i];
|
|
929
|
+
if (ch === "(") depth++;
|
|
930
|
+
else if (ch === ")") {
|
|
931
|
+
depth--;
|
|
932
|
+
if (depth === 0) return i;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return -1;
|
|
936
|
+
}
|
|
937
|
+
function dropPreprocessKey(source) {
|
|
938
|
+
const startRe = /^(\s*)preprocess:\s*vitePreprocess\(/m;
|
|
939
|
+
const m = startRe.exec(source);
|
|
940
|
+
if (!m) return source;
|
|
941
|
+
const indent = m[1] ?? "";
|
|
942
|
+
const parenOpenAbs = m.index + m[0].length - 1;
|
|
943
|
+
const parenCloseAbs = findMatchingParen(source, parenOpenAbs);
|
|
944
|
+
if (parenCloseAbs < 0) return source;
|
|
945
|
+
let tailIdx = parenCloseAbs + 1;
|
|
946
|
+
while (tailIdx < source.length && /[ \t,]/.test(source[tailIdx] ?? "")) tailIdx++;
|
|
947
|
+
if (source[tailIdx] === "\n") tailIdx++;
|
|
948
|
+
return source.slice(0, m.index) + source.slice(tailIdx).replace(new RegExp(`^${indent}\\n`), "");
|
|
949
|
+
}
|
|
885
950
|
async function migrateSvelteConfig(cwd) {
|
|
886
951
|
const path = join7(cwd, "svelte.config.js");
|
|
887
952
|
let src;
|
|
@@ -891,11 +956,8 @@ async function migrateSvelteConfig(cwd) {
|
|
|
891
956
|
return false;
|
|
892
957
|
}
|
|
893
958
|
let next = src;
|
|
894
|
-
next = next
|
|
895
|
-
|
|
896
|
-
""
|
|
897
|
-
);
|
|
898
|
-
next = next.replace(/^\s*preprocess:\s*vitePreprocess\(\)\s*,?\s*\n/m, "");
|
|
959
|
+
next = dropPreprocessKey(next);
|
|
960
|
+
next = dropVitePreprocessImport(next);
|
|
899
961
|
if (next === src) return false;
|
|
900
962
|
await writeFile5(path, next, "utf-8");
|
|
901
963
|
return true;
|
|
@@ -1005,10 +1067,32 @@ function exportLetToProps(source) {
|
|
|
1005
1067
|
}
|
|
1006
1068
|
|
|
1007
1069
|
// src/recipes/svelte-5/codemods/dollar-restprops.ts
|
|
1070
|
+
function removeInterfaceBlock(source) {
|
|
1071
|
+
const re = /^\s*interface\s+\$\$Props\s*\{/m;
|
|
1072
|
+
let out = source;
|
|
1073
|
+
while (true) {
|
|
1074
|
+
const match = re.exec(out);
|
|
1075
|
+
if (!match) return out;
|
|
1076
|
+
const openBraceIdx = match.index + match[0].length - 1;
|
|
1077
|
+
let depth = 1;
|
|
1078
|
+
let i = openBraceIdx + 1;
|
|
1079
|
+
while (i < out.length && depth > 0) {
|
|
1080
|
+
const ch = out[i];
|
|
1081
|
+
if (ch === "{") depth++;
|
|
1082
|
+
else if (ch === "}") depth--;
|
|
1083
|
+
i++;
|
|
1084
|
+
}
|
|
1085
|
+
if (depth !== 0) return out;
|
|
1086
|
+
let endIdx = i;
|
|
1087
|
+
while (endIdx < out.length && /[ \t]/.test(out[endIdx] ?? "")) endIdx++;
|
|
1088
|
+
if (out[endIdx] === "\n") endIdx++;
|
|
1089
|
+
out = out.slice(0, match.index) + out.slice(endIdx);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1008
1092
|
function removeDollarRestProps(source) {
|
|
1009
1093
|
let next = source;
|
|
1010
1094
|
next = next.replace(/\$\$restProps/g, "rest");
|
|
1011
|
-
next = next
|
|
1095
|
+
next = removeInterfaceBlock(next);
|
|
1012
1096
|
return next;
|
|
1013
1097
|
}
|
|
1014
1098
|
|
|
@@ -1169,6 +1253,7 @@ function localPath(path, opts = {}) {
|
|
|
1169
1253
|
|
|
1170
1254
|
// src/inventory/json.ts
|
|
1171
1255
|
import { readFile as readFile9 } from "fs/promises";
|
|
1256
|
+
import { isAbsolute } from "path";
|
|
1172
1257
|
function validate(raw) {
|
|
1173
1258
|
if (!Array.isArray(raw)) {
|
|
1174
1259
|
throw new Error("inventory JSON must be an array of sites");
|
|
@@ -1181,6 +1266,11 @@ function validate(raw) {
|
|
|
1181
1266
|
if (typeof e.path !== "string" || e.path.length === 0) {
|
|
1182
1267
|
throw new Error(`inventory entry ${i} is missing required field: path`);
|
|
1183
1268
|
}
|
|
1269
|
+
if (!isAbsolute(e.path)) {
|
|
1270
|
+
throw new Error(
|
|
1271
|
+
`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.`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1184
1274
|
const site = { path: e.path };
|
|
1185
1275
|
if (typeof e.name === "string") site.name = e.name;
|
|
1186
1276
|
if (typeof e.repoUrl === "string") site.repoUrl = e.repoUrl;
|