@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/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
- child.stdout.on("data", (chunk) => stdout += String(chunk));
12
- child.stderr.on("data", (chunk) => stderr += String(chunk));
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, relative } from "path";
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 filesToLint = relFiles.map((f) => join2(site.path, f));
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 file of filesToLint) {
175
- const source = await readFile2(file, "utf-8");
176
- const options = await prettierResolveConfig(file) ?? {};
177
- const ok = await prettierCheck(source, { ...options, filepath: file });
178
- if (!ok) prettierUnformatted.push(relative(site.path, file));
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 ${filesToLint.length} files` : `${eslintErrors} eslint errors, ${eslintWarnings} warnings, ${prettierUnformatted.length} unformatted`;
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: filesToLint.length
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 "moderate";
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 out = [];
225
- for (const [name, v] of Object.entries(parsed.vulnerabilities ?? {})) {
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
- let title = name;
228
- let url;
229
- if (Array.isArray(v.via)) {
230
- const detailed = v.via.find(
231
- (entry) => typeof entry === "object" && entry !== null
232
- );
233
- if (detailed) {
234
- title = detailed.title ?? name;
235
- url = detailed.url;
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 out;
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)], { cwd: site.path });
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.replace(
895
- /^import\s+\{\s*vitePreprocess\s*\}\s+from\s+["']@sveltejs\/vite-plugin-svelte["'];\n/m,
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.replace(/^\s*interface\s+\$\$Props\s*\{[^}]*\}\s*\n/gm, "");
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;