@cellajs/cli 0.6.5 → 0.6.7
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/README.md +4 -4
- package/dist/a11y.js.map +1 -1
- package/dist/bench.js +1 -1
- package/dist/bench.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/drive.js.map +1 -1
- package/dist/index.js +408 -253
- package/dist/index.js.map +1 -1
- package/dist/screenshots.js.map +1 -1
- package/dist/style.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ async function importFromApp(forkPath, file) {
|
|
|
49
49
|
setEnv("NODE_ENV", "development");
|
|
50
50
|
return import(pathToFileURL(path).href);
|
|
51
51
|
}
|
|
52
|
+
var movedPath = (forkPath, current, legacy) => !existsSync(join(forkPath, current)) && existsSync(join(forkPath, legacy)) ? legacy : current;
|
|
52
53
|
async function loadAppFacts(forkPath) {
|
|
53
54
|
const parsed2 = appConfigSchema.safeParse((await importFromApp(forkPath, "shared/index.ts")).appConfig);
|
|
54
55
|
if (!parsed2.success)
|
|
@@ -233,7 +234,7 @@ import process6 from "process";
|
|
|
233
234
|
// package.json
|
|
234
235
|
var package_default = {
|
|
235
236
|
name: "@cellajs/cli",
|
|
236
|
-
version: "0.6.
|
|
237
|
+
version: "0.6.7",
|
|
237
238
|
private: false,
|
|
238
239
|
license: "MIT",
|
|
239
240
|
description: "CLI for managing your monorepo and upgrading it with upstream Cella.",
|
|
@@ -761,6 +762,15 @@ async function gitMv(cwd, oldPath, newPath) {
|
|
|
761
762
|
await mkdir2(newDir, { recursive: true });
|
|
762
763
|
await git(["mv", "-f", oldPath, newPath], cwd);
|
|
763
764
|
}
|
|
765
|
+
async function moveFromRef(cwd, ref, from, to) {
|
|
766
|
+
const [mode, , blob] = (await git(["ls-tree", ref, "--", from], cwd)).split(/\s+/);
|
|
767
|
+
if (!blob) throw new Error(`${ref} holds no ${from}`);
|
|
768
|
+
await gitRm(cwd, [to], { cached: true });
|
|
769
|
+
await git(["update-index", "--add", "--cacheinfo", `${mode},${blob},${to}`], cwd);
|
|
770
|
+
await mkdir2(dirname3(join3(cwd, to)), { recursive: true });
|
|
771
|
+
await git(["checkout-index", "-f", "--", to], cwd);
|
|
772
|
+
await removeFileFully(cwd, from);
|
|
773
|
+
}
|
|
764
774
|
async function fileExistsInWorktree(cwd, filePath) {
|
|
765
775
|
return existsSync3(join3(cwd, filePath));
|
|
766
776
|
}
|
|
@@ -1309,6 +1319,9 @@ function printUpstreamOverrideChanges(result) {
|
|
|
1309
1319
|
for (const entry of report[list2].removed) {
|
|
1310
1320
|
lines.push(` ${colors_default.red("\u2212")} ${list2}: ${entry}${colors_default.dim(" \xB7 dropped upstream, still in your config")}`);
|
|
1311
1321
|
}
|
|
1322
|
+
for (const { from, to } of report[list2].moved ?? []) {
|
|
1323
|
+
lines.push(colors_default.dim(` ~ ${list2}: ${from} \u2192 ${to} \xB7 renamed upstream, your config never had it`));
|
|
1324
|
+
}
|
|
1312
1325
|
}
|
|
1313
1326
|
console.info(
|
|
1314
1327
|
`${warningMark} ${colors_default.yellow(`upstream changed its CLI config \xB7 ${plural(lines.length, "entry", "entries")} to review`)}`
|
|
@@ -1373,11 +1386,12 @@ var STARTUP_TIMEOUT_MS = 5 * 6e4;
|
|
|
1373
1386
|
var STOP_TIMEOUT_MS = 1e4;
|
|
1374
1387
|
var DATABASE = "testbed";
|
|
1375
1388
|
var DATABASE_SERVICE = "db_testbed";
|
|
1376
|
-
var
|
|
1389
|
+
var showcaseFile = (forkPath) => movedPath(forkPath, "backend/scripts/seeds/showcase.json", "json/showcase.json");
|
|
1390
|
+
var showcasePath = (forkPath) => join4(forkPath, showcaseFile(forkPath));
|
|
1377
1391
|
var hasShowcase = (forkPath) => existsSync4(showcasePath(forkPath));
|
|
1378
1392
|
function showcaseAdmin(forkPath) {
|
|
1379
1393
|
const { admin } = JSON.parse(readFileSync2(showcasePath(forkPath), "utf8"));
|
|
1380
|
-
if (typeof admin?.email !== "string") throw new Error(
|
|
1394
|
+
if (typeof admin?.email !== "string") throw new Error(`${showcaseFile(forkPath)} names no admin email`);
|
|
1381
1395
|
return admin.email;
|
|
1382
1396
|
}
|
|
1383
1397
|
var shiftPort = (url) => {
|
|
@@ -1392,8 +1406,8 @@ async function resolveTestbed(forkPath, data = "seed") {
|
|
|
1392
1406
|
throw new Error("shared/src/local-database.ts names no testbed database: upgrade the app from cella first");
|
|
1393
1407
|
}
|
|
1394
1408
|
const composeEnv = databases.composeEnv();
|
|
1395
|
-
const envFile = join4(forkPath, "backend/.env");
|
|
1396
|
-
const fileEnv =
|
|
1409
|
+
const envFile = [join4(forkPath, ".env"), join4(forkPath, "backend/.env")].find((path) => existsSync4(path));
|
|
1410
|
+
const fileEnv = envFile ? parseEnv(readFileSync2(envFile, "utf8")) : {};
|
|
1397
1411
|
const portOffset = facts.devPortOffset + PORT_SHIFT;
|
|
1398
1412
|
return {
|
|
1399
1413
|
forkPath,
|
|
@@ -1402,7 +1416,7 @@ async function resolveTestbed(forkPath, data = "seed") {
|
|
|
1402
1416
|
databasePort,
|
|
1403
1417
|
portOffset,
|
|
1404
1418
|
env: {
|
|
1405
|
-
// The seed creates the admin that
|
|
1419
|
+
// The seed creates the admin that the env file names, and a command signs in as that user
|
|
1406
1420
|
...fileEnv.ADMIN_EMAIL ? { ADMIN_EMAIL: fileEnv.ADMIN_EMAIL } : {},
|
|
1407
1421
|
...getEnvSnapshot(),
|
|
1408
1422
|
...data === "showcase" ? { ADMIN_EMAIL: showcaseAdmin(forkPath) } : {},
|
|
@@ -1666,7 +1680,7 @@ async function withTestbed(forkPath, use, { fresh = false, destructive = false,
|
|
|
1666
1680
|
}
|
|
1667
1681
|
async function runTestbed(forkPath, options) {
|
|
1668
1682
|
if (options.job === "stop") return stopTestbed(await resolveTestbed(forkPath));
|
|
1669
|
-
if (options.showcase && !hasShowcase(forkPath)) throw new Error(
|
|
1683
|
+
if (options.showcase && !hasShowcase(forkPath)) throw new Error(`this app keeps no ${showcaseFile(forkPath)}`);
|
|
1670
1684
|
const data = options.showcase ? "showcase" : "seed";
|
|
1671
1685
|
if (options.command.length > 0) {
|
|
1672
1686
|
return withTestbed(forkPath, (testbed2) => runCommand(forkPath, testbed2.env, options.command), { ...options, data });
|
|
@@ -1701,7 +1715,7 @@ var testbedCommand = {
|
|
|
1701
1715
|
],
|
|
1702
1716
|
options: [
|
|
1703
1717
|
["--fresh", "start from an empty database: migrate and seed it again"],
|
|
1704
|
-
["--showcase", "hold the showcase
|
|
1718
|
+
["--showcase", "hold the showcase seed, which the screenshots shoot, for a review loop"]
|
|
1705
1719
|
],
|
|
1706
1720
|
passthrough: [
|
|
1707
1721
|
"command",
|
|
@@ -1716,8 +1730,8 @@ var testbedCommand = {
|
|
|
1716
1730
|
};
|
|
1717
1731
|
|
|
1718
1732
|
// src/tools/a11y/audit.ts
|
|
1719
|
-
import { mkdirSync as mkdirSync3, statSync, writeFileSync as
|
|
1720
|
-
import { join as
|
|
1733
|
+
import { mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1734
|
+
import { join as join10 } from "path";
|
|
1721
1735
|
|
|
1722
1736
|
// src/tools/a11y/axe-scan.ts
|
|
1723
1737
|
import axe from "axe-core";
|
|
@@ -2387,8 +2401,97 @@ function recordDecisions(forkPath, decisions) {
|
|
|
2387
2401
|
}
|
|
2388
2402
|
|
|
2389
2403
|
// src/tools/a11y/probes/page-structure.ts
|
|
2390
|
-
import { readFileSync as
|
|
2404
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2405
|
+
import { join as join8 } from "path";
|
|
2406
|
+
|
|
2407
|
+
// src/tools/i18n/locales.ts
|
|
2408
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2391
2409
|
import { join as join7 } from "path";
|
|
2410
|
+
var localesDir = (forkPath) => movedPath(forkPath, "shared/locales", "locales");
|
|
2411
|
+
var MANIFEST = "namespaces.json";
|
|
2412
|
+
var SOURCE_LANGUAGE = "en";
|
|
2413
|
+
var entryPattern = /^(\s+)("(?:[^"\\]|\\.)*")\s*:\s*("(?:[^"\\]|\\.)*"),?\s*$/;
|
|
2414
|
+
function parseLocaleFile(path, text2) {
|
|
2415
|
+
if (text2.trim() === "{}") return { path, indent: " ", entries: [] };
|
|
2416
|
+
const lines = text2.replace(/\n$/, "").split("\n");
|
|
2417
|
+
if (lines[0] !== "{" || lines.at(-1) !== "}") return "not one JSON object with one entry per line";
|
|
2418
|
+
const entries = [];
|
|
2419
|
+
let indent = " ";
|
|
2420
|
+
for (const line of lines.slice(1, -1)) {
|
|
2421
|
+
const match = entryPattern.exec(line);
|
|
2422
|
+
if (!match) return `cannot read the line ${JSON.stringify(line.trim())}`;
|
|
2423
|
+
indent = match[1];
|
|
2424
|
+
entries.push({ key: JSON.parse(match[2]), value: JSON.parse(match[3]), raw: [match[2], match[3]] });
|
|
2425
|
+
}
|
|
2426
|
+
return { path, indent, entries };
|
|
2427
|
+
}
|
|
2428
|
+
function serializeLocaleFile(file) {
|
|
2429
|
+
if (file.entries.length === 0) return "{}\n";
|
|
2430
|
+
return `{
|
|
2431
|
+
${file.entries.map(({ raw }) => `${file.indent}${raw[0]}: ${raw[1]}`).join(",\n")}
|
|
2432
|
+
}
|
|
2433
|
+
`;
|
|
2434
|
+
}
|
|
2435
|
+
function writeLocaleFile(forkPath, file) {
|
|
2436
|
+
writeFileSync3(join7(forkPath, file.path), serializeLocaleFile(file));
|
|
2437
|
+
}
|
|
2438
|
+
var textsOf = (file) => Object.fromEntries((file?.entries ?? []).map(({ key, value }) => [key, value]));
|
|
2439
|
+
var readJsonObject = (path) => {
|
|
2440
|
+
const parsed2 = JSON.parse(readFileSync4(path, "utf8"));
|
|
2441
|
+
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) throw new Error(`${path} is not a JSON object`);
|
|
2442
|
+
return Object.fromEntries(Object.entries(parsed2));
|
|
2443
|
+
};
|
|
2444
|
+
function readLocales(forkPath) {
|
|
2445
|
+
const localesPath = localesDir(forkPath);
|
|
2446
|
+
const dir = join7(forkPath, localesPath);
|
|
2447
|
+
if (!existsSync7(join7(dir, SOURCE_LANGUAGE))) throw new Error(`no ${localesPath}/${SOURCE_LANGUAGE}/ in ${forkPath}`);
|
|
2448
|
+
const languages = readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort((a, b) => a === SOURCE_LANGUAGE ? -1 : b === SOURCE_LANGUAGE ? 1 : a.localeCompare(b));
|
|
2449
|
+
const files2 = /* @__PURE__ */ new Map();
|
|
2450
|
+
for (const language2 of languages) {
|
|
2451
|
+
for (const name of readdirSync(join7(dir, language2)).filter((name2) => name2.endsWith(".json"))) {
|
|
2452
|
+
const path = `${localesPath}/${language2}/${name}`;
|
|
2453
|
+
const parsed2 = parseLocaleFile(path, readFileSync4(join7(forkPath, path), "utf8"));
|
|
2454
|
+
if (typeof parsed2 === "string") throw new Error(`${path}: ${parsed2}; pnpm lint:fix writes one entry per line`);
|
|
2455
|
+
files2.set(`${language2}/${name.slice(0, -".json".length)}`, parsed2);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
const manifestPath = join7(dir, MANIFEST);
|
|
2459
|
+
if (!existsSync7(manifestPath)) throw new Error(`no ${localesPath}/${MANIFEST}: upgrade the app from cella first`);
|
|
2460
|
+
const merged = readJsonObject(manifestPath);
|
|
2461
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
2462
|
+
for (const [namespace, sources] of Object.entries(merged)) {
|
|
2463
|
+
if (!Array.isArray(sources) || !sources.every((source) => typeof source === "string")) {
|
|
2464
|
+
throw new Error(`${localesPath}/${MANIFEST}: ${namespace} must list file names`);
|
|
2465
|
+
}
|
|
2466
|
+
namespaces.set(namespace, sources);
|
|
2467
|
+
}
|
|
2468
|
+
const mergedFiles = new Set([...namespaces.values()].flat());
|
|
2469
|
+
const ownFiles = [...new Set([...files2.keys()].map((key) => key.split("/")[1]))].filter(
|
|
2470
|
+
(file) => !mergedFiles.has(file)
|
|
2471
|
+
);
|
|
2472
|
+
for (const file of ownFiles.sort()) namespaces.set(file, [file]);
|
|
2473
|
+
return { dir: localesPath, languages, namespaces, files: files2 };
|
|
2474
|
+
}
|
|
2475
|
+
function namespaceTexts(locales, language2, namespace) {
|
|
2476
|
+
const sources = locales.namespaces.get(namespace) ?? [];
|
|
2477
|
+
return Object.assign({}, ...sources.map((file) => textsOf(locales.files.get(`${language2}/${file}`))));
|
|
2478
|
+
}
|
|
2479
|
+
var lineOf = (file, key) => file.entries.findIndex((entry) => entry.key === key) + 2;
|
|
2480
|
+
var pluralSuffix = /_(ordinal_)?(zero|one|two|few|many|other)$/;
|
|
2481
|
+
function pluralForm(key) {
|
|
2482
|
+
const match = pluralSuffix.exec(key);
|
|
2483
|
+
if (!match) return void 0;
|
|
2484
|
+
return { base: key.slice(0, match.index), category: match[2], ordinal: !!match[1] };
|
|
2485
|
+
}
|
|
2486
|
+
var withPluralForms = (key) => [
|
|
2487
|
+
key,
|
|
2488
|
+
...["zero", "one", "two", "few", "many", "other"].flatMap((category) => [
|
|
2489
|
+
`${key}_${category}`,
|
|
2490
|
+
`${key}_ordinal_${category}`
|
|
2491
|
+
])
|
|
2492
|
+
];
|
|
2493
|
+
|
|
2494
|
+
// src/tools/a11y/probes/page-structure.ts
|
|
2392
2495
|
var titles = { criteria: ["2.4.2"], check: "probe:titles", what: "A distinct, descriptive page title" };
|
|
2393
2496
|
var language = {
|
|
2394
2497
|
criteria: ["3.1.1"],
|
|
@@ -2422,7 +2525,7 @@ var pageStructure = async (page, state, { app }) => {
|
|
|
2422
2525
|
var languageSwitch = async (page, state, { app, forkPath }) => {
|
|
2423
2526
|
if (state.open) return [];
|
|
2424
2527
|
const names = JSON.parse(
|
|
2425
|
-
|
|
2528
|
+
readFileSync5(join8(forkPath, localesDir(forkPath), app.defaultLanguage, "common.json"), "utf8")
|
|
2426
2529
|
);
|
|
2427
2530
|
const findings2 = [];
|
|
2428
2531
|
for (const other of app.languages.filter((lng) => lng !== app.defaultLanguage)) {
|
|
@@ -2756,15 +2859,15 @@ var inputs = async (page, state) => {
|
|
|
2756
2859
|
};
|
|
2757
2860
|
|
|
2758
2861
|
// src/tools/a11y/review-packet.ts
|
|
2759
|
-
import { mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as
|
|
2760
|
-
import { join as
|
|
2761
|
-
var packetDir = (run, stateId) =>
|
|
2862
|
+
import { mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2863
|
+
import { join as join9 } from "path";
|
|
2864
|
+
var packetDir = (run, stateId) => join9(run.resultsDir, "review", stateId);
|
|
2762
2865
|
function writePacketFile(run, stateId, name, content) {
|
|
2763
2866
|
mkdirSync2(packetDir(run, stateId), { recursive: true });
|
|
2764
|
-
|
|
2867
|
+
writeFileSync4(join9(packetDir(run, stateId), name), content);
|
|
2765
2868
|
}
|
|
2766
2869
|
function removePacketFile(run, stateId, name) {
|
|
2767
|
-
rmSync2(
|
|
2870
|
+
rmSync2(join9(packetDir(run, stateId), name), { force: true });
|
|
2768
2871
|
}
|
|
2769
2872
|
function pageFacts() {
|
|
2770
2873
|
const text2 = (el) => (el?.textContent ?? "").trim().replace(/\s+/g, " ");
|
|
@@ -3224,8 +3327,8 @@ var seconds = (since) => `${((Date.now() - since) / 1e3).toFixed(1)}s`;
|
|
|
3224
3327
|
async function runAudit(run, scope, testbed, options) {
|
|
3225
3328
|
const startedAt = Date.now();
|
|
3226
3329
|
const email = options.email ?? testbed.env.ADMIN_EMAIL;
|
|
3227
|
-
if (!email) throw new Error("no audit user: pass --email, or set ADMIN_EMAIL in
|
|
3228
|
-
const builtAt = statSync(
|
|
3330
|
+
if (!email) throw new Error("no audit user: pass --email, or set ADMIN_EMAIL in .env");
|
|
3331
|
+
const builtAt = statSync(join10(run.forkPath, "frontend/dist/index.html"), { throwIfNoEntry: false })?.mtime;
|
|
3229
3332
|
printStep("auditing", `${testbed.frontendUrl}, frontend build of ${builtAt?.toLocaleString() ?? "unknown date"}`);
|
|
3230
3333
|
const states = options.states.length > 0 ? scope.states.filter((state) => options.states.includes(state.id)) : scope.states;
|
|
3231
3334
|
const session = await startAudit(run.forkPath, scope, { email, env: testbed.env });
|
|
@@ -3265,12 +3368,12 @@ async function runAudit(run, scope, testbed, options) {
|
|
|
3265
3368
|
mkdirSync3(run.resultsDir, { recursive: true });
|
|
3266
3369
|
const axe2 = summarizeAxe(scans);
|
|
3267
3370
|
for (const [id, items] of axe2.evidence) for (const item of items) evidence.add([id], item);
|
|
3268
|
-
|
|
3371
|
+
writeFileSync5(join10(run.resultsDir, "axe.json"), `${JSON.stringify({ ...axe2.report, errors }, null, 2)}
|
|
3269
3372
|
`);
|
|
3270
3373
|
evidence.addFindings(acrossStates(findings2));
|
|
3271
3374
|
runCodeChecks(evidence, run, scope.sources);
|
|
3272
3375
|
const skipped2 = Object.keys(errors).length;
|
|
3273
|
-
const target = options.states.length > 0 || skipped2 ?
|
|
3376
|
+
const target = options.states.length > 0 || skipped2 ? join10(run.resultsDir, "ledger-partial.json") : join10(run.forkPath, ledgerFile);
|
|
3274
3377
|
const ledger = writeLedger(
|
|
3275
3378
|
run.forkPath,
|
|
3276
3379
|
evidence.byCriterion,
|
|
@@ -3294,10 +3397,10 @@ async function runAudit(run, scope, testbed, options) {
|
|
|
3294
3397
|
}
|
|
3295
3398
|
|
|
3296
3399
|
// src/tools/a11y/decide.ts
|
|
3297
|
-
import { readFileSync as
|
|
3400
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3298
3401
|
import { resolve as resolve4 } from "path";
|
|
3299
3402
|
function runDecide(forkPath, { file, decision }) {
|
|
3300
|
-
const input2 = file ? JSON.parse(
|
|
3403
|
+
const input2 = file ? JSON.parse(readFileSync6(resolve4(file), "utf8")) : [decision];
|
|
3301
3404
|
if (!Array.isArray(input2)) throw new Error(`${file} holds no list of decisions`);
|
|
3302
3405
|
const decisions = input2.map(parseDecision);
|
|
3303
3406
|
const ledger = recordDecisions(forkPath, decisions);
|
|
@@ -3311,8 +3414,8 @@ function runDecide(forkPath, { file, decision }) {
|
|
|
3311
3414
|
|
|
3312
3415
|
// src/tools/a11y/report.ts
|
|
3313
3416
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
3314
|
-
import { copyFileSync, mkdirSync as mkdirSync4, writeFileSync as
|
|
3315
|
-
import { dirname as dirname4, join as
|
|
3417
|
+
import { copyFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3418
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
3316
3419
|
var statusLabel = {
|
|
3317
3420
|
supports: "Supports",
|
|
3318
3421
|
"partially-supports": "Partially Supports",
|
|
@@ -3431,7 +3534,7 @@ Not evaluated.
|
|
|
3431
3534
|
|
|
3432
3535
|
This report describes the product as tested on the report date. Accessibility can change as the product changes.
|
|
3433
3536
|
`;
|
|
3434
|
-
const tracked = spawnSync5("git", ["ls-files", "frontend/src", "backend/src", "locales"], {
|
|
3537
|
+
const tracked = spawnSync5("git", ["ls-files", "frontend/src", "backend/src", "shared/locales", "locales"], {
|
|
3435
3538
|
cwd: forkPath,
|
|
3436
3539
|
encoding: "utf8"
|
|
3437
3540
|
}).stdout.split("\n");
|
|
@@ -3458,7 +3561,7 @@ NVDA and Firefox on Windows, keyboard only, in light and dark mode.
|
|
|
3458
3561
|
|
|
3459
3562
|
## Confirm: decided by an agent (${byAgent.length})
|
|
3460
3563
|
|
|
3461
|
-
The agent judged these from the review packets in \`${
|
|
3564
|
+
The agent judged these from the review packets in \`${join11(resultsDir, "review")}/\`. Check its evidence, then record your own
|
|
3462
3565
|
decision; a wrong Supports here ends up in the published report.
|
|
3463
3566
|
|
|
3464
3567
|
${byAgent.map((r) => {
|
|
@@ -3502,11 +3605,11 @@ ${failing.map((r) => {
|
|
|
3502
3605
|
}).join("\n")}
|
|
3503
3606
|
`;
|
|
3504
3607
|
mkdirSync4(resultsDir, { recursive: true });
|
|
3505
|
-
|
|
3506
|
-
|
|
3608
|
+
writeFileSync6(join11(resultsDir, "vpat-draft.md"), vpat);
|
|
3609
|
+
writeFileSync6(join11(resultsDir, "manual-pass.md"), checklist);
|
|
3507
3610
|
const html = toHtml(vpat, `${app.name} Accessibility Conformance Report`);
|
|
3508
|
-
const pdfPath =
|
|
3509
|
-
|
|
3611
|
+
const pdfPath = join11(resultsDir, "conformance-report.pdf");
|
|
3612
|
+
writeFileSync6(join11(resultsDir, "conformance-report.html"), html);
|
|
3510
3613
|
const { chromium } = await loadPlaywright(forkPath);
|
|
3511
3614
|
const browser = await chromium.launch();
|
|
3512
3615
|
try {
|
|
@@ -3526,12 +3629,12 @@ ${failing.map((r) => {
|
|
|
3526
3629
|
if (unconfirmed && !draft) {
|
|
3527
3630
|
const ids = [...open, ...byAgent].map((r) => r.id).join(", ");
|
|
3528
3631
|
throw new Error(
|
|
3529
|
-
`not published: ${open.length} criteria are open and ${byAgent.length} are decided by an agent only (${ids}). See ${
|
|
3632
|
+
`not published: ${open.length} criteria are open and ${byAgent.length} are decided by an agent only (${ids}). See ${join11(resultsDir, "manual-pass.md")}, or add --draft to publish it as a draft`
|
|
3530
3633
|
);
|
|
3531
3634
|
}
|
|
3532
3635
|
const publicPath = "frontend/public/static/common/accessibility-conformance-report.pdf";
|
|
3533
|
-
mkdirSync4(dirname4(
|
|
3534
|
-
copyFileSync(pdfPath,
|
|
3636
|
+
mkdirSync4(dirname4(join11(forkPath, publicPath)), { recursive: true });
|
|
3637
|
+
copyFileSync(pdfPath, join11(forkPath, publicPath));
|
|
3535
3638
|
const count3 = (status) => ledger.criteria.filter((r) => r.status === status).length;
|
|
3536
3639
|
const review = {
|
|
3537
3640
|
standard: ledger.standard,
|
|
@@ -3622,7 +3725,7 @@ var a11yCommand = {
|
|
|
3622
3725
|
options: [
|
|
3623
3726
|
["--states <ids>", "audit: these states only, comma separated; writes ledger-partial.json, never the ledger"],
|
|
3624
3727
|
["--workers <n>", "audit: states audited at the same time (default 4)"],
|
|
3625
|
-
["--email <address>", "audit: the audit user (default ADMIN_EMAIL in
|
|
3728
|
+
["--email <address>", "audit: the audit user (default ADMIN_EMAIL in .env)"],
|
|
3626
3729
|
["--publish", "report: put the PDF where the app serves it and print what the statement needs"],
|
|
3627
3730
|
["--draft", "report: publish while rows still need a person; the statement calls the results provisional"],
|
|
3628
3731
|
["--file <path>", "decide: a JSON list of decisions"],
|
|
@@ -3638,8 +3741,8 @@ var a11yCommand = {
|
|
|
3638
3741
|
};
|
|
3639
3742
|
|
|
3640
3743
|
// src/tools/bench/command.ts
|
|
3641
|
-
import { existsSync as
|
|
3642
|
-
import { basename, join as
|
|
3744
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "fs";
|
|
3745
|
+
import { basename, join as join12 } from "path";
|
|
3643
3746
|
|
|
3644
3747
|
// src/tools/bench/request.ts
|
|
3645
3748
|
var BENCH_REQUEST_ENV = "CELLA_BENCH";
|
|
@@ -3648,11 +3751,11 @@ var BENCH_REQUEST_ENV = "CELLA_BENCH";
|
|
|
3648
3751
|
var BENCH_DIR = "backend/bench";
|
|
3649
3752
|
var ENTRY_COMMAND = ["pnpm", "exec", "tsx", "--no-experimental-webstorage", "bench/run.ts"];
|
|
3650
3753
|
async function runBench(forkPath, options) {
|
|
3651
|
-
if (!
|
|
3754
|
+
if (!existsSync8(join12(forkPath, BENCH_DIR, "run.ts"))) {
|
|
3652
3755
|
throw new Error(`${BENCH_DIR}/run.ts is missing: upgrade the app from cella first`);
|
|
3653
3756
|
}
|
|
3654
|
-
const scenariosDir =
|
|
3655
|
-
const names =
|
|
3757
|
+
const scenariosDir = join12(forkPath, BENCH_DIR, "scenarios");
|
|
3758
|
+
const names = readdirSync2(scenariosDir).filter((file) => file.endsWith(".ts")).map((file) => basename(file, ".ts")).sort();
|
|
3656
3759
|
if (options.scenario && !names.includes(options.scenario)) {
|
|
3657
3760
|
throw new Error(`unknown scenario '${options.scenario}'. expected one of: ${names.join(", ")}`);
|
|
3658
3761
|
}
|
|
@@ -3664,7 +3767,7 @@ async function runBench(forkPath, options) {
|
|
|
3664
3767
|
scenariosDir,
|
|
3665
3768
|
dataDir: toolDir(forkPath, "bench", "repository")
|
|
3666
3769
|
};
|
|
3667
|
-
const run = (env) => runCommand(
|
|
3770
|
+
const run = (env) => runCommand(join12(forkPath, "backend"), { ...env, [BENCH_REQUEST_ENV]: JSON.stringify(request) }, ENTRY_COMMAND);
|
|
3668
3771
|
return options.isolated ? withTestbed(forkPath, (testbed) => run(testbed.env)) : run(getEnvSnapshot());
|
|
3669
3772
|
}
|
|
3670
3773
|
var benchCommand = {
|
|
@@ -3703,98 +3806,12 @@ import { spawnSync as spawnSync6 } from "child_process";
|
|
|
3703
3806
|
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
3704
3807
|
import { join as join14 } from "path";
|
|
3705
3808
|
|
|
3706
|
-
// src/tools/i18n/locales.ts
|
|
3707
|
-
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
3708
|
-
import { join as join12 } from "path";
|
|
3709
|
-
var LOCALES_DIR = "locales";
|
|
3710
|
-
var MANIFEST = "namespaces.json";
|
|
3711
|
-
var SOURCE_LANGUAGE = "en";
|
|
3712
|
-
var entryPattern = /^(\s+)("(?:[^"\\]|\\.)*")\s*:\s*("(?:[^"\\]|\\.)*"),?\s*$/;
|
|
3713
|
-
function parseLocaleFile(path, text2) {
|
|
3714
|
-
if (text2.trim() === "{}") return { path, indent: " ", entries: [] };
|
|
3715
|
-
const lines = text2.replace(/\n$/, "").split("\n");
|
|
3716
|
-
if (lines[0] !== "{" || lines.at(-1) !== "}") return "not one JSON object with one entry per line";
|
|
3717
|
-
const entries = [];
|
|
3718
|
-
let indent = " ";
|
|
3719
|
-
for (const line of lines.slice(1, -1)) {
|
|
3720
|
-
const match = entryPattern.exec(line);
|
|
3721
|
-
if (!match) return `cannot read the line ${JSON.stringify(line.trim())}`;
|
|
3722
|
-
indent = match[1];
|
|
3723
|
-
entries.push({ key: JSON.parse(match[2]), value: JSON.parse(match[3]), raw: [match[2], match[3]] });
|
|
3724
|
-
}
|
|
3725
|
-
return { path, indent, entries };
|
|
3726
|
-
}
|
|
3727
|
-
function serializeLocaleFile(file) {
|
|
3728
|
-
if (file.entries.length === 0) return "{}\n";
|
|
3729
|
-
return `{
|
|
3730
|
-
${file.entries.map(({ raw }) => `${file.indent}${raw[0]}: ${raw[1]}`).join(",\n")}
|
|
3731
|
-
}
|
|
3732
|
-
`;
|
|
3733
|
-
}
|
|
3734
|
-
function writeLocaleFile(forkPath, file) {
|
|
3735
|
-
writeFileSync6(join12(forkPath, file.path), serializeLocaleFile(file));
|
|
3736
|
-
}
|
|
3737
|
-
var textsOf = (file) => Object.fromEntries((file?.entries ?? []).map(({ key, value }) => [key, value]));
|
|
3738
|
-
var readJsonObject = (path) => {
|
|
3739
|
-
const parsed2 = JSON.parse(readFileSync6(path, "utf8"));
|
|
3740
|
-
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) throw new Error(`${path} is not a JSON object`);
|
|
3741
|
-
return Object.fromEntries(Object.entries(parsed2));
|
|
3742
|
-
};
|
|
3743
|
-
function readLocales(forkPath) {
|
|
3744
|
-
const dir = join12(forkPath, LOCALES_DIR);
|
|
3745
|
-
if (!existsSync8(join12(dir, SOURCE_LANGUAGE))) throw new Error(`no ${LOCALES_DIR}/${SOURCE_LANGUAGE}/ in ${forkPath}`);
|
|
3746
|
-
const languages = readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort((a, b) => a === SOURCE_LANGUAGE ? -1 : b === SOURCE_LANGUAGE ? 1 : a.localeCompare(b));
|
|
3747
|
-
const files2 = /* @__PURE__ */ new Map();
|
|
3748
|
-
for (const language2 of languages) {
|
|
3749
|
-
for (const name of readdirSync2(join12(dir, language2)).filter((name2) => name2.endsWith(".json"))) {
|
|
3750
|
-
const path = `${LOCALES_DIR}/${language2}/${name}`;
|
|
3751
|
-
const parsed2 = parseLocaleFile(path, readFileSync6(join12(forkPath, path), "utf8"));
|
|
3752
|
-
if (typeof parsed2 === "string") throw new Error(`${path}: ${parsed2}; pnpm lint:fix writes one entry per line`);
|
|
3753
|
-
files2.set(`${language2}/${name.slice(0, -".json".length)}`, parsed2);
|
|
3754
|
-
}
|
|
3755
|
-
}
|
|
3756
|
-
const manifestPath = join12(dir, MANIFEST);
|
|
3757
|
-
if (!existsSync8(manifestPath)) throw new Error(`no ${LOCALES_DIR}/${MANIFEST}: upgrade the app from cella first`);
|
|
3758
|
-
const merged = readJsonObject(manifestPath);
|
|
3759
|
-
const namespaces = /* @__PURE__ */ new Map();
|
|
3760
|
-
for (const [namespace, sources] of Object.entries(merged)) {
|
|
3761
|
-
if (!Array.isArray(sources) || !sources.every((source) => typeof source === "string")) {
|
|
3762
|
-
throw new Error(`${LOCALES_DIR}/${MANIFEST}: ${namespace} must list file names`);
|
|
3763
|
-
}
|
|
3764
|
-
namespaces.set(namespace, sources);
|
|
3765
|
-
}
|
|
3766
|
-
const mergedFiles = new Set([...namespaces.values()].flat());
|
|
3767
|
-
const ownFiles = [...new Set([...files2.keys()].map((key) => key.split("/")[1]))].filter(
|
|
3768
|
-
(file) => !mergedFiles.has(file)
|
|
3769
|
-
);
|
|
3770
|
-
for (const file of ownFiles.sort()) namespaces.set(file, [file]);
|
|
3771
|
-
return { languages, namespaces, files: files2 };
|
|
3772
|
-
}
|
|
3773
|
-
function namespaceTexts(locales, language2, namespace) {
|
|
3774
|
-
const sources = locales.namespaces.get(namespace) ?? [];
|
|
3775
|
-
return Object.assign({}, ...sources.map((file) => textsOf(locales.files.get(`${language2}/${file}`))));
|
|
3776
|
-
}
|
|
3777
|
-
var lineOf = (file, key) => file.entries.findIndex((entry) => entry.key === key) + 2;
|
|
3778
|
-
var pluralSuffix = /_(ordinal_)?(zero|one|two|few|many|other)$/;
|
|
3779
|
-
function pluralForm(key) {
|
|
3780
|
-
const match = pluralSuffix.exec(key);
|
|
3781
|
-
if (!match) return void 0;
|
|
3782
|
-
return { base: key.slice(0, match.index), category: match[2], ordinal: !!match[1] };
|
|
3783
|
-
}
|
|
3784
|
-
var withPluralForms = (key) => [
|
|
3785
|
-
key,
|
|
3786
|
-
...["zero", "one", "two", "few", "many", "other"].flatMap((category) => [
|
|
3787
|
-
`${key}_${category}`,
|
|
3788
|
-
`${key}_ordinal_${category}`
|
|
3789
|
-
])
|
|
3790
|
-
];
|
|
3791
|
-
|
|
3792
3809
|
// src/tools/i18n/scan.ts
|
|
3793
3810
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3794
3811
|
import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
3795
3812
|
import { dirname as dirname5, join as join13 } from "path";
|
|
3796
3813
|
var codeFile = /\.(?:[cm]?[jt]sx?)$/;
|
|
3797
|
-
var skipped = (path) => path.endsWith(".d.ts") || path.startsWith("locales/") || path.startsWith("cella/alignments/") || path.split("/").some((segment) => segment === "node_modules" || segment === "gen" || segment.includes(".gen."));
|
|
3814
|
+
var skipped = (path) => path.endsWith(".d.ts") || path.startsWith("locales/") || path.startsWith("shared/locales/") || path.startsWith("cella/alignments/") || path.split("/").some((segment) => segment === "node_modules" || segment === "gen" || segment.includes(".gen."));
|
|
3798
3815
|
var MAX_FILE_BYTES = 1e6;
|
|
3799
3816
|
var MAX_COMBINATIONS = 500;
|
|
3800
3817
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -4211,10 +4228,10 @@ function printList(title, items) {
|
|
|
4211
4228
|
console.info(` ${colors_default.dim(`${items.slice(0, LIST_MAX).join(", ")}${more}`)}`);
|
|
4212
4229
|
}
|
|
4213
4230
|
var describe = ({ key, missing, extra }) => [key, ...missing.map((name) => `-${name}`), ...extra.map((name) => `+${name}`)].join(" ");
|
|
4214
|
-
function printStatus(report) {
|
|
4231
|
+
function printStatus(report, dir) {
|
|
4215
4232
|
const label = ({ name, files: files2 }) => files2.length > 1 || files2[0] !== name ? `${name} (${files2.join(" + ")})` : name;
|
|
4216
4233
|
console.info(
|
|
4217
|
-
`${colors_default.bold(`${
|
|
4234
|
+
`${colors_default.bold(`${dir}/`)} ${colors_default.dim(`${report.languages.join(", ")} \xB7 namespaces ${report.namespaces.map(label).join(", ")}`)}`
|
|
4218
4235
|
);
|
|
4219
4236
|
console.info();
|
|
4220
4237
|
console.info(colors_default.dim(row2(columns.map(([title]) => title))));
|
|
@@ -4286,9 +4303,10 @@ function printStatus(report) {
|
|
|
4286
4303
|
console.info(` ${colors_default.dim("missing and same-as-English keys: --json lists them \xB7 cella i18n unused")}`);
|
|
4287
4304
|
}
|
|
4288
4305
|
function runStatus(forkPath, json) {
|
|
4289
|
-
const
|
|
4306
|
+
const locales = readLocales(forkPath);
|
|
4307
|
+
const report = buildStatus(locales);
|
|
4290
4308
|
if (json) writeStdout(JSON.stringify(report, null, 2));
|
|
4291
|
-
else printStatus(report);
|
|
4309
|
+
else printStatus(report, locales.dir);
|
|
4292
4310
|
return defectCount(report) === 0 ? 0 : 1;
|
|
4293
4311
|
}
|
|
4294
4312
|
|
|
@@ -5391,7 +5409,7 @@ async function spinnersGone(page, quiet = 1e3, cap = 45e3) {
|
|
|
5391
5409
|
var hasMfaScript = (forkPath) => Boolean(JSON.parse(readFileSync13(join21(forkPath, "backend/package.json"), "utf8")).scripts?.["showcase:mfa"]);
|
|
5392
5410
|
async function shoot(forkPath, list2, testbed, options) {
|
|
5393
5411
|
const email = options.email ?? testbed.env.ADMIN_EMAIL;
|
|
5394
|
-
if (!email) throw new Error("no shot user: pass --email, or set ADMIN_EMAIL in
|
|
5412
|
+
if (!email) throw new Error("no shot user: pass --email, or set ADMIN_EMAIL in .env");
|
|
5395
5413
|
const todo = options.ids.length > 0 ? list2.shots.filter((shot2) => options.ids.includes(shot2.id)) : list2.shots;
|
|
5396
5414
|
const user = mintSession(email, { forkPath, env: testbed.env });
|
|
5397
5415
|
const scratch = mkdtempSync(join21(tmpdir(), "cella-screenshots-"));
|
|
@@ -5520,7 +5538,7 @@ var screenshotsCommand = {
|
|
|
5520
5538
|
operands: ["[ids...]", "the shots to take (default: all)"],
|
|
5521
5539
|
options: [
|
|
5522
5540
|
["--out <dir>", "write into this folder, a trial run that leaves the working tree alone"],
|
|
5523
|
-
["--email <address>", "the shot user (default the showcase admin, else ADMIN_EMAIL in
|
|
5541
|
+
["--email <address>", "the shot user (default the showcase admin, else ADMIN_EMAIL in .env)"]
|
|
5524
5542
|
],
|
|
5525
5543
|
parse: ({ operands, opts }) => ({
|
|
5526
5544
|
ids: operands,
|
|
@@ -5779,7 +5797,9 @@ var alignmentExempt = [
|
|
|
5779
5797
|
"json/",
|
|
5780
5798
|
"locales/",
|
|
5781
5799
|
"sdk/gen",
|
|
5782
|
-
"shared/config"
|
|
5800
|
+
"shared/config",
|
|
5801
|
+
"shared/json/",
|
|
5802
|
+
"shared/locales/"
|
|
5783
5803
|
];
|
|
5784
5804
|
function ownedPaths(configSource) {
|
|
5785
5805
|
const lists = [...configSource.matchAll(/\b(?:ignored|pinned): \[([\s\S]*?)\n {4}\]/g)].map((match) => match[1]);
|
|
@@ -7035,13 +7055,27 @@ function parseOverrideLists(source) {
|
|
|
7035
7055
|
}
|
|
7036
7056
|
}
|
|
7037
7057
|
function diffOverrideLists(base, incoming, fork) {
|
|
7058
|
+
const basenameOf2 = (entry) => entry.slice(entry.lastIndexOf("/") + 1);
|
|
7038
7059
|
const diff = (key) => {
|
|
7039
7060
|
const baseSet = new Set(base[key].map(normalizeEntry).filter(Boolean));
|
|
7040
7061
|
const incomingSet = new Set(incoming[key].map(normalizeEntry).filter(Boolean));
|
|
7041
7062
|
const forkEntries = fork[key].map(normalizeEntry).filter(Boolean);
|
|
7063
|
+
const newEntries = [...incomingSet].filter((entry) => !baseSet.has(entry) && !isUnderAnyFolder(entry, forkEntries));
|
|
7064
|
+
const renameSources = [...baseSet].filter(
|
|
7065
|
+
(entry) => !incomingSet.has(entry) && !forkEntries.includes(entry) && !isUnderAnyFolder(entry, forkEntries)
|
|
7066
|
+
);
|
|
7067
|
+
const moved = [];
|
|
7068
|
+
const added = newEntries.filter((entry) => {
|
|
7069
|
+
const sourceAt = renameSources.findIndex((source) => basenameOf2(source) === basenameOf2(entry));
|
|
7070
|
+
if (sourceAt === -1) return true;
|
|
7071
|
+
moved.push({ from: renameSources[sourceAt], to: entry });
|
|
7072
|
+
renameSources.splice(sourceAt, 1);
|
|
7073
|
+
return false;
|
|
7074
|
+
});
|
|
7042
7075
|
return {
|
|
7043
|
-
added
|
|
7044
|
-
removed: [...baseSet].filter((entry) => !incomingSet.has(entry) && forkEntries.includes(entry))
|
|
7076
|
+
added,
|
|
7077
|
+
removed: [...baseSet].filter((entry) => !incomingSet.has(entry) && forkEntries.includes(entry)),
|
|
7078
|
+
...moved.length > 0 ? { moved } : {}
|
|
7045
7079
|
};
|
|
7046
7080
|
};
|
|
7047
7081
|
const diffKeys = () => {
|
|
@@ -7069,9 +7103,17 @@ async function compareUpstreamOverrides(repoPath, mergeBaseRef, incomingRef, for
|
|
|
7069
7103
|
packageJsonKeys: forkConfig.settings.packageJsonKeys ?? DEFAULT_PACKAGE_JSON_KEYS
|
|
7070
7104
|
};
|
|
7071
7105
|
const changes = diffOverrideLists(base, incoming, fork);
|
|
7072
|
-
const count3 = Object.values(changes).reduce(
|
|
7106
|
+
const count3 = Object.values(changes).reduce(
|
|
7107
|
+
(n, list2) => n + list2.added.length + list2.removed.length + (list2.moved?.length ?? 0),
|
|
7108
|
+
0
|
|
7109
|
+
);
|
|
7073
7110
|
return count3 > 0 ? { kind: "changes", ...changes } : void 0;
|
|
7074
7111
|
}
|
|
7112
|
+
function hasBlockingOverrideChanges(report) {
|
|
7113
|
+
if (report.kind !== "changes") return false;
|
|
7114
|
+
const lists = [report.pinned, report.ignored, report.packageJsonKeys];
|
|
7115
|
+
return lists.some((list2) => list2.added.length > 0 || list2.removed.length > 0);
|
|
7116
|
+
}
|
|
7075
7117
|
|
|
7076
7118
|
// src/utils/overrides.ts
|
|
7077
7119
|
function normalizeEntry(entry) {
|
|
@@ -7120,8 +7162,9 @@ async function validateOverrides(config, forkPath, upstreamRef) {
|
|
|
7120
7162
|
const path = normalizeEntry(entry);
|
|
7121
7163
|
const ignoredEntry = ignored.find((folder) => isUnderAnyFolder(path, [folder]));
|
|
7122
7164
|
if (/[*?]/.test(entry)) warnings.push(`pin uses glob (not supported, use a path or folder): ${entry}`);
|
|
7123
|
-
else if (!existsSync21(join28(forkPath, path)))
|
|
7124
|
-
|
|
7165
|
+
else if (!existsSync21(join28(forkPath, path))) {
|
|
7166
|
+
if (!upstream || !await fileExistsAtRef(forkPath, upstream, path)) warnings.push(`pin not found: ${entry}`);
|
|
7167
|
+
} else if (ignoredEntry) warnings.push(`pin does nothing, '${normalizeEntry(ignoredEntry)}' is ignored: ${entry}`);
|
|
7125
7168
|
else if (!upstream) continue;
|
|
7126
7169
|
else if (await fileExistsAtRef(forkPath, upstream, path)) pinsUpstreamHas.push(entry);
|
|
7127
7170
|
else warnings.push(`pin does nothing, upstream no longer has it: ${entry}`);
|
|
@@ -7142,6 +7185,7 @@ async function validateOverrides(config, forkPath, upstreamRef) {
|
|
|
7142
7185
|
warnings.push(`ignored entry uses glob (not supported, use a path or folder): ${entry}`);
|
|
7143
7186
|
} else if (!existsSync21(join28(forkPath, path))) {
|
|
7144
7187
|
if (upstreamRef && await fileExistsAtRef(forkPath, upstreamRef, path)) continue;
|
|
7188
|
+
if (isGeneratedFile(path)) continue;
|
|
7145
7189
|
warnings.push(`ignored entry not found: ${entry}`);
|
|
7146
7190
|
}
|
|
7147
7191
|
}
|
|
@@ -7472,6 +7516,109 @@ import { existsSync as existsSync22 } from "fs";
|
|
|
7472
7516
|
import { rm as rm2 } from "fs/promises";
|
|
7473
7517
|
import { join as join29 } from "path";
|
|
7474
7518
|
|
|
7519
|
+
// src/utils/audit-utils.ts
|
|
7520
|
+
import { execFile as execFile2 } from "child_process";
|
|
7521
|
+
import { promisify as promisify2 } from "util";
|
|
7522
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
7523
|
+
function outdatedPackageName(key) {
|
|
7524
|
+
const name = key.split(" ")[0];
|
|
7525
|
+
const at = name.indexOf("@", 1);
|
|
7526
|
+
return at === -1 ? name : name.slice(0, at);
|
|
7527
|
+
}
|
|
7528
|
+
function getRepoUrl(repositoryUrl) {
|
|
7529
|
+
if (!repositoryUrl) return null;
|
|
7530
|
+
let url = repositoryUrl;
|
|
7531
|
+
if (url.startsWith("git+")) {
|
|
7532
|
+
url = url.slice(4);
|
|
7533
|
+
}
|
|
7534
|
+
if (url.endsWith(".git")) {
|
|
7535
|
+
url = url.slice(0, -4);
|
|
7536
|
+
}
|
|
7537
|
+
if (url.startsWith("git://")) {
|
|
7538
|
+
url = url.replace("git://", "https://");
|
|
7539
|
+
}
|
|
7540
|
+
return url;
|
|
7541
|
+
}
|
|
7542
|
+
function isGitHubRepoUrl(repoUrl) {
|
|
7543
|
+
if (!repoUrl) return false;
|
|
7544
|
+
try {
|
|
7545
|
+
const parsed2 = new URL(repoUrl);
|
|
7546
|
+
return parsed2.hostname === "github.com" || parsed2.hostname === "www.github.com";
|
|
7547
|
+
} catch {
|
|
7548
|
+
return false;
|
|
7549
|
+
}
|
|
7550
|
+
}
|
|
7551
|
+
function getReleasesUrl(repoUrl) {
|
|
7552
|
+
if (!isGitHubRepoUrl(repoUrl)) return null;
|
|
7553
|
+
return `${repoUrl}/releases`;
|
|
7554
|
+
}
|
|
7555
|
+
async function runPnpm(args, cwd) {
|
|
7556
|
+
try {
|
|
7557
|
+
const { stdout, stderr } = await execFileAsync2("pnpm", args, {
|
|
7558
|
+
encoding: "utf8",
|
|
7559
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
7560
|
+
cwd
|
|
7561
|
+
});
|
|
7562
|
+
return { ok: true, stdout, stderr };
|
|
7563
|
+
} catch (error) {
|
|
7564
|
+
const { stdout = "", stderr = "" } = error;
|
|
7565
|
+
return { ok: false, stdout, stderr: stdout || stderr ? stderr : String(error) };
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
async function runPnpmJson(args, cwd) {
|
|
7569
|
+
try {
|
|
7570
|
+
return JSON.parse((await runPnpm([...args, "--loglevel=error"], cwd)).stdout);
|
|
7571
|
+
} catch {
|
|
7572
|
+
return null;
|
|
7573
|
+
}
|
|
7574
|
+
}
|
|
7575
|
+
async function runPnpmAudit(cwd) {
|
|
7576
|
+
const result = await runPnpmJson(["audit", "--json"], cwd);
|
|
7577
|
+
if (!result || typeof result.advisories !== "object" || result.advisories === null) return null;
|
|
7578
|
+
return result;
|
|
7579
|
+
}
|
|
7580
|
+
async function readReleaseAgeMinutes(cwd) {
|
|
7581
|
+
const minutes = Number.parseInt((await runPnpm(["config", "get", "minimumReleaseAge"], cwd)).stdout, 10);
|
|
7582
|
+
return Number.isNaN(minutes) ? 0 : minutes;
|
|
7583
|
+
}
|
|
7584
|
+
async function fetchPublishTimes(name, cwd) {
|
|
7585
|
+
const times = await runPnpmJson(["view", name, "time", "--json"], cwd);
|
|
7586
|
+
return times && typeof times === "object" && !("error" in times) ? times : null;
|
|
7587
|
+
}
|
|
7588
|
+
function editJsonText(content, change) {
|
|
7589
|
+
const json = JSON.parse(content);
|
|
7590
|
+
change(json);
|
|
7591
|
+
const indent = content.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
|
|
7592
|
+
return `${JSON.stringify(json, null, indent)}${content.endsWith("\n") ? "\n" : ""}`;
|
|
7593
|
+
}
|
|
7594
|
+
function rewriteSpecifier(spec, latest) {
|
|
7595
|
+
const match = spec.match(/^([\^~]?)\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?$/);
|
|
7596
|
+
return match ? `${match[1]}${latest}` : null;
|
|
7597
|
+
}
|
|
7598
|
+
var vulnIcons = {
|
|
7599
|
+
critical: colors_default.red("\u25CF"),
|
|
7600
|
+
high: colors_default.red("\u25CF"),
|
|
7601
|
+
moderate: colors_default.yellow("\u25CF"),
|
|
7602
|
+
low: colors_default.blue("\u25CF"),
|
|
7603
|
+
info: colors_default.gray("\u25CF")
|
|
7604
|
+
};
|
|
7605
|
+
function getVulnIcon(severity) {
|
|
7606
|
+
return vulnIcons[severity] ?? colors_default.gray("\u25CF");
|
|
7607
|
+
}
|
|
7608
|
+
function middleTruncate(str2, maxLen) {
|
|
7609
|
+
if (str2.length <= maxLen) return str2;
|
|
7610
|
+
const ellipsis = "\u2026";
|
|
7611
|
+
const charsToShow = maxLen - ellipsis.length;
|
|
7612
|
+
const frontChars = Math.ceil(charsToShow / 2);
|
|
7613
|
+
const backChars = Math.floor(charsToShow / 2);
|
|
7614
|
+
return str2.slice(0, frontChars) + ellipsis + str2.slice(-backChars);
|
|
7615
|
+
}
|
|
7616
|
+
function formatDependents(dependents) {
|
|
7617
|
+
if (dependents.length === 0) return "";
|
|
7618
|
+
if (dependents.length === 1) return dependents[0];
|
|
7619
|
+
return `${dependents[0]} +${dependents.length - 1}`;
|
|
7620
|
+
}
|
|
7621
|
+
|
|
7475
7622
|
// src/utils/versions.ts
|
|
7476
7623
|
function parseComparableVersion(version) {
|
|
7477
7624
|
const trimmed = version.trim();
|
|
@@ -7535,6 +7682,10 @@ async function readUpstreamCliRange(repoPath, ref) {
|
|
|
7535
7682
|
return null;
|
|
7536
7683
|
}
|
|
7537
7684
|
}
|
|
7685
|
+
function releaseAgeExcludeHint(range) {
|
|
7686
|
+
const version = range.match(/\d+\S*/)?.[0] ?? range;
|
|
7687
|
+
return ` pnpm delays fresh releases (minimumReleaseAge): if the install is refused, add '${CLI_PACKAGE}@${version}' to minimumReleaseAgeExclude in pnpm-workspace.yaml.`;
|
|
7688
|
+
}
|
|
7538
7689
|
function cliVersionMismatch(running, range) {
|
|
7539
7690
|
if (/^\s*(<|>(?!=))/.test(range)) return null;
|
|
7540
7691
|
const required = parseComparableVersion(range);
|
|
@@ -7550,6 +7701,81 @@ function cliVersionMismatch(running, range) {
|
|
|
7550
7701
|
function isReportableIgnored(filePath) {
|
|
7551
7702
|
return !isManagedFile(filePath) && !isGeneratedFile(filePath);
|
|
7552
7703
|
}
|
|
7704
|
+
var basenameOf = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
7705
|
+
var dirnameOf = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
7706
|
+
function directoryMoves(changes) {
|
|
7707
|
+
const votes = /* @__PURE__ */ new Map();
|
|
7708
|
+
for (const [newPath, change] of changes) {
|
|
7709
|
+
if (change.status !== "R" || !change.oldPath || basenameOf(change.oldPath) !== basenameOf(newPath)) continue;
|
|
7710
|
+
const from = dirnameOf(change.oldPath);
|
|
7711
|
+
const counts = votes.get(from) ?? /* @__PURE__ */ new Map();
|
|
7712
|
+
counts.set(dirnameOf(newPath), (counts.get(dirnameOf(newPath)) ?? 0) + 1);
|
|
7713
|
+
votes.set(from, counts);
|
|
7714
|
+
}
|
|
7715
|
+
const moves = /* @__PURE__ */ new Map();
|
|
7716
|
+
for (const [from, counts] of votes) {
|
|
7717
|
+
const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
7718
|
+
if (ranked.length === 1 || ranked[0][1] > ranked[1][1]) moves.set(from, ranked[0][0]);
|
|
7719
|
+
}
|
|
7720
|
+
return moves;
|
|
7721
|
+
}
|
|
7722
|
+
var joinDir = (dir, name) => dir ? `${dir}/${name}` : name;
|
|
7723
|
+
function repairIncomingRenames(changes, context) {
|
|
7724
|
+
const dirMoves = directoryMoves(changes);
|
|
7725
|
+
const snapshot = new Map(changes);
|
|
7726
|
+
const crossed = [];
|
|
7727
|
+
const keptTargets = /* @__PURE__ */ new Set();
|
|
7728
|
+
const renameSources = /* @__PURE__ */ new Set();
|
|
7729
|
+
for (const [newPath, change] of changes) {
|
|
7730
|
+
if (change.status !== "R" || !change.oldPath) continue;
|
|
7731
|
+
renameSources.add(change.oldPath);
|
|
7732
|
+
if (basenameOf(change.oldPath) === basenameOf(newPath)) keptTargets.add(newPath);
|
|
7733
|
+
else crossed.push({ from: newPath, oldPath: change.oldPath, change });
|
|
7734
|
+
}
|
|
7735
|
+
const desired = /* @__PURE__ */ new Map();
|
|
7736
|
+
for (const pair of crossed) {
|
|
7737
|
+
const name = basenameOf(pair.oldPath);
|
|
7738
|
+
const mappedDir = dirMoves.get(dirnameOf(pair.oldPath));
|
|
7739
|
+
const candidates = [
|
|
7740
|
+
...mappedDir === void 0 ? [] : [joinDir(mappedDir, name)],
|
|
7741
|
+
joinDir(dirnameOf(pair.from), name)
|
|
7742
|
+
];
|
|
7743
|
+
const target = candidates.find(
|
|
7744
|
+
(path) => context.incomingPaths.has(path) && !context.basePaths.has(path) && !keptTargets.has(path)
|
|
7745
|
+
);
|
|
7746
|
+
desired.set(pair.from, target ?? pair.from);
|
|
7747
|
+
}
|
|
7748
|
+
const targets = [...desired.values()];
|
|
7749
|
+
if (new Set(targets).size === targets.length) {
|
|
7750
|
+
const targetSet = new Set(targets);
|
|
7751
|
+
for (const pair of crossed) {
|
|
7752
|
+
const target = desired.get(pair.from);
|
|
7753
|
+
if (target === pair.from) continue;
|
|
7754
|
+
changes.set(target, {
|
|
7755
|
+
status: "R",
|
|
7756
|
+
oldPath: pair.oldPath,
|
|
7757
|
+
baseHash: pair.change.baseHash,
|
|
7758
|
+
targetHash: snapshot.get(target)?.targetHash ?? null
|
|
7759
|
+
});
|
|
7760
|
+
}
|
|
7761
|
+
for (const pair of crossed) {
|
|
7762
|
+
if (desired.get(pair.from) === pair.from || targetSet.has(pair.from)) continue;
|
|
7763
|
+
changes.set(pair.from, { status: "A", baseHash: null, targetHash: snapshot.get(pair.from)?.targetHash ?? null });
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
for (const oldPath of context.localPaths) {
|
|
7767
|
+
if (context.incomingPaths.has(oldPath) || !context.basePaths.has(oldPath)) continue;
|
|
7768
|
+
if (renameSources.has(oldPath) || !context.isProtected(oldPath)) continue;
|
|
7769
|
+
const mappedDir = dirMoves.get(dirnameOf(oldPath));
|
|
7770
|
+
if (mappedDir === void 0) continue;
|
|
7771
|
+
const target = joinDir(mappedDir, basenameOf(oldPath));
|
|
7772
|
+
if (!context.incomingPaths.has(target) || context.basePaths.has(target) || changes.get(target)?.status !== "A")
|
|
7773
|
+
continue;
|
|
7774
|
+
changes.set(target, { status: "R", oldPath, baseHash: null, targetHash: changes.get(target)?.targetHash ?? null });
|
|
7775
|
+
changes.delete(oldPath);
|
|
7776
|
+
renameSources.add(oldPath);
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7553
7779
|
async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predicates, onProgress) {
|
|
7554
7780
|
onProgress?.("collecting file hashes (batch)...");
|
|
7555
7781
|
const [forkHashes, upstreamHashes, baseHashes] = await Promise.all([
|
|
@@ -7559,6 +7785,12 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
|
|
|
7559
7785
|
]);
|
|
7560
7786
|
const upstreamChanges = await getFileChanges(repoPath, mergeBaseRef, incomingRef);
|
|
7561
7787
|
const forkChanges = await getFileChanges(repoPath, mergeBaseRef, localRef);
|
|
7788
|
+
repairIncomingRenames(upstreamChanges, {
|
|
7789
|
+
basePaths: new Set(baseHashes.keys()),
|
|
7790
|
+
incomingPaths: new Set(upstreamHashes.keys()),
|
|
7791
|
+
localPaths: new Set(forkHashes.keys()),
|
|
7792
|
+
isProtected: (path) => predicates.isIgnored(path) || predicates.isPinned(path)
|
|
7793
|
+
});
|
|
7562
7794
|
const upstreamRenames = /* @__PURE__ */ new Map();
|
|
7563
7795
|
for (const [newPath, change] of upstreamChanges) {
|
|
7564
7796
|
if (change.status === "R" && change.oldPath) {
|
|
@@ -7615,8 +7847,9 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
|
|
|
7615
7847
|
const forkModifiedOld = forkOldHash !== baseOldHash;
|
|
7616
7848
|
const oldPathPinned = predicates.isPinned(oldPath);
|
|
7617
7849
|
let upstreamChanged2;
|
|
7618
|
-
if (fileIsPinned || oldPathPinned || predicates.isIgnored(oldPath)) {
|
|
7850
|
+
if (fileIsPinned || fileIsIgnored || oldPathPinned || predicates.isIgnored(oldPath)) {
|
|
7619
7851
|
status = "pinned";
|
|
7852
|
+
if (oldPathInFork) renamedFrom = oldPath;
|
|
7620
7853
|
if (forkModifiedOld && upstreamHash !== baseOldHash) {
|
|
7621
7854
|
upstreamChanged2 = true;
|
|
7622
7855
|
renamedProtected.add(filePath);
|
|
@@ -7642,7 +7875,7 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
|
|
|
7642
7875
|
status = "ignored";
|
|
7643
7876
|
} else if (!inFork && inUpstream) {
|
|
7644
7877
|
const locallyDeleted = baseHash !== null && upstreamHash === baseHash;
|
|
7645
|
-
status = fileIsPinned || locallyDeleted ? "deleted" : "behind";
|
|
7878
|
+
status = fileIsPinned && baseHash !== null || locallyDeleted ? "deleted" : "behind";
|
|
7646
7879
|
} else if (inFork && !inUpstream) {
|
|
7647
7880
|
if (baseHash !== null) {
|
|
7648
7881
|
status = fileIsPinned ? "ahead" : "behind";
|
|
@@ -7754,6 +7987,9 @@ async function readOriginGitHubUrl(forkPath) {
|
|
|
7754
7987
|
function isProtectedFile(file) {
|
|
7755
7988
|
return file.isIgnored || file.isPinned;
|
|
7756
7989
|
}
|
|
7990
|
+
function isPinnedFirstArrival(file) {
|
|
7991
|
+
return file.isPinned && !file.isIgnored && !file.existsInFork && file.existsInUpstream && file.status === "behind";
|
|
7992
|
+
}
|
|
7757
7993
|
function syncPredicates(config) {
|
|
7758
7994
|
return {
|
|
7759
7995
|
isIgnored: (path) => isIgnored(path, config),
|
|
@@ -7772,9 +8008,11 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
|
|
|
7772
8008
|
);
|
|
7773
8009
|
const batchRestorePaths = [];
|
|
7774
8010
|
const batchRemovePaths = [];
|
|
8011
|
+
const movedProtected = [];
|
|
7775
8012
|
for (const file of analyzedFiles) {
|
|
7776
|
-
if (file.status === "identical" || file.status === "ahead") continue;
|
|
7777
|
-
if (file.
|
|
8013
|
+
if (file.status === "identical" || file.status === "ahead" || isPinnedFirstArrival(file)) continue;
|
|
8014
|
+
if (file.status === "pinned" && file.renamedFrom) movedProtected.push({ from: file.renamedFrom, to: file.path });
|
|
8015
|
+
else if (file.isIgnored || file.isPinned) {
|
|
7778
8016
|
(file.existsInFork ? batchRestorePaths : batchRemovePaths).push(file.path);
|
|
7779
8017
|
}
|
|
7780
8018
|
}
|
|
@@ -7791,16 +8029,23 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
|
|
|
7791
8029
|
await removeFileFromWorktree(forkPath, filePath);
|
|
7792
8030
|
}
|
|
7793
8031
|
}
|
|
8032
|
+
for (const { from, to } of movedProtected) {
|
|
8033
|
+
onProgress?.(`\u2192 ${from} \u2192 ${to}: keeping your copy (moved in upstream)`);
|
|
8034
|
+
await moveFromRef(forkPath, "HEAD", from, to);
|
|
8035
|
+
}
|
|
7794
8036
|
for (const file of analyzedFiles) {
|
|
7795
8037
|
const { path: filePath, status, existsInFork } = file;
|
|
7796
|
-
if (status === "identical" || status === "ahead" || isProtectedFile(file))
|
|
8038
|
+
if (status === "identical" || status === "ahead" || isProtectedFile(file) && !isPinnedFirstArrival(file))
|
|
8039
|
+
continue;
|
|
7797
8040
|
if (status === "diverged") {
|
|
7798
8041
|
onProgress?.(`\u2192 ${filePath}: using git merge result (diverged)`);
|
|
7799
8042
|
continue;
|
|
7800
8043
|
}
|
|
7801
8044
|
if (status === "behind") {
|
|
7802
8045
|
if (!existsInFork) {
|
|
7803
|
-
onProgress?.(
|
|
8046
|
+
onProgress?.(
|
|
8047
|
+
`\u2192 ${filePath}: adding from upstream (${isPinnedFirstArrival(file) ? "pinned, upstream's copy is your first" : "new file"})`
|
|
8048
|
+
);
|
|
7804
8049
|
await checkoutFromRef(forkPath, upstreamRef, [filePath]);
|
|
7805
8050
|
} else if (!file.existsInUpstream) {
|
|
7806
8051
|
onProgress?.(`\u2192 ${filePath}: removing (deleted in upstream)`);
|
|
@@ -7939,7 +8184,11 @@ async function prepareUpstream(config, onProgress, onStep) {
|
|
|
7939
8184
|
config.upstreamRef = upstreamRef;
|
|
7940
8185
|
const cliRange = await readUpstreamCliRange(forkPath, upstreamRef);
|
|
7941
8186
|
const cliMismatch = cliRange ? cliVersionMismatch(VERSION, cliRange) : null;
|
|
7942
|
-
if (cliMismatch
|
|
8187
|
+
if (cliMismatch && cliRange) {
|
|
8188
|
+
const delayed = await readReleaseAgeMinutes(forkPath) > 0;
|
|
8189
|
+
throw new Error(delayed ? `${cliMismatch}
|
|
8190
|
+
${releaseAgeExcludeHint(cliRange)}` : cliMismatch);
|
|
8191
|
+
}
|
|
7943
8192
|
onProgress?.("checking sync base...");
|
|
7944
8193
|
await ensureSyncBase(forkPath, "HEAD", upstreamRef);
|
|
7945
8194
|
const upstreamGitHubUrl = getGitHubBaseUrl(config.settings.upstreamUrl) ?? void 0;
|
|
@@ -7982,7 +8231,7 @@ async function assertSyncConfigFollowed(config, ctx) {
|
|
|
7982
8231
|
const report = await compareUpstreamOverrides(config.forkPath, ctx.mergeBase, ctx.upstreamRef, config).catch(
|
|
7983
8232
|
() => void 0
|
|
7984
8233
|
);
|
|
7985
|
-
if (report
|
|
8234
|
+
if (report && hasBlockingOverrideChanges(report)) throw new UpstreamConfigChangedError(report);
|
|
7986
8235
|
}
|
|
7987
8236
|
function resultMeta(config, ctx) {
|
|
7988
8237
|
return {
|
|
@@ -9045,109 +9294,6 @@ import { readFileSync as readFileSync19 } from "fs";
|
|
|
9045
9294
|
import { basename as basename9, join as join35, relative as relative4 } from "path";
|
|
9046
9295
|
import { checkbox, Separator } from "@inquirer/prompts";
|
|
9047
9296
|
|
|
9048
|
-
// src/utils/audit-utils.ts
|
|
9049
|
-
import { execFile as execFile2 } from "child_process";
|
|
9050
|
-
import { promisify as promisify2 } from "util";
|
|
9051
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
9052
|
-
function outdatedPackageName(key) {
|
|
9053
|
-
const name = key.split(" ")[0];
|
|
9054
|
-
const at = name.indexOf("@", 1);
|
|
9055
|
-
return at === -1 ? name : name.slice(0, at);
|
|
9056
|
-
}
|
|
9057
|
-
function getRepoUrl(repositoryUrl) {
|
|
9058
|
-
if (!repositoryUrl) return null;
|
|
9059
|
-
let url = repositoryUrl;
|
|
9060
|
-
if (url.startsWith("git+")) {
|
|
9061
|
-
url = url.slice(4);
|
|
9062
|
-
}
|
|
9063
|
-
if (url.endsWith(".git")) {
|
|
9064
|
-
url = url.slice(0, -4);
|
|
9065
|
-
}
|
|
9066
|
-
if (url.startsWith("git://")) {
|
|
9067
|
-
url = url.replace("git://", "https://");
|
|
9068
|
-
}
|
|
9069
|
-
return url;
|
|
9070
|
-
}
|
|
9071
|
-
function isGitHubRepoUrl(repoUrl) {
|
|
9072
|
-
if (!repoUrl) return false;
|
|
9073
|
-
try {
|
|
9074
|
-
const parsed2 = new URL(repoUrl);
|
|
9075
|
-
return parsed2.hostname === "github.com" || parsed2.hostname === "www.github.com";
|
|
9076
|
-
} catch {
|
|
9077
|
-
return false;
|
|
9078
|
-
}
|
|
9079
|
-
}
|
|
9080
|
-
function getReleasesUrl(repoUrl) {
|
|
9081
|
-
if (!isGitHubRepoUrl(repoUrl)) return null;
|
|
9082
|
-
return `${repoUrl}/releases`;
|
|
9083
|
-
}
|
|
9084
|
-
async function runPnpm(args, cwd) {
|
|
9085
|
-
try {
|
|
9086
|
-
const { stdout, stderr } = await execFileAsync2("pnpm", args, {
|
|
9087
|
-
encoding: "utf8",
|
|
9088
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
9089
|
-
cwd
|
|
9090
|
-
});
|
|
9091
|
-
return { ok: true, stdout, stderr };
|
|
9092
|
-
} catch (error) {
|
|
9093
|
-
const { stdout = "", stderr = "" } = error;
|
|
9094
|
-
return { ok: false, stdout, stderr: stdout || stderr ? stderr : String(error) };
|
|
9095
|
-
}
|
|
9096
|
-
}
|
|
9097
|
-
async function runPnpmJson(args, cwd) {
|
|
9098
|
-
try {
|
|
9099
|
-
return JSON.parse((await runPnpm([...args, "--loglevel=error"], cwd)).stdout);
|
|
9100
|
-
} catch {
|
|
9101
|
-
return null;
|
|
9102
|
-
}
|
|
9103
|
-
}
|
|
9104
|
-
async function runPnpmAudit(cwd) {
|
|
9105
|
-
const result = await runPnpmJson(["audit", "--json"], cwd);
|
|
9106
|
-
if (!result || typeof result.advisories !== "object" || result.advisories === null) return null;
|
|
9107
|
-
return result;
|
|
9108
|
-
}
|
|
9109
|
-
async function readReleaseAgeMinutes(cwd) {
|
|
9110
|
-
const minutes = Number.parseInt((await runPnpm(["config", "get", "minimumReleaseAge"], cwd)).stdout, 10);
|
|
9111
|
-
return Number.isNaN(minutes) ? 0 : minutes;
|
|
9112
|
-
}
|
|
9113
|
-
async function fetchPublishTimes(name, cwd) {
|
|
9114
|
-
const times = await runPnpmJson(["view", name, "time", "--json"], cwd);
|
|
9115
|
-
return times && typeof times === "object" && !("error" in times) ? times : null;
|
|
9116
|
-
}
|
|
9117
|
-
function editJsonText(content, change) {
|
|
9118
|
-
const json = JSON.parse(content);
|
|
9119
|
-
change(json);
|
|
9120
|
-
const indent = content.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
|
|
9121
|
-
return `${JSON.stringify(json, null, indent)}${content.endsWith("\n") ? "\n" : ""}`;
|
|
9122
|
-
}
|
|
9123
|
-
function rewriteSpecifier(spec, latest) {
|
|
9124
|
-
const match = spec.match(/^([\^~]?)\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?$/);
|
|
9125
|
-
return match ? `${match[1]}${latest}` : null;
|
|
9126
|
-
}
|
|
9127
|
-
var vulnIcons = {
|
|
9128
|
-
critical: colors_default.red("\u25CF"),
|
|
9129
|
-
high: colors_default.red("\u25CF"),
|
|
9130
|
-
moderate: colors_default.yellow("\u25CF"),
|
|
9131
|
-
low: colors_default.blue("\u25CF"),
|
|
9132
|
-
info: colors_default.gray("\u25CF")
|
|
9133
|
-
};
|
|
9134
|
-
function getVulnIcon(severity) {
|
|
9135
|
-
return vulnIcons[severity] ?? colors_default.gray("\u25CF");
|
|
9136
|
-
}
|
|
9137
|
-
function middleTruncate(str2, maxLen) {
|
|
9138
|
-
if (str2.length <= maxLen) return str2;
|
|
9139
|
-
const ellipsis = "\u2026";
|
|
9140
|
-
const charsToShow = maxLen - ellipsis.length;
|
|
9141
|
-
const frontChars = Math.ceil(charsToShow / 2);
|
|
9142
|
-
const backChars = Math.floor(charsToShow / 2);
|
|
9143
|
-
return str2.slice(0, frontChars) + ellipsis + str2.slice(-backChars);
|
|
9144
|
-
}
|
|
9145
|
-
function formatDependents(dependents) {
|
|
9146
|
-
if (dependents.length === 0) return "";
|
|
9147
|
-
if (dependents.length === 1) return dependents[0];
|
|
9148
|
-
return `${dependents[0]} +${dependents.length - 1}`;
|
|
9149
|
-
}
|
|
9150
|
-
|
|
9151
9297
|
// src/utils/local-tools.ts
|
|
9152
9298
|
import { execFile as execFile3 } from "child_process";
|
|
9153
9299
|
import { existsSync as existsSync24, realpathSync as realpathSync2 } from "fs";
|
|
@@ -10897,9 +11043,18 @@ function readSearchable(filePath) {
|
|
|
10897
11043
|
return "";
|
|
10898
11044
|
}
|
|
10899
11045
|
}
|
|
11046
|
+
var codeFilePattern = /\.(?:ts|tsx|js|jsx|mjs|cjs|mts|cts)$/;
|
|
11047
|
+
var proseFilePattern = /\.(?:md|mdx|markdown)$/i;
|
|
11048
|
+
function stripComments(text2) {
|
|
11049
|
+
return text2.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
11050
|
+
}
|
|
10900
11051
|
function fileSource(forkPath, path) {
|
|
10901
11052
|
let text2;
|
|
10902
|
-
|
|
11053
|
+
const read = () => {
|
|
11054
|
+
const content = readSearchable(join37(forkPath, path));
|
|
11055
|
+
return codeFilePattern.test(path) ? stripComments(content) : content;
|
|
11056
|
+
};
|
|
11057
|
+
return { path, label: path, text: () => text2 ??= read() };
|
|
10903
11058
|
}
|
|
10904
11059
|
async function readScriptsAtRef(forkPath, ref, path) {
|
|
10905
11060
|
try {
|
|
@@ -10952,7 +11107,7 @@ async function loadForkUsage(forkPath, upstreamRef, baseRef) {
|
|
|
10952
11107
|
const sources = [];
|
|
10953
11108
|
for (const path of diff.split("\0").filter(Boolean)) {
|
|
10954
11109
|
if (isPackageJson(path)) sources.push(...await scriptSources(forkPath, upstreamRef, baseRef, path));
|
|
10955
|
-
else if (!isManagedFile(path)) sources.push(fileSource(forkPath, path));
|
|
11110
|
+
else if (!isManagedFile(path) && !proseFilePattern.test(path)) sources.push(fileSource(forkPath, path));
|
|
10956
11111
|
}
|
|
10957
11112
|
const findIn = (scoped, patterns) => scoped.find((source) => patterns.some((pattern) => pattern.test(source.text())))?.label;
|
|
10958
11113
|
return {
|