@reddoorla/maintenance 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/bin.js +213 -48
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +52 -43
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/configs/lighthouse.d.ts +1 -1
- package/dist/configs/lighthouse.js +4 -1
- package/dist/configs/lighthouse.js.map +1 -1
- package/dist/configs/playwright-a11y.js +2 -1
- package/dist/configs/playwright-a11y.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +170 -44
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/{sync-configs-Z5a0iaDd.d.ts → sync-configs-7XIzzN_L.d.ts} +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolve as resolve2 } from "path";
|
|
|
10
10
|
|
|
11
11
|
// src/audits/util/spawn.ts
|
|
12
12
|
import { spawn } from "child_process";
|
|
13
|
-
var defaultSpawn = (cmd, args, opts = {}) => new Promise((
|
|
13
|
+
var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve7, reject) => {
|
|
14
14
|
const streaming = opts.streaming === true;
|
|
15
15
|
const child = spawn(cmd, [...args], {
|
|
16
16
|
cwd: opts.cwd,
|
|
@@ -33,7 +33,7 @@ var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve6, reject) => {
|
|
|
33
33
|
});
|
|
34
34
|
child.on("close", (code) => {
|
|
35
35
|
if (timer) clearTimeout(timer);
|
|
36
|
-
|
|
36
|
+
resolve7({ code: code ?? -1, stdout, stderr });
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
|
|
@@ -270,59 +270,64 @@ function extractAdvisoriesFromNpm(parsed) {
|
|
|
270
270
|
}
|
|
271
271
|
return [...roots.values()];
|
|
272
272
|
}
|
|
273
|
-
async function
|
|
273
|
+
async function runAuditTool(spawn2, cmd, args, cwd) {
|
|
274
|
+
let raw;
|
|
274
275
|
try {
|
|
275
|
-
|
|
276
|
+
raw = await spawn2(cmd, args, { cwd });
|
|
276
277
|
} catch (err) {
|
|
277
278
|
const e = err;
|
|
278
|
-
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) return {
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
async function securityAudit(ctx) {
|
|
283
|
-
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
284
|
-
const site = ctx.site;
|
|
285
|
-
const label = siteLabel3(site);
|
|
286
|
-
let used = "pnpm audit";
|
|
287
|
-
let raw = await tryRun(
|
|
288
|
-
spawn2,
|
|
289
|
-
"pnpm",
|
|
290
|
-
["audit", "--json", "--prod"],
|
|
291
|
-
site.path
|
|
292
|
-
);
|
|
293
|
-
if ("missing" in raw) {
|
|
294
|
-
used = "npm audit";
|
|
295
|
-
raw = await tryRun(spawn2, "npm", ["audit", "--json", "--omit=dev"], site.path);
|
|
296
|
-
}
|
|
297
|
-
if ("missing" in raw) {
|
|
298
|
-
return {
|
|
299
|
-
audit: "security",
|
|
300
|
-
site: label,
|
|
301
|
-
status: "skip",
|
|
302
|
-
summary: "neither pnpm nor npm is available on PATH"
|
|
303
|
-
};
|
|
279
|
+
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) return { kind: "missing" };
|
|
280
|
+
return { kind: "error", reason: `spawn failed: ${String(err).slice(0, 200)}` };
|
|
304
281
|
}
|
|
305
282
|
if (raw.code !== 0 && raw.code !== 1) {
|
|
306
283
|
return {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
status: "skip",
|
|
310
|
-
summary: `${used} exited with code ${raw.code}`,
|
|
311
|
-
details: { stderr: raw.stderr }
|
|
284
|
+
kind: "error",
|
|
285
|
+
reason: `exit ${raw.code}${raw.stderr ? `: ${raw.stderr.slice(0, 150)}` : ""}`
|
|
312
286
|
};
|
|
313
287
|
}
|
|
314
288
|
let parsed;
|
|
315
289
|
try {
|
|
316
|
-
parsed = JSON.parse(raw.stdout);
|
|
290
|
+
parsed = JSON.parse(raw.stdout || "{}");
|
|
317
291
|
} catch (err) {
|
|
318
|
-
return {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
}
|
|
325
329
|
}
|
|
330
|
+
const parsed = result.parsed;
|
|
326
331
|
const counts = {
|
|
327
332
|
low: parsed.metadata?.vulnerabilities?.low ?? 0,
|
|
328
333
|
moderate: parsed.metadata?.vulnerabilities?.moderate ?? 0,
|
|
@@ -352,7 +357,10 @@ var lighthouseConfig = {
|
|
|
352
357
|
ci: {
|
|
353
358
|
collect: {
|
|
354
359
|
url: ["http://localhost:5173/dev/a11y-fixtures"],
|
|
355
|
-
|
|
360
|
+
// `npm run vite:dev` works on both pnpm and npm sites — pnpm respects
|
|
361
|
+
// the `run` form too. Keeps this config portable across the fleet
|
|
362
|
+
// while sites transition to pnpm.
|
|
363
|
+
startServerCommand: "npm run vite:dev",
|
|
356
364
|
startServerReadyPattern: "ready in",
|
|
357
365
|
startServerReadyTimeout: 12e4,
|
|
358
366
|
numberOfRuns: 1,
|
|
@@ -504,7 +512,8 @@ var playwrightA11yConfig = defineConfig({
|
|
|
504
512
|
}
|
|
505
513
|
],
|
|
506
514
|
webServer: {
|
|
507
|
-
|
|
515
|
+
// Portable across pnpm and npm sites — pnpm respects `npm run` too.
|
|
516
|
+
command: "npm run vite:dev",
|
|
508
517
|
url: "http://localhost:5173/dev/a11y-fixtures",
|
|
509
518
|
reuseExistingServer: !process.env.CI,
|
|
510
519
|
timeout: 12e4
|
|
@@ -1541,12 +1550,151 @@ async function runUpgradeCommand(upgradeName, site, opts = {}) {
|
|
|
1541
1550
|
return { output, code };
|
|
1542
1551
|
}
|
|
1543
1552
|
|
|
1553
|
+
// src/cli/commands/convert-to-pnpm.ts
|
|
1554
|
+
import { resolve as resolve6 } from "path";
|
|
1555
|
+
|
|
1556
|
+
// src/recipes/convert-to-pnpm.ts
|
|
1557
|
+
import { rm as rm3, stat as stat2 } from "fs/promises";
|
|
1558
|
+
import { join as join14 } from "path";
|
|
1559
|
+
|
|
1560
|
+
// src/recipes/convert-to-pnpm/script-rewrites.ts
|
|
1561
|
+
function rewriteScriptForPnpm(script) {
|
|
1562
|
+
let out = script;
|
|
1563
|
+
out = out.replace(/\bnpm run(?=\s)/g, "pnpm run");
|
|
1564
|
+
out = out.replace(/\bnpx(?=\s)/g, "pnpm dlx");
|
|
1565
|
+
return out;
|
|
1566
|
+
}
|
|
1567
|
+
function rewriteScriptsForPnpm(scripts) {
|
|
1568
|
+
const next = {};
|
|
1569
|
+
let changedCount = 0;
|
|
1570
|
+
for (const [name, value] of Object.entries(scripts)) {
|
|
1571
|
+
const rewritten = rewriteScriptForPnpm(value);
|
|
1572
|
+
next[name] = rewritten;
|
|
1573
|
+
if (rewritten !== value) changedCount++;
|
|
1574
|
+
}
|
|
1575
|
+
return { scripts: next, changedCount };
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/recipes/convert-to-pnpm.ts
|
|
1579
|
+
var DEFAULT_PNPM_VERSION = "10.33.1";
|
|
1580
|
+
async function exists(path) {
|
|
1581
|
+
try {
|
|
1582
|
+
await stat2(path);
|
|
1583
|
+
return true;
|
|
1584
|
+
} catch {
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
function siteLabel9(site) {
|
|
1589
|
+
return site.name ?? site.path;
|
|
1590
|
+
}
|
|
1591
|
+
async function convertToPnpm(site, opts = {}) {
|
|
1592
|
+
const label = siteLabel9(site);
|
|
1593
|
+
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
1594
|
+
const pnpmVersion = opts.pnpmVersion ?? DEFAULT_PNPM_VERSION;
|
|
1595
|
+
const pnpmLockPath = join14(site.path, "pnpm-lock.yaml");
|
|
1596
|
+
const npmLockPath = join14(site.path, "package-lock.json");
|
|
1597
|
+
const yarnLockPath = join14(site.path, "yarn.lock");
|
|
1598
|
+
if (await exists(pnpmLockPath)) {
|
|
1599
|
+
return {
|
|
1600
|
+
recipe: "convert-to-pnpm",
|
|
1601
|
+
site: label,
|
|
1602
|
+
status: "noop",
|
|
1603
|
+
commits: [],
|
|
1604
|
+
notes: "site already has pnpm-lock.yaml"
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
const hasNpmLock = await exists(npmLockPath);
|
|
1608
|
+
const hasYarnLock = await exists(yarnLockPath);
|
|
1609
|
+
if (!hasNpmLock && !hasYarnLock) {
|
|
1610
|
+
return {
|
|
1611
|
+
recipe: "convert-to-pnpm",
|
|
1612
|
+
site: label,
|
|
1613
|
+
status: "noop",
|
|
1614
|
+
commits: [],
|
|
1615
|
+
notes: "no convertible lockfile (package-lock.json or yarn.lock) at site root"
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
if (!await isWorkingTreeClean(site.path)) {
|
|
1619
|
+
throw new Error(`refusing to run: working tree is not clean at ${site.path}`);
|
|
1620
|
+
}
|
|
1621
|
+
const branch = branchName("convert-to-pnpm");
|
|
1622
|
+
await createBranch(site.path, branch);
|
|
1623
|
+
const shas = [];
|
|
1624
|
+
if (hasNpmLock) await rm3(npmLockPath, { force: true });
|
|
1625
|
+
if (hasYarnLock) await rm3(yarnLockPath, { force: true });
|
|
1626
|
+
const sourceLock = hasNpmLock ? "package-lock.json" : "yarn.lock";
|
|
1627
|
+
const lockSha = await commit(site.path, `chore(pnpm): remove ${sourceLock}`);
|
|
1628
|
+
if (lockSha) shas.push(lockSha);
|
|
1629
|
+
const pkgPath = join14(site.path, "package.json");
|
|
1630
|
+
const pkg = await readPackageJson(pkgPath);
|
|
1631
|
+
const next = { ...pkg, packageManager: `pnpm@${pnpmVersion}` };
|
|
1632
|
+
if (pkg.scripts && typeof pkg.scripts === "object") {
|
|
1633
|
+
const { scripts: rewritten, changedCount } = rewriteScriptsForPnpm(
|
|
1634
|
+
pkg.scripts
|
|
1635
|
+
);
|
|
1636
|
+
if (changedCount > 0) {
|
|
1637
|
+
next.scripts = rewritten;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
await writePackageJson(pkgPath, next);
|
|
1641
|
+
const pkgSha = await commit(site.path, "chore(pnpm): pin packageManager + rewrite npm scripts");
|
|
1642
|
+
if (pkgSha) shas.push(pkgSha);
|
|
1643
|
+
const installResult = await spawn2("pnpm", ["install"], {
|
|
1644
|
+
cwd: site.path,
|
|
1645
|
+
streaming: true
|
|
1646
|
+
});
|
|
1647
|
+
if (installResult.code !== 0) {
|
|
1648
|
+
return {
|
|
1649
|
+
recipe: "convert-to-pnpm",
|
|
1650
|
+
site: label,
|
|
1651
|
+
status: "failed",
|
|
1652
|
+
commits: shas,
|
|
1653
|
+
notes: `pnpm install failed (exit ${installResult.code}). branch ${branch} left for inspection.`
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
const installSha = await commit(site.path, "chore(pnpm): add pnpm-lock.yaml");
|
|
1657
|
+
if (installSha) shas.push(installSha);
|
|
1658
|
+
return {
|
|
1659
|
+
recipe: "convert-to-pnpm",
|
|
1660
|
+
site: label,
|
|
1661
|
+
status: "applied",
|
|
1662
|
+
commits: shas,
|
|
1663
|
+
notes: `branch: ${branch}`
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/cli/commands/convert-to-pnpm.ts
|
|
1668
|
+
function formatResult4(r) {
|
|
1669
|
+
if (r.status === "noop") return `[${r.site}] noop: ${r.notes ?? ""}`;
|
|
1670
|
+
if (r.status === "failed") return `[${r.site}] failed: ${r.notes ?? ""}`;
|
|
1671
|
+
return `[${r.site}] applied: ${r.commits.length} commit(s)
|
|
1672
|
+
${r.notes ?? ""}`;
|
|
1673
|
+
}
|
|
1674
|
+
async function runConvertToPnpmCommand(site, opts) {
|
|
1675
|
+
const cwd = opts.cwd ? resolve6(opts.cwd) : process.cwd();
|
|
1676
|
+
let sites = await resolveSites({
|
|
1677
|
+
...site !== void 0 ? { site } : {},
|
|
1678
|
+
...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
|
|
1679
|
+
cwd
|
|
1680
|
+
});
|
|
1681
|
+
if (opts.fleet) {
|
|
1682
|
+
const workdir = opts.workdir ?? `${process.env.HOME ?? ""}/.reddoor-maint/sites`;
|
|
1683
|
+
sites = await Promise.all(sites.map((s) => cloneIfNeeded(s, { workdir })));
|
|
1684
|
+
}
|
|
1685
|
+
const results = [];
|
|
1686
|
+
for (const s of sites) results.push(await convertToPnpm(s));
|
|
1687
|
+
const output = results.map(formatResult4).join("\n");
|
|
1688
|
+
const code = results.some((r) => r.status === "failed") ? 1 : 0;
|
|
1689
|
+
return { output, code };
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1544
1692
|
// src/cli/version.ts
|
|
1545
1693
|
import { readFileSync } from "fs";
|
|
1546
|
-
import { join as
|
|
1694
|
+
import { join as join15 } from "path";
|
|
1547
1695
|
function resolvePackageVersion(fromDir) {
|
|
1548
1696
|
try {
|
|
1549
|
-
const raw = readFileSync(
|
|
1697
|
+
const raw = readFileSync(join15(fromDir, "..", "..", "package.json"), "utf-8");
|
|
1550
1698
|
const pkg = JSON.parse(raw);
|
|
1551
1699
|
return pkg.version ?? "unknown";
|
|
1552
1700
|
} catch {
|
|
@@ -1567,7 +1715,8 @@ var AUDIT_DESCRIPTIONS = {
|
|
|
1567
1715
|
var RECIPE_DESCRIPTIONS = {
|
|
1568
1716
|
"sync-configs": "Overwrite a site's canonical configs to match @reddoorla/maintenance.",
|
|
1569
1717
|
"bump-deps": "Bump dependencies and commit the lockfile change.",
|
|
1570
|
-
"svelte-4-to-5": "Run the 7-commit Svelte 4 \u2192 5 upgrade recipe."
|
|
1718
|
+
"svelte-4-to-5": "Run the 7-commit Svelte 4 \u2192 5 upgrade recipe.",
|
|
1719
|
+
"convert-to-pnpm": "Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts)."
|
|
1571
1720
|
};
|
|
1572
1721
|
var cli = cac("reddoor-maint");
|
|
1573
1722
|
cli.option("--cwd <path>", "Override working directory (default: process.cwd())");
|
|
@@ -1634,6 +1783,22 @@ cli.command("upgrade <upgrade> [site]", "Run a named upgrade recipe (svelte-4-to
|
|
|
1634
1783
|
}
|
|
1635
1784
|
}
|
|
1636
1785
|
);
|
|
1786
|
+
cli.command(
|
|
1787
|
+
"convert-to-pnpm [site]",
|
|
1788
|
+
"Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts)."
|
|
1789
|
+
).option("--fleet <inventory>", "Inventory file (.json or .mjs/.js)").option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
1790
|
+
async (site, opts) => {
|
|
1791
|
+
try {
|
|
1792
|
+
const { output, code } = await runConvertToPnpmCommand(site, opts);
|
|
1793
|
+
console.log(output);
|
|
1794
|
+
process.exit(code);
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
const e = err;
|
|
1797
|
+
console.error(opts.verbose ? e.stack ?? e.message : e.message ?? String(err));
|
|
1798
|
+
process.exit(e.exitCode ?? 1);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1637
1802
|
cli.help();
|
|
1638
1803
|
cli.version(version);
|
|
1639
1804
|
cli.parse();
|