@cellajs/cli 0.6.4 → 0.6.6
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 +12 -4
- package/dist/a11y.js.map +1 -1
- package/dist/bench.js +1 -1
- package/dist/bench.js.map +1 -1
- package/dist/drive.js.map +1 -1
- package/dist/index.js +242 -176
- 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.6",
|
|
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
|
}
|
|
@@ -1373,12 +1383,20 @@ var STARTUP_TIMEOUT_MS = 5 * 6e4;
|
|
|
1373
1383
|
var STOP_TIMEOUT_MS = 1e4;
|
|
1374
1384
|
var DATABASE = "testbed";
|
|
1375
1385
|
var DATABASE_SERVICE = "db_testbed";
|
|
1386
|
+
var showcaseFile = (forkPath) => movedPath(forkPath, "backend/scripts/seeds/showcase.json", "json/showcase.json");
|
|
1387
|
+
var showcasePath = (forkPath) => join4(forkPath, showcaseFile(forkPath));
|
|
1388
|
+
var hasShowcase = (forkPath) => existsSync4(showcasePath(forkPath));
|
|
1389
|
+
function showcaseAdmin(forkPath) {
|
|
1390
|
+
const { admin } = JSON.parse(readFileSync2(showcasePath(forkPath), "utf8"));
|
|
1391
|
+
if (typeof admin?.email !== "string") throw new Error(`${showcaseFile(forkPath)} names no admin email`);
|
|
1392
|
+
return admin.email;
|
|
1393
|
+
}
|
|
1376
1394
|
var shiftPort = (url) => {
|
|
1377
1395
|
const next = new URL(url);
|
|
1378
1396
|
next.port = String(Number(next.port) + PORT_SHIFT);
|
|
1379
1397
|
return next.toString().replace(/\/$/, "");
|
|
1380
1398
|
};
|
|
1381
|
-
async function resolveTestbed(forkPath) {
|
|
1399
|
+
async function resolveTestbed(forkPath, data = "seed") {
|
|
1382
1400
|
const [facts, databases] = await Promise.all([loadAppFacts(forkPath), loadLocalDatabases(forkPath)]);
|
|
1383
1401
|
const databasePort = databases.port(DATABASE);
|
|
1384
1402
|
if (!Number.isInteger(databasePort)) {
|
|
@@ -1398,6 +1416,7 @@ async function resolveTestbed(forkPath) {
|
|
|
1398
1416
|
// The seed creates the admin that backend/.env names, and a command signs in as that user
|
|
1399
1417
|
...fileEnv.ADMIN_EMAIL ? { ADMIN_EMAIL: fileEnv.ADMIN_EMAIL } : {},
|
|
1400
1418
|
...getEnvSnapshot(),
|
|
1419
|
+
...data === "showcase" ? { ADMIN_EMAIL: showcaseAdmin(forkPath) } : {},
|
|
1401
1420
|
...composeEnv,
|
|
1402
1421
|
NODE_ENV: "development",
|
|
1403
1422
|
DATABASE_URL: databases.url(DATABASE, "runtime_role"),
|
|
@@ -1411,12 +1430,22 @@ async function resolveTestbed(forkPath) {
|
|
|
1411
1430
|
({ port, internalPort }) => (internalPort === void 0 ? [port] : [port, internalPort]).map((value) => value + PORT_SHIFT)
|
|
1412
1431
|
),
|
|
1413
1432
|
container: `${composeEnv.PROJECT_SLUG}_${DATABASE_SERVICE}`,
|
|
1433
|
+
data,
|
|
1414
1434
|
yjs: facts.services.yjs?.enabled ?? false,
|
|
1415
1435
|
stateDir: toolDir(forkPath, "testbed", "repository")
|
|
1416
1436
|
};
|
|
1417
1437
|
}
|
|
1418
1438
|
var lockPath = (testbed) => join4(testbed.stateDir, "lock.json");
|
|
1419
1439
|
var logPath = (testbed) => join4(testbed.stateDir, "testbed.log");
|
|
1440
|
+
var dataPath = (testbed) => join4(testbed.stateDir, "data.json");
|
|
1441
|
+
function heldData(testbed) {
|
|
1442
|
+
try {
|
|
1443
|
+
return JSON.parse(readFileSync2(dataPath(testbed), "utf8")).data === "showcase" ? "showcase" : "seed";
|
|
1444
|
+
} catch {
|
|
1445
|
+
return "seed";
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
var describeData = (data) => data === "showcase" ? "the showcase" : "the seed data";
|
|
1420
1449
|
function isAlive(pid) {
|
|
1421
1450
|
try {
|
|
1422
1451
|
process8.kill(pid, 0);
|
|
@@ -1498,7 +1527,12 @@ async function startTestbed(testbed, fresh, drop = false) {
|
|
|
1498
1527
|
mkdirSync(testbed.stateDir, { recursive: true });
|
|
1499
1528
|
writeFileSync(
|
|
1500
1529
|
lockPath(testbed),
|
|
1501
|
-
JSON.stringify({
|
|
1530
|
+
JSON.stringify({
|
|
1531
|
+
pid: process8.pid,
|
|
1532
|
+
forkPath: testbed.forkPath,
|
|
1533
|
+
frontendUrl: testbed.frontendUrl,
|
|
1534
|
+
data: testbed.data
|
|
1535
|
+
})
|
|
1502
1536
|
);
|
|
1503
1537
|
const log = openSync(logPath(testbed), "w");
|
|
1504
1538
|
let stack;
|
|
@@ -1525,14 +1559,15 @@ async function startTestbed(testbed, fresh, drop = false) {
|
|
|
1525
1559
|
};
|
|
1526
1560
|
const forget = onShutdown(stop);
|
|
1527
1561
|
try {
|
|
1528
|
-
if (fresh) removeDatabase(testbed);
|
|
1562
|
+
if (fresh || testbed.data === "showcase" || heldData(testbed) !== testbed.data) removeDatabase(testbed);
|
|
1529
1563
|
const spinner = createSpinner(`starting the database on port ${testbed.databasePort}`);
|
|
1530
1564
|
if (!compose(testbed, ["up", "-d", "--wait", DATABASE_SERVICE])) {
|
|
1531
1565
|
throw new Error("the testbed database did not start: is Docker running?");
|
|
1532
1566
|
}
|
|
1533
1567
|
const seeded = isSeeded(testbed);
|
|
1534
|
-
spinner.text = seeded ? "migrating the database" :
|
|
1535
|
-
const
|
|
1568
|
+
spinner.text = seeded ? "migrating the database" : `migrating the database and seeding ${describeData(testbed.data)}`;
|
|
1569
|
+
const seed = testbed.data === "showcase" ? ["seed", "showcase"] : ["seed"];
|
|
1570
|
+
const prepare = seeded ? ["--filter", "backend", "migrate"] : seed;
|
|
1536
1571
|
const prepared = spawnSync3("pnpm", prepare, {
|
|
1537
1572
|
cwd: testbed.forkPath,
|
|
1538
1573
|
env: testbed.env,
|
|
@@ -1540,7 +1575,11 @@ async function startTestbed(testbed, fresh, drop = false) {
|
|
|
1540
1575
|
});
|
|
1541
1576
|
if (prepared.status !== 0) throw new Error(`\`pnpm ${prepare.join(" ")}\` failed:
|
|
1542
1577
|
${logTail(testbed)}`);
|
|
1543
|
-
|
|
1578
|
+
writeFileSync(dataPath(testbed), JSON.stringify({ data: testbed.data }));
|
|
1579
|
+
printStep(
|
|
1580
|
+
"database",
|
|
1581
|
+
`${seeded ? "migrated" : `seeded with ${describeData(testbed.data)}`}, port ${testbed.databasePort}`
|
|
1582
|
+
);
|
|
1544
1583
|
const relay = testbed.yjs ? "pnpm --filter yjs-worker dev & " : "";
|
|
1545
1584
|
stack = spawn("sh", ["-c", `${relay}pnpm offline`], {
|
|
1546
1585
|
cwd: testbed.forkPath,
|
|
@@ -1608,14 +1647,19 @@ function ownLock(testbed) {
|
|
|
1608
1647
|
}
|
|
1609
1648
|
return lock;
|
|
1610
1649
|
}
|
|
1611
|
-
async function withTestbed(forkPath, use, { fresh = false, destructive = false } = {}) {
|
|
1612
|
-
const testbed = await resolveTestbed(forkPath);
|
|
1650
|
+
async function withTestbed(forkPath, use, { fresh = false, destructive = false, data = "seed" } = {}) {
|
|
1651
|
+
const testbed = await resolveTestbed(forkPath, data);
|
|
1613
1652
|
const lock = ownLock(testbed);
|
|
1614
1653
|
if (lock && destructive) {
|
|
1615
1654
|
throw new Error(
|
|
1616
1655
|
`a testbed runs at ${lock.frontendUrl}: this command needs one of its own, so stop that one first (\`pnpm cella testbed stop\`)`
|
|
1617
1656
|
);
|
|
1618
1657
|
}
|
|
1658
|
+
if (lock && (lock.data ?? "seed") !== data) {
|
|
1659
|
+
throw new Error(
|
|
1660
|
+
`a testbed with ${describeData(lock.data ?? "seed")} runs at ${lock.frontendUrl}: this command needs ${describeData(data)}, so stop that one first (\`pnpm cella testbed stop\`)`
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1619
1663
|
if (lock) {
|
|
1620
1664
|
await untilAnswering(
|
|
1621
1665
|
testbed,
|
|
@@ -1633,10 +1677,12 @@ async function withTestbed(forkPath, use, { fresh = false, destructive = false }
|
|
|
1633
1677
|
}
|
|
1634
1678
|
async function runTestbed(forkPath, options) {
|
|
1635
1679
|
if (options.job === "stop") return stopTestbed(await resolveTestbed(forkPath));
|
|
1680
|
+
if (options.showcase && !hasShowcase(forkPath)) throw new Error(`this app keeps no ${showcaseFile(forkPath)}`);
|
|
1681
|
+
const data = options.showcase ? "showcase" : "seed";
|
|
1636
1682
|
if (options.command.length > 0) {
|
|
1637
|
-
return withTestbed(forkPath, (testbed2) => runCommand(forkPath, testbed2.env, options.command), options);
|
|
1683
|
+
return withTestbed(forkPath, (testbed2) => runCommand(forkPath, testbed2.env, options.command), { ...options, data });
|
|
1638
1684
|
}
|
|
1639
|
-
const testbed = await resolveTestbed(forkPath);
|
|
1685
|
+
const testbed = await resolveTestbed(forkPath, data);
|
|
1640
1686
|
const lock = ownLock(testbed);
|
|
1641
1687
|
if (lock) {
|
|
1642
1688
|
throw new Error(
|
|
@@ -1664,7 +1710,10 @@ var testbedCommand = {
|
|
|
1664
1710
|
["start", "start it and hold it until ctrl-c"],
|
|
1665
1711
|
["stop", "stop the testbed that runs, or one left behind"]
|
|
1666
1712
|
],
|
|
1667
|
-
options: [
|
|
1713
|
+
options: [
|
|
1714
|
+
["--fresh", "start from an empty database: migrate and seed it again"],
|
|
1715
|
+
["--showcase", "hold the showcase seed, which the screenshots shoot, for a review loop"]
|
|
1716
|
+
],
|
|
1668
1717
|
passthrough: [
|
|
1669
1718
|
"command",
|
|
1670
1719
|
"after `--`: run it against the testbed (this checkout's running one, else one started for it and stopped after)"
|
|
@@ -1672,14 +1721,14 @@ var testbedCommand = {
|
|
|
1672
1721
|
parse: ({ job = "start", opts, passthrough }) => {
|
|
1673
1722
|
if (job !== "start" && job !== "stop") throw new Error(`unknown job '${job}'. expected one of: start, stop`);
|
|
1674
1723
|
if (job === "stop" && passthrough.length > 0) throw new Error("stop takes no command");
|
|
1675
|
-
return { job, fresh: opts.fresh === true, command: passthrough };
|
|
1724
|
+
return { job, fresh: opts.fresh === true, showcase: opts.showcase === true, command: passthrough };
|
|
1676
1725
|
},
|
|
1677
1726
|
run: runTestbed
|
|
1678
1727
|
};
|
|
1679
1728
|
|
|
1680
1729
|
// src/tools/a11y/audit.ts
|
|
1681
|
-
import { mkdirSync as mkdirSync3, statSync, writeFileSync as
|
|
1682
|
-
import { join as
|
|
1730
|
+
import { mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1731
|
+
import { join as join10 } from "path";
|
|
1683
1732
|
|
|
1684
1733
|
// src/tools/a11y/axe-scan.ts
|
|
1685
1734
|
import axe from "axe-core";
|
|
@@ -2349,8 +2398,97 @@ function recordDecisions(forkPath, decisions) {
|
|
|
2349
2398
|
}
|
|
2350
2399
|
|
|
2351
2400
|
// src/tools/a11y/probes/page-structure.ts
|
|
2352
|
-
import { readFileSync as
|
|
2401
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2402
|
+
import { join as join8 } from "path";
|
|
2403
|
+
|
|
2404
|
+
// src/tools/i18n/locales.ts
|
|
2405
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2353
2406
|
import { join as join7 } from "path";
|
|
2407
|
+
var localesDir = (forkPath) => movedPath(forkPath, "shared/locales", "locales");
|
|
2408
|
+
var MANIFEST = "namespaces.json";
|
|
2409
|
+
var SOURCE_LANGUAGE = "en";
|
|
2410
|
+
var entryPattern = /^(\s+)("(?:[^"\\]|\\.)*")\s*:\s*("(?:[^"\\]|\\.)*"),?\s*$/;
|
|
2411
|
+
function parseLocaleFile(path, text2) {
|
|
2412
|
+
if (text2.trim() === "{}") return { path, indent: " ", entries: [] };
|
|
2413
|
+
const lines = text2.replace(/\n$/, "").split("\n");
|
|
2414
|
+
if (lines[0] !== "{" || lines.at(-1) !== "}") return "not one JSON object with one entry per line";
|
|
2415
|
+
const entries = [];
|
|
2416
|
+
let indent = " ";
|
|
2417
|
+
for (const line of lines.slice(1, -1)) {
|
|
2418
|
+
const match = entryPattern.exec(line);
|
|
2419
|
+
if (!match) return `cannot read the line ${JSON.stringify(line.trim())}`;
|
|
2420
|
+
indent = match[1];
|
|
2421
|
+
entries.push({ key: JSON.parse(match[2]), value: JSON.parse(match[3]), raw: [match[2], match[3]] });
|
|
2422
|
+
}
|
|
2423
|
+
return { path, indent, entries };
|
|
2424
|
+
}
|
|
2425
|
+
function serializeLocaleFile(file) {
|
|
2426
|
+
if (file.entries.length === 0) return "{}\n";
|
|
2427
|
+
return `{
|
|
2428
|
+
${file.entries.map(({ raw }) => `${file.indent}${raw[0]}: ${raw[1]}`).join(",\n")}
|
|
2429
|
+
}
|
|
2430
|
+
`;
|
|
2431
|
+
}
|
|
2432
|
+
function writeLocaleFile(forkPath, file) {
|
|
2433
|
+
writeFileSync3(join7(forkPath, file.path), serializeLocaleFile(file));
|
|
2434
|
+
}
|
|
2435
|
+
var textsOf = (file) => Object.fromEntries((file?.entries ?? []).map(({ key, value }) => [key, value]));
|
|
2436
|
+
var readJsonObject = (path) => {
|
|
2437
|
+
const parsed2 = JSON.parse(readFileSync4(path, "utf8"));
|
|
2438
|
+
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) throw new Error(`${path} is not a JSON object`);
|
|
2439
|
+
return Object.fromEntries(Object.entries(parsed2));
|
|
2440
|
+
};
|
|
2441
|
+
function readLocales(forkPath) {
|
|
2442
|
+
const localesPath = localesDir(forkPath);
|
|
2443
|
+
const dir = join7(forkPath, localesPath);
|
|
2444
|
+
if (!existsSync7(join7(dir, SOURCE_LANGUAGE))) throw new Error(`no ${localesPath}/${SOURCE_LANGUAGE}/ in ${forkPath}`);
|
|
2445
|
+
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));
|
|
2446
|
+
const files2 = /* @__PURE__ */ new Map();
|
|
2447
|
+
for (const language2 of languages) {
|
|
2448
|
+
for (const name of readdirSync(join7(dir, language2)).filter((name2) => name2.endsWith(".json"))) {
|
|
2449
|
+
const path = `${localesPath}/${language2}/${name}`;
|
|
2450
|
+
const parsed2 = parseLocaleFile(path, readFileSync4(join7(forkPath, path), "utf8"));
|
|
2451
|
+
if (typeof parsed2 === "string") throw new Error(`${path}: ${parsed2}; pnpm lint:fix writes one entry per line`);
|
|
2452
|
+
files2.set(`${language2}/${name.slice(0, -".json".length)}`, parsed2);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
const manifestPath = join7(dir, MANIFEST);
|
|
2456
|
+
if (!existsSync7(manifestPath)) throw new Error(`no ${localesPath}/${MANIFEST}: upgrade the app from cella first`);
|
|
2457
|
+
const merged = readJsonObject(manifestPath);
|
|
2458
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
2459
|
+
for (const [namespace, sources] of Object.entries(merged)) {
|
|
2460
|
+
if (!Array.isArray(sources) || !sources.every((source) => typeof source === "string")) {
|
|
2461
|
+
throw new Error(`${localesPath}/${MANIFEST}: ${namespace} must list file names`);
|
|
2462
|
+
}
|
|
2463
|
+
namespaces.set(namespace, sources);
|
|
2464
|
+
}
|
|
2465
|
+
const mergedFiles = new Set([...namespaces.values()].flat());
|
|
2466
|
+
const ownFiles = [...new Set([...files2.keys()].map((key) => key.split("/")[1]))].filter(
|
|
2467
|
+
(file) => !mergedFiles.has(file)
|
|
2468
|
+
);
|
|
2469
|
+
for (const file of ownFiles.sort()) namespaces.set(file, [file]);
|
|
2470
|
+
return { dir: localesPath, languages, namespaces, files: files2 };
|
|
2471
|
+
}
|
|
2472
|
+
function namespaceTexts(locales, language2, namespace) {
|
|
2473
|
+
const sources = locales.namespaces.get(namespace) ?? [];
|
|
2474
|
+
return Object.assign({}, ...sources.map((file) => textsOf(locales.files.get(`${language2}/${file}`))));
|
|
2475
|
+
}
|
|
2476
|
+
var lineOf = (file, key) => file.entries.findIndex((entry) => entry.key === key) + 2;
|
|
2477
|
+
var pluralSuffix = /_(ordinal_)?(zero|one|two|few|many|other)$/;
|
|
2478
|
+
function pluralForm(key) {
|
|
2479
|
+
const match = pluralSuffix.exec(key);
|
|
2480
|
+
if (!match) return void 0;
|
|
2481
|
+
return { base: key.slice(0, match.index), category: match[2], ordinal: !!match[1] };
|
|
2482
|
+
}
|
|
2483
|
+
var withPluralForms = (key) => [
|
|
2484
|
+
key,
|
|
2485
|
+
...["zero", "one", "two", "few", "many", "other"].flatMap((category) => [
|
|
2486
|
+
`${key}_${category}`,
|
|
2487
|
+
`${key}_ordinal_${category}`
|
|
2488
|
+
])
|
|
2489
|
+
];
|
|
2490
|
+
|
|
2491
|
+
// src/tools/a11y/probes/page-structure.ts
|
|
2354
2492
|
var titles = { criteria: ["2.4.2"], check: "probe:titles", what: "A distinct, descriptive page title" };
|
|
2355
2493
|
var language = {
|
|
2356
2494
|
criteria: ["3.1.1"],
|
|
@@ -2384,7 +2522,7 @@ var pageStructure = async (page, state, { app }) => {
|
|
|
2384
2522
|
var languageSwitch = async (page, state, { app, forkPath }) => {
|
|
2385
2523
|
if (state.open) return [];
|
|
2386
2524
|
const names = JSON.parse(
|
|
2387
|
-
|
|
2525
|
+
readFileSync5(join8(forkPath, localesDir(forkPath), app.defaultLanguage, "common.json"), "utf8")
|
|
2388
2526
|
);
|
|
2389
2527
|
const findings2 = [];
|
|
2390
2528
|
for (const other of app.languages.filter((lng) => lng !== app.defaultLanguage)) {
|
|
@@ -2718,15 +2856,15 @@ var inputs = async (page, state) => {
|
|
|
2718
2856
|
};
|
|
2719
2857
|
|
|
2720
2858
|
// src/tools/a11y/review-packet.ts
|
|
2721
|
-
import { mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as
|
|
2722
|
-
import { join as
|
|
2723
|
-
var packetDir = (run, stateId) =>
|
|
2859
|
+
import { mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2860
|
+
import { join as join9 } from "path";
|
|
2861
|
+
var packetDir = (run, stateId) => join9(run.resultsDir, "review", stateId);
|
|
2724
2862
|
function writePacketFile(run, stateId, name, content) {
|
|
2725
2863
|
mkdirSync2(packetDir(run, stateId), { recursive: true });
|
|
2726
|
-
|
|
2864
|
+
writeFileSync4(join9(packetDir(run, stateId), name), content);
|
|
2727
2865
|
}
|
|
2728
2866
|
function removePacketFile(run, stateId, name) {
|
|
2729
|
-
rmSync2(
|
|
2867
|
+
rmSync2(join9(packetDir(run, stateId), name), { force: true });
|
|
2730
2868
|
}
|
|
2731
2869
|
function pageFacts() {
|
|
2732
2870
|
const text2 = (el) => (el?.textContent ?? "").trim().replace(/\s+/g, " ");
|
|
@@ -3187,7 +3325,7 @@ async function runAudit(run, scope, testbed, options) {
|
|
|
3187
3325
|
const startedAt = Date.now();
|
|
3188
3326
|
const email = options.email ?? testbed.env.ADMIN_EMAIL;
|
|
3189
3327
|
if (!email) throw new Error("no audit user: pass --email, or set ADMIN_EMAIL in backend/.env");
|
|
3190
|
-
const builtAt = statSync(
|
|
3328
|
+
const builtAt = statSync(join10(run.forkPath, "frontend/dist/index.html"), { throwIfNoEntry: false })?.mtime;
|
|
3191
3329
|
printStep("auditing", `${testbed.frontendUrl}, frontend build of ${builtAt?.toLocaleString() ?? "unknown date"}`);
|
|
3192
3330
|
const states = options.states.length > 0 ? scope.states.filter((state) => options.states.includes(state.id)) : scope.states;
|
|
3193
3331
|
const session = await startAudit(run.forkPath, scope, { email, env: testbed.env });
|
|
@@ -3227,12 +3365,12 @@ async function runAudit(run, scope, testbed, options) {
|
|
|
3227
3365
|
mkdirSync3(run.resultsDir, { recursive: true });
|
|
3228
3366
|
const axe2 = summarizeAxe(scans);
|
|
3229
3367
|
for (const [id, items] of axe2.evidence) for (const item of items) evidence.add([id], item);
|
|
3230
|
-
|
|
3368
|
+
writeFileSync5(join10(run.resultsDir, "axe.json"), `${JSON.stringify({ ...axe2.report, errors }, null, 2)}
|
|
3231
3369
|
`);
|
|
3232
3370
|
evidence.addFindings(acrossStates(findings2));
|
|
3233
3371
|
runCodeChecks(evidence, run, scope.sources);
|
|
3234
3372
|
const skipped2 = Object.keys(errors).length;
|
|
3235
|
-
const target = options.states.length > 0 || skipped2 ?
|
|
3373
|
+
const target = options.states.length > 0 || skipped2 ? join10(run.resultsDir, "ledger-partial.json") : join10(run.forkPath, ledgerFile);
|
|
3236
3374
|
const ledger = writeLedger(
|
|
3237
3375
|
run.forkPath,
|
|
3238
3376
|
evidence.byCriterion,
|
|
@@ -3256,10 +3394,10 @@ async function runAudit(run, scope, testbed, options) {
|
|
|
3256
3394
|
}
|
|
3257
3395
|
|
|
3258
3396
|
// src/tools/a11y/decide.ts
|
|
3259
|
-
import { readFileSync as
|
|
3397
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3260
3398
|
import { resolve as resolve4 } from "path";
|
|
3261
3399
|
function runDecide(forkPath, { file, decision }) {
|
|
3262
|
-
const input2 = file ? JSON.parse(
|
|
3400
|
+
const input2 = file ? JSON.parse(readFileSync6(resolve4(file), "utf8")) : [decision];
|
|
3263
3401
|
if (!Array.isArray(input2)) throw new Error(`${file} holds no list of decisions`);
|
|
3264
3402
|
const decisions = input2.map(parseDecision);
|
|
3265
3403
|
const ledger = recordDecisions(forkPath, decisions);
|
|
@@ -3273,8 +3411,8 @@ function runDecide(forkPath, { file, decision }) {
|
|
|
3273
3411
|
|
|
3274
3412
|
// src/tools/a11y/report.ts
|
|
3275
3413
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
3276
|
-
import { copyFileSync, mkdirSync as mkdirSync4, writeFileSync as
|
|
3277
|
-
import { dirname as dirname4, join as
|
|
3414
|
+
import { copyFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3415
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
3278
3416
|
var statusLabel = {
|
|
3279
3417
|
supports: "Supports",
|
|
3280
3418
|
"partially-supports": "Partially Supports",
|
|
@@ -3393,7 +3531,7 @@ Not evaluated.
|
|
|
3393
3531
|
|
|
3394
3532
|
This report describes the product as tested on the report date. Accessibility can change as the product changes.
|
|
3395
3533
|
`;
|
|
3396
|
-
const tracked = spawnSync5("git", ["ls-files", "frontend/src", "backend/src", "locales"], {
|
|
3534
|
+
const tracked = spawnSync5("git", ["ls-files", "frontend/src", "backend/src", "shared/locales", "locales"], {
|
|
3397
3535
|
cwd: forkPath,
|
|
3398
3536
|
encoding: "utf8"
|
|
3399
3537
|
}).stdout.split("\n");
|
|
@@ -3420,7 +3558,7 @@ NVDA and Firefox on Windows, keyboard only, in light and dark mode.
|
|
|
3420
3558
|
|
|
3421
3559
|
## Confirm: decided by an agent (${byAgent.length})
|
|
3422
3560
|
|
|
3423
|
-
The agent judged these from the review packets in \`${
|
|
3561
|
+
The agent judged these from the review packets in \`${join11(resultsDir, "review")}/\`. Check its evidence, then record your own
|
|
3424
3562
|
decision; a wrong Supports here ends up in the published report.
|
|
3425
3563
|
|
|
3426
3564
|
${byAgent.map((r) => {
|
|
@@ -3464,11 +3602,11 @@ ${failing.map((r) => {
|
|
|
3464
3602
|
}).join("\n")}
|
|
3465
3603
|
`;
|
|
3466
3604
|
mkdirSync4(resultsDir, { recursive: true });
|
|
3467
|
-
|
|
3468
|
-
|
|
3605
|
+
writeFileSync6(join11(resultsDir, "vpat-draft.md"), vpat);
|
|
3606
|
+
writeFileSync6(join11(resultsDir, "manual-pass.md"), checklist);
|
|
3469
3607
|
const html = toHtml(vpat, `${app.name} Accessibility Conformance Report`);
|
|
3470
|
-
const pdfPath =
|
|
3471
|
-
|
|
3608
|
+
const pdfPath = join11(resultsDir, "conformance-report.pdf");
|
|
3609
|
+
writeFileSync6(join11(resultsDir, "conformance-report.html"), html);
|
|
3472
3610
|
const { chromium } = await loadPlaywright(forkPath);
|
|
3473
3611
|
const browser = await chromium.launch();
|
|
3474
3612
|
try {
|
|
@@ -3488,12 +3626,12 @@ ${failing.map((r) => {
|
|
|
3488
3626
|
if (unconfirmed && !draft) {
|
|
3489
3627
|
const ids = [...open, ...byAgent].map((r) => r.id).join(", ");
|
|
3490
3628
|
throw new Error(
|
|
3491
|
-
`not published: ${open.length} criteria are open and ${byAgent.length} are decided by an agent only (${ids}). See ${
|
|
3629
|
+
`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`
|
|
3492
3630
|
);
|
|
3493
3631
|
}
|
|
3494
3632
|
const publicPath = "frontend/public/static/common/accessibility-conformance-report.pdf";
|
|
3495
|
-
mkdirSync4(dirname4(
|
|
3496
|
-
copyFileSync(pdfPath,
|
|
3633
|
+
mkdirSync4(dirname4(join11(forkPath, publicPath)), { recursive: true });
|
|
3634
|
+
copyFileSync(pdfPath, join11(forkPath, publicPath));
|
|
3497
3635
|
const count3 = (status) => ledger.criteria.filter((r) => r.status === status).length;
|
|
3498
3636
|
const review = {
|
|
3499
3637
|
standard: ledger.standard,
|
|
@@ -3600,8 +3738,8 @@ var a11yCommand = {
|
|
|
3600
3738
|
};
|
|
3601
3739
|
|
|
3602
3740
|
// src/tools/bench/command.ts
|
|
3603
|
-
import { existsSync as
|
|
3604
|
-
import { basename, join as
|
|
3741
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "fs";
|
|
3742
|
+
import { basename, join as join12 } from "path";
|
|
3605
3743
|
|
|
3606
3744
|
// src/tools/bench/request.ts
|
|
3607
3745
|
var BENCH_REQUEST_ENV = "CELLA_BENCH";
|
|
@@ -3610,11 +3748,11 @@ var BENCH_REQUEST_ENV = "CELLA_BENCH";
|
|
|
3610
3748
|
var BENCH_DIR = "backend/bench";
|
|
3611
3749
|
var ENTRY_COMMAND = ["pnpm", "exec", "tsx", "--no-experimental-webstorage", "bench/run.ts"];
|
|
3612
3750
|
async function runBench(forkPath, options) {
|
|
3613
|
-
if (!
|
|
3751
|
+
if (!existsSync8(join12(forkPath, BENCH_DIR, "run.ts"))) {
|
|
3614
3752
|
throw new Error(`${BENCH_DIR}/run.ts is missing: upgrade the app from cella first`);
|
|
3615
3753
|
}
|
|
3616
|
-
const scenariosDir =
|
|
3617
|
-
const names =
|
|
3754
|
+
const scenariosDir = join12(forkPath, BENCH_DIR, "scenarios");
|
|
3755
|
+
const names = readdirSync2(scenariosDir).filter((file) => file.endsWith(".ts")).map((file) => basename(file, ".ts")).sort();
|
|
3618
3756
|
if (options.scenario && !names.includes(options.scenario)) {
|
|
3619
3757
|
throw new Error(`unknown scenario '${options.scenario}'. expected one of: ${names.join(", ")}`);
|
|
3620
3758
|
}
|
|
@@ -3626,7 +3764,7 @@ async function runBench(forkPath, options) {
|
|
|
3626
3764
|
scenariosDir,
|
|
3627
3765
|
dataDir: toolDir(forkPath, "bench", "repository")
|
|
3628
3766
|
};
|
|
3629
|
-
const run = (env) => runCommand(
|
|
3767
|
+
const run = (env) => runCommand(join12(forkPath, "backend"), { ...env, [BENCH_REQUEST_ENV]: JSON.stringify(request) }, ENTRY_COMMAND);
|
|
3630
3768
|
return options.isolated ? withTestbed(forkPath, (testbed) => run(testbed.env)) : run(getEnvSnapshot());
|
|
3631
3769
|
}
|
|
3632
3770
|
var benchCommand = {
|
|
@@ -3665,98 +3803,12 @@ import { spawnSync as spawnSync6 } from "child_process";
|
|
|
3665
3803
|
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
3666
3804
|
import { join as join14 } from "path";
|
|
3667
3805
|
|
|
3668
|
-
// src/tools/i18n/locales.ts
|
|
3669
|
-
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
3670
|
-
import { join as join12 } from "path";
|
|
3671
|
-
var LOCALES_DIR = "locales";
|
|
3672
|
-
var MANIFEST = "namespaces.json";
|
|
3673
|
-
var SOURCE_LANGUAGE = "en";
|
|
3674
|
-
var entryPattern = /^(\s+)("(?:[^"\\]|\\.)*")\s*:\s*("(?:[^"\\]|\\.)*"),?\s*$/;
|
|
3675
|
-
function parseLocaleFile(path, text2) {
|
|
3676
|
-
if (text2.trim() === "{}") return { path, indent: " ", entries: [] };
|
|
3677
|
-
const lines = text2.replace(/\n$/, "").split("\n");
|
|
3678
|
-
if (lines[0] !== "{" || lines.at(-1) !== "}") return "not one JSON object with one entry per line";
|
|
3679
|
-
const entries = [];
|
|
3680
|
-
let indent = " ";
|
|
3681
|
-
for (const line of lines.slice(1, -1)) {
|
|
3682
|
-
const match = entryPattern.exec(line);
|
|
3683
|
-
if (!match) return `cannot read the line ${JSON.stringify(line.trim())}`;
|
|
3684
|
-
indent = match[1];
|
|
3685
|
-
entries.push({ key: JSON.parse(match[2]), value: JSON.parse(match[3]), raw: [match[2], match[3]] });
|
|
3686
|
-
}
|
|
3687
|
-
return { path, indent, entries };
|
|
3688
|
-
}
|
|
3689
|
-
function serializeLocaleFile(file) {
|
|
3690
|
-
if (file.entries.length === 0) return "{}\n";
|
|
3691
|
-
return `{
|
|
3692
|
-
${file.entries.map(({ raw }) => `${file.indent}${raw[0]}: ${raw[1]}`).join(",\n")}
|
|
3693
|
-
}
|
|
3694
|
-
`;
|
|
3695
|
-
}
|
|
3696
|
-
function writeLocaleFile(forkPath, file) {
|
|
3697
|
-
writeFileSync6(join12(forkPath, file.path), serializeLocaleFile(file));
|
|
3698
|
-
}
|
|
3699
|
-
var textsOf = (file) => Object.fromEntries((file?.entries ?? []).map(({ key, value }) => [key, value]));
|
|
3700
|
-
var readJsonObject = (path) => {
|
|
3701
|
-
const parsed2 = JSON.parse(readFileSync6(path, "utf8"));
|
|
3702
|
-
if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) throw new Error(`${path} is not a JSON object`);
|
|
3703
|
-
return Object.fromEntries(Object.entries(parsed2));
|
|
3704
|
-
};
|
|
3705
|
-
function readLocales(forkPath) {
|
|
3706
|
-
const dir = join12(forkPath, LOCALES_DIR);
|
|
3707
|
-
if (!existsSync8(join12(dir, SOURCE_LANGUAGE))) throw new Error(`no ${LOCALES_DIR}/${SOURCE_LANGUAGE}/ in ${forkPath}`);
|
|
3708
|
-
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));
|
|
3709
|
-
const files2 = /* @__PURE__ */ new Map();
|
|
3710
|
-
for (const language2 of languages) {
|
|
3711
|
-
for (const name of readdirSync2(join12(dir, language2)).filter((name2) => name2.endsWith(".json"))) {
|
|
3712
|
-
const path = `${LOCALES_DIR}/${language2}/${name}`;
|
|
3713
|
-
const parsed2 = parseLocaleFile(path, readFileSync6(join12(forkPath, path), "utf8"));
|
|
3714
|
-
if (typeof parsed2 === "string") throw new Error(`${path}: ${parsed2}; pnpm lint:fix writes one entry per line`);
|
|
3715
|
-
files2.set(`${language2}/${name.slice(0, -".json".length)}`, parsed2);
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
const manifestPath = join12(dir, MANIFEST);
|
|
3719
|
-
if (!existsSync8(manifestPath)) throw new Error(`no ${LOCALES_DIR}/${MANIFEST}: upgrade the app from cella first`);
|
|
3720
|
-
const merged = readJsonObject(manifestPath);
|
|
3721
|
-
const namespaces = /* @__PURE__ */ new Map();
|
|
3722
|
-
for (const [namespace, sources] of Object.entries(merged)) {
|
|
3723
|
-
if (!Array.isArray(sources) || !sources.every((source) => typeof source === "string")) {
|
|
3724
|
-
throw new Error(`${LOCALES_DIR}/${MANIFEST}: ${namespace} must list file names`);
|
|
3725
|
-
}
|
|
3726
|
-
namespaces.set(namespace, sources);
|
|
3727
|
-
}
|
|
3728
|
-
const mergedFiles = new Set([...namespaces.values()].flat());
|
|
3729
|
-
const ownFiles = [...new Set([...files2.keys()].map((key) => key.split("/")[1]))].filter(
|
|
3730
|
-
(file) => !mergedFiles.has(file)
|
|
3731
|
-
);
|
|
3732
|
-
for (const file of ownFiles.sort()) namespaces.set(file, [file]);
|
|
3733
|
-
return { languages, namespaces, files: files2 };
|
|
3734
|
-
}
|
|
3735
|
-
function namespaceTexts(locales, language2, namespace) {
|
|
3736
|
-
const sources = locales.namespaces.get(namespace) ?? [];
|
|
3737
|
-
return Object.assign({}, ...sources.map((file) => textsOf(locales.files.get(`${language2}/${file}`))));
|
|
3738
|
-
}
|
|
3739
|
-
var lineOf = (file, key) => file.entries.findIndex((entry) => entry.key === key) + 2;
|
|
3740
|
-
var pluralSuffix = /_(ordinal_)?(zero|one|two|few|many|other)$/;
|
|
3741
|
-
function pluralForm(key) {
|
|
3742
|
-
const match = pluralSuffix.exec(key);
|
|
3743
|
-
if (!match) return void 0;
|
|
3744
|
-
return { base: key.slice(0, match.index), category: match[2], ordinal: !!match[1] };
|
|
3745
|
-
}
|
|
3746
|
-
var withPluralForms = (key) => [
|
|
3747
|
-
key,
|
|
3748
|
-
...["zero", "one", "two", "few", "many", "other"].flatMap((category) => [
|
|
3749
|
-
`${key}_${category}`,
|
|
3750
|
-
`${key}_ordinal_${category}`
|
|
3751
|
-
])
|
|
3752
|
-
];
|
|
3753
|
-
|
|
3754
3806
|
// src/tools/i18n/scan.ts
|
|
3755
3807
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3756
3808
|
import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
3757
3809
|
import { dirname as dirname5, join as join13 } from "path";
|
|
3758
3810
|
var codeFile = /\.(?:[cm]?[jt]sx?)$/;
|
|
3759
|
-
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."));
|
|
3811
|
+
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."));
|
|
3760
3812
|
var MAX_FILE_BYTES = 1e6;
|
|
3761
3813
|
var MAX_COMBINATIONS = 500;
|
|
3762
3814
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -4173,10 +4225,10 @@ function printList(title, items) {
|
|
|
4173
4225
|
console.info(` ${colors_default.dim(`${items.slice(0, LIST_MAX).join(", ")}${more}`)}`);
|
|
4174
4226
|
}
|
|
4175
4227
|
var describe = ({ key, missing, extra }) => [key, ...missing.map((name) => `-${name}`), ...extra.map((name) => `+${name}`)].join(" ");
|
|
4176
|
-
function printStatus(report) {
|
|
4228
|
+
function printStatus(report, dir) {
|
|
4177
4229
|
const label = ({ name, files: files2 }) => files2.length > 1 || files2[0] !== name ? `${name} (${files2.join(" + ")})` : name;
|
|
4178
4230
|
console.info(
|
|
4179
|
-
`${colors_default.bold(`${
|
|
4231
|
+
`${colors_default.bold(`${dir}/`)} ${colors_default.dim(`${report.languages.join(", ")} \xB7 namespaces ${report.namespaces.map(label).join(", ")}`)}`
|
|
4180
4232
|
);
|
|
4181
4233
|
console.info();
|
|
4182
4234
|
console.info(colors_default.dim(row2(columns.map(([title]) => title))));
|
|
@@ -4248,9 +4300,10 @@ function printStatus(report) {
|
|
|
4248
4300
|
console.info(` ${colors_default.dim("missing and same-as-English keys: --json lists them \xB7 cella i18n unused")}`);
|
|
4249
4301
|
}
|
|
4250
4302
|
function runStatus(forkPath, json) {
|
|
4251
|
-
const
|
|
4303
|
+
const locales = readLocales(forkPath);
|
|
4304
|
+
const report = buildStatus(locales);
|
|
4252
4305
|
if (json) writeStdout(JSON.stringify(report, null, 2));
|
|
4253
|
-
else printStatus(report);
|
|
4306
|
+
else printStatus(report, locales.dir);
|
|
4254
4307
|
return defectCount(report) === 0 ? 0 : 1;
|
|
4255
4308
|
}
|
|
4256
4309
|
|
|
@@ -5270,7 +5323,7 @@ var pentestCommand = {
|
|
|
5270
5323
|
|
|
5271
5324
|
// src/tools/screenshots/shoot.ts
|
|
5272
5325
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
5273
|
-
import { mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync5, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
5326
|
+
import { mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync5, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
5274
5327
|
import { tmpdir } from "os";
|
|
5275
5328
|
import { basename as basename4, dirname as dirname7, join as join21, resolve as resolve7 } from "path";
|
|
5276
5329
|
|
|
@@ -5350,6 +5403,7 @@ async function spinnersGone(page, quiet = 1e3, cap = 45e3) {
|
|
|
5350
5403
|
await page.waitForTimeout(100);
|
|
5351
5404
|
}
|
|
5352
5405
|
}
|
|
5406
|
+
var hasMfaScript = (forkPath) => Boolean(JSON.parse(readFileSync13(join21(forkPath, "backend/package.json"), "utf8")).scripts?.["showcase:mfa"]);
|
|
5353
5407
|
async function shoot(forkPath, list2, testbed, options) {
|
|
5354
5408
|
const email = options.email ?? testbed.env.ADMIN_EMAIL;
|
|
5355
5409
|
if (!email) throw new Error("no shot user: pass --email, or set ADMIN_EMAIL in backend/.env");
|
|
@@ -5376,15 +5430,17 @@ async function shoot(forkPath, list2, testbed, options) {
|
|
|
5376
5430
|
if (encoded.status !== 0) throw new Error(`cwebp could not write ${path}: ${encoded.stderr.trim()}`);
|
|
5377
5431
|
};
|
|
5378
5432
|
try {
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5433
|
+
if (testbed.data === "seed" && hasMfaScript(forkPath)) {
|
|
5434
|
+
const showcase = spawnSync9("pnpm", ["--silent", "--filter", "backend", "showcase:mfa", email], {
|
|
5435
|
+
cwd: forkPath,
|
|
5436
|
+
env: testbed.env,
|
|
5437
|
+
encoding: "utf8"
|
|
5438
|
+
});
|
|
5439
|
+
if (showcase.status !== 0) throw new Error(`showcase:mfa failed:
|
|
5385
5440
|
${showcase.stdout}
|
|
5386
5441
|
${showcase.stderr}`);
|
|
5387
|
-
|
|
5442
|
+
printStep("mfa", showcase.stdout.trim().split("\n").at(-1));
|
|
5443
|
+
}
|
|
5388
5444
|
for (const prepare of list2.prepare) await prepare(signedIn(user));
|
|
5389
5445
|
const values = {};
|
|
5390
5446
|
for (const [name, resolve12] of Object.entries(list2.placeholders)) values[name] = await resolve12(signedIn(user));
|
|
@@ -5468,7 +5524,8 @@ async function runScreenshots(forkPath, options) {
|
|
|
5468
5524
|
}
|
|
5469
5525
|
requireEncoder(list2.format);
|
|
5470
5526
|
await requireChromium(forkPath);
|
|
5471
|
-
|
|
5527
|
+
const data = hasShowcase(forkPath) ? "showcase" : "seed";
|
|
5528
|
+
return withTestbed(forkPath, (testbed) => shoot(forkPath, list2, testbed, options), { data });
|
|
5472
5529
|
}
|
|
5473
5530
|
var screenshotsCommand = {
|
|
5474
5531
|
name: "screenshots",
|
|
@@ -5478,7 +5535,7 @@ var screenshotsCommand = {
|
|
|
5478
5535
|
operands: ["[ids...]", "the shots to take (default: all)"],
|
|
5479
5536
|
options: [
|
|
5480
5537
|
["--out <dir>", "write into this folder, a trial run that leaves the working tree alone"],
|
|
5481
|
-
["--email <address>", "the shot user (default ADMIN_EMAIL in backend/.env)"]
|
|
5538
|
+
["--email <address>", "the shot user (default the showcase admin, else ADMIN_EMAIL in backend/.env)"]
|
|
5482
5539
|
],
|
|
5483
5540
|
parse: ({ operands, opts }) => ({
|
|
5484
5541
|
ids: operands,
|
|
@@ -5489,7 +5546,7 @@ var screenshotsCommand = {
|
|
|
5489
5546
|
};
|
|
5490
5547
|
|
|
5491
5548
|
// src/tools/style/index.ts
|
|
5492
|
-
import { readFileSync as
|
|
5549
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
5493
5550
|
import { join as join27 } from "path";
|
|
5494
5551
|
|
|
5495
5552
|
// src/utils/alignments.ts
|
|
@@ -5737,7 +5794,9 @@ var alignmentExempt = [
|
|
|
5737
5794
|
"json/",
|
|
5738
5795
|
"locales/",
|
|
5739
5796
|
"sdk/gen",
|
|
5740
|
-
"shared/config"
|
|
5797
|
+
"shared/config",
|
|
5798
|
+
"shared/json/",
|
|
5799
|
+
"shared/locales/"
|
|
5741
5800
|
];
|
|
5742
5801
|
function ownedPaths(configSource) {
|
|
5743
5802
|
const lists = [...configSource.matchAll(/\b(?:ignored|pinned): \[([\s\S]*?)\n {4}\]/g)].map((match) => match[1]);
|
|
@@ -6042,7 +6101,7 @@ function docFindings(file, source) {
|
|
|
6042
6101
|
}
|
|
6043
6102
|
|
|
6044
6103
|
// src/tools/style/frontend-rules.ts
|
|
6045
|
-
import { existsSync as existsSync19, readFileSync as
|
|
6104
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
|
|
6046
6105
|
import { extname as extname4, join as join24 } from "path";
|
|
6047
6106
|
function containsJsx(node) {
|
|
6048
6107
|
return ts.isJsxElement(node) || ts.isJsxFragment(node) || ts.isJsxSelfClosingElement(node) || !!ts.forEachChild(node, containsJsx);
|
|
@@ -6214,7 +6273,7 @@ function inProductModule(file) {
|
|
|
6214
6273
|
if (!folder) return false;
|
|
6215
6274
|
const queryFile = join24(repoRoot, `${folder}query.ts`);
|
|
6216
6275
|
if (!productModuleFolders.has(queryFile)) {
|
|
6217
|
-
const source = existsSync19(queryFile) ?
|
|
6276
|
+
const source = existsSync19(queryFile) ? readFileSync14(queryFile, "utf8") : "";
|
|
6218
6277
|
productModuleFolders.set(
|
|
6219
6278
|
queryFile,
|
|
6220
6279
|
markCalls(ts.createSourceFile(queryFile, source, ts.ScriptTarget.Latest)).registersRangeFetch
|
|
@@ -6238,7 +6297,7 @@ function checkProductCacheWrite(file, sourceFile, report) {
|
|
|
6238
6297
|
function frontendStores(files2) {
|
|
6239
6298
|
const stores = /* @__PURE__ */ new Set();
|
|
6240
6299
|
for (const file of files2.filter(isFrontendSource)) {
|
|
6241
|
-
const source =
|
|
6300
|
+
const source = readFileSync14(join24(repoRoot, file), "utf8");
|
|
6242
6301
|
if (source.includes("zustand")) collectStores(parseSource(file, source), stores);
|
|
6243
6302
|
}
|
|
6244
6303
|
return stores;
|
|
@@ -6324,7 +6383,7 @@ function stampFindings(file, source) {
|
|
|
6324
6383
|
}
|
|
6325
6384
|
|
|
6326
6385
|
// src/tools/style/tailwind-class-rules.ts
|
|
6327
|
-
import { readFileSync as
|
|
6386
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
6328
6387
|
import { createRequire as createRequire2 } from "module";
|
|
6329
6388
|
import { dirname as dirname9, join as join25, relative as relative2 } from "path";
|
|
6330
6389
|
|
|
@@ -6392,11 +6451,11 @@ function addMatches(target, text2, pattern) {
|
|
|
6392
6451
|
function usedClassNames(files2) {
|
|
6393
6452
|
const names = /* @__PURE__ */ new Set();
|
|
6394
6453
|
for (const file of files2.filter(isFrontendStylesheet)) {
|
|
6395
|
-
const css =
|
|
6454
|
+
const css = readFileSync15(join25(repoRoot, file), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
6396
6455
|
addMatches(names, css, /(?<!\\)\.(-?[_a-zA-Z][\w-]*)/g);
|
|
6397
6456
|
}
|
|
6398
6457
|
for (const file of files2.filter(isFrontendScript)) {
|
|
6399
|
-
const source =
|
|
6458
|
+
const source = readFileSync15(join25(repoRoot, file), "utf8");
|
|
6400
6459
|
for (const [chunk] of source.matchAll(/(?<![\w$)\]?.])\[[^\s'"`]*/g))
|
|
6401
6460
|
addMatches(names, chunk, /(?<![a-zA-Z0-9$.)\]?!])\.(-?[_a-zA-Z][\w-]*)/g);
|
|
6402
6461
|
for (const [, , text2] of source.matchAll(/(['"`])((?:\\.|(?!\1)[^\\\n])*)\1/g)) {
|
|
@@ -6412,14 +6471,14 @@ function usedClassNames(files2) {
|
|
|
6412
6471
|
return names;
|
|
6413
6472
|
}
|
|
6414
6473
|
async function tailwindContext(files2, appMarkerClasses = {}) {
|
|
6415
|
-
const entry = files2.filter(isFrontendStylesheet).find((file) => tailwindImport.test(
|
|
6474
|
+
const entry = files2.filter(isFrontendStylesheet).find((file) => tailwindImport.test(readFileSync15(join25(repoRoot, file), "utf8")));
|
|
6416
6475
|
if (!entry) return void 0;
|
|
6417
6476
|
const path = join25(repoRoot, entry);
|
|
6418
6477
|
const knownClasses = usedClassNames(files2);
|
|
6419
6478
|
for (const name of Object.keys({ ...markerClasses, ...appMarkerClasses })) knownClasses.add(name);
|
|
6420
6479
|
const context = { entry, knownClasses, compiles: /* @__PURE__ */ new Map() };
|
|
6421
6480
|
try {
|
|
6422
|
-
context.designSystem = await (await loadTailwindNode()).__unstable__loadDesignSystem(
|
|
6481
|
+
context.designSystem = await (await loadTailwindNode()).__unstable__loadDesignSystem(readFileSync15(path, "utf8"), {
|
|
6423
6482
|
base: dirname9(path)
|
|
6424
6483
|
});
|
|
6425
6484
|
} catch (error) {
|
|
@@ -6848,7 +6907,7 @@ async function runStyle(root, { paths }) {
|
|
|
6848
6907
|
const files2 = repoFiles();
|
|
6849
6908
|
const stores = frontendStores(files2);
|
|
6850
6909
|
const tailwind = await tailwindContext(files2, allowlist.markerClasses);
|
|
6851
|
-
const read = (file) =>
|
|
6910
|
+
const read = (file) => readFileSync16(join27(repoRoot, file), "utf8");
|
|
6852
6911
|
const requested = files2.filter((file) => isRequested(file, paths));
|
|
6853
6912
|
const findings2 = paths.filter((path) => !files2.some((file) => isRequested(file, [path]))).map((path) => ({ file: path, rule: "path-argument", message: "no file matches this path" }));
|
|
6854
6913
|
findings2.push(...alignmentFindings(requested, read));
|
|
@@ -6856,7 +6915,7 @@ async function runStyle(root, { paths }) {
|
|
|
6856
6915
|
findings2.push(...owedAlignmentFindings(await branchChanges(), read("cella/cella.config.ts")));
|
|
6857
6916
|
}
|
|
6858
6917
|
for (const file of requested) {
|
|
6859
|
-
const content =
|
|
6918
|
+
const content = readFileSync16(join27(repoRoot, file));
|
|
6860
6919
|
if (content.includes(0)) continue;
|
|
6861
6920
|
const source = content.toString("utf8");
|
|
6862
6921
|
const prose = allowlist.proseExclude?.some((prefix) => file.startsWith(prefix)) ? [] : [...docFindings(file, source), ...commentFindings(file, source)];
|
|
@@ -7573,8 +7632,9 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
|
|
|
7573
7632
|
const forkModifiedOld = forkOldHash !== baseOldHash;
|
|
7574
7633
|
const oldPathPinned = predicates.isPinned(oldPath);
|
|
7575
7634
|
let upstreamChanged2;
|
|
7576
|
-
if (fileIsPinned || oldPathPinned || predicates.isIgnored(oldPath)) {
|
|
7635
|
+
if (fileIsPinned || fileIsIgnored || oldPathPinned || predicates.isIgnored(oldPath)) {
|
|
7577
7636
|
status = "pinned";
|
|
7637
|
+
if (oldPathInFork) renamedFrom = oldPath;
|
|
7578
7638
|
if (forkModifiedOld && upstreamHash !== baseOldHash) {
|
|
7579
7639
|
upstreamChanged2 = true;
|
|
7580
7640
|
renamedProtected.add(filePath);
|
|
@@ -7730,9 +7790,11 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
|
|
|
7730
7790
|
);
|
|
7731
7791
|
const batchRestorePaths = [];
|
|
7732
7792
|
const batchRemovePaths = [];
|
|
7793
|
+
const movedProtected = [];
|
|
7733
7794
|
for (const file of analyzedFiles) {
|
|
7734
7795
|
if (file.status === "identical" || file.status === "ahead") continue;
|
|
7735
|
-
if (file.
|
|
7796
|
+
if (file.status === "pinned" && file.renamedFrom) movedProtected.push({ from: file.renamedFrom, to: file.path });
|
|
7797
|
+
else if (file.isIgnored || file.isPinned) {
|
|
7736
7798
|
(file.existsInFork ? batchRestorePaths : batchRemovePaths).push(file.path);
|
|
7737
7799
|
}
|
|
7738
7800
|
}
|
|
@@ -7749,6 +7811,10 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
|
|
|
7749
7811
|
await removeFileFromWorktree(forkPath, filePath);
|
|
7750
7812
|
}
|
|
7751
7813
|
}
|
|
7814
|
+
for (const { from, to } of movedProtected) {
|
|
7815
|
+
onProgress?.(`\u2192 ${from} \u2192 ${to}: keeping your copy (moved in upstream)`);
|
|
7816
|
+
await moveFromRef(forkPath, "HEAD", from, to);
|
|
7817
|
+
}
|
|
7752
7818
|
for (const file of analyzedFiles) {
|
|
7753
7819
|
const { path: filePath, status, existsInFork } = file;
|
|
7754
7820
|
if (status === "identical" || status === "ahead" || isProtectedFile(file)) continue;
|
|
@@ -8999,7 +9065,7 @@ function syncStep(result) {
|
|
|
8999
9065
|
}
|
|
9000
9066
|
|
|
9001
9067
|
// src/services/audit.ts
|
|
9002
|
-
import { readFileSync as
|
|
9068
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
9003
9069
|
import { basename as basename9, join as join35, relative as relative4 } from "path";
|
|
9004
9070
|
import { checkbox, Separator } from "@inquirer/prompts";
|
|
9005
9071
|
|
|
@@ -9114,7 +9180,7 @@ import { delimiter, dirname as dirname13, join as join33, sep as sep2 } from "pa
|
|
|
9114
9180
|
import { promisify as promisify3 } from "util";
|
|
9115
9181
|
|
|
9116
9182
|
// src/utils/runtimes.ts
|
|
9117
|
-
import { readFileSync as
|
|
9183
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
9118
9184
|
import { basename as basename8, dirname as dirname12, join as join32 } from "path";
|
|
9119
9185
|
var DAY = 24 * 60 * 60 * 1e3;
|
|
9120
9186
|
var FROZEN_AFTER = 180 * DAY;
|
|
@@ -9157,7 +9223,7 @@ function parseDockerfile(file, content) {
|
|
|
9157
9223
|
async function readRuntimePins(forkPath) {
|
|
9158
9224
|
let manifest = {};
|
|
9159
9225
|
try {
|
|
9160
|
-
manifest = JSON.parse(
|
|
9226
|
+
manifest = JSON.parse(readFileSync17(join32(forkPath, "package.json"), "utf8"));
|
|
9161
9227
|
} catch {
|
|
9162
9228
|
}
|
|
9163
9229
|
const node = manifest.engines?.node?.trim() ?? "";
|
|
@@ -9168,7 +9234,7 @@ async function readRuntimePins(forkPath) {
|
|
|
9168
9234
|
for (const file of tracked) {
|
|
9169
9235
|
let content;
|
|
9170
9236
|
try {
|
|
9171
|
-
content =
|
|
9237
|
+
content = readFileSync17(join32(forkPath, file), "utf8");
|
|
9172
9238
|
} catch {
|
|
9173
9239
|
continue;
|
|
9174
9240
|
}
|
|
@@ -9684,14 +9750,14 @@ function writeAuditSettings(content, next) {
|
|
|
9684
9750
|
|
|
9685
9751
|
// src/services/audit-security.ts
|
|
9686
9752
|
import { spawnSync as spawnSync11 } from "child_process";
|
|
9687
|
-
import { cpSync, existsSync as existsSync25, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readFileSync as
|
|
9753
|
+
import { cpSync, existsSync as existsSync25, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
9688
9754
|
import { tmpdir as tmpdir3 } from "os";
|
|
9689
9755
|
import { dirname as dirname14, join as join34, relative as relative3 } from "path";
|
|
9690
9756
|
import { confirm, select as select2 } from "@inquirer/prompts";
|
|
9691
9757
|
var WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
9692
9758
|
var LOCK_FILE = "pnpm-lock.yaml";
|
|
9693
9759
|
var RESOLUTION_FILES = ["package.json", LOCK_FILE, ".npmrc", ".pnpmfile.cjs"];
|
|
9694
|
-
var readOr = (path, fallback = "") => existsSync25(path) ?
|
|
9760
|
+
var readOr = (path, fallback = "") => existsSync25(path) ? readFileSync18(path, "utf8") : fallback;
|
|
9695
9761
|
var count2 = (n, noun, plural2 = `${noun}s`) => `${n} ${n === 1 ? noun : plural2}`;
|
|
9696
9762
|
var isBreaking = (item) => item.remedy.kind !== "none" && item.remedy.breaking;
|
|
9697
9763
|
var needsDecision = (item) => item.standing === "new" && (item.remedy.kind !== "override" || item.remedy.breaking || item.direct);
|
|
@@ -10004,7 +10070,7 @@ function createDependentPkgReader() {
|
|
|
10004
10070
|
let cached = cache.get(location);
|
|
10005
10071
|
if (cached === void 0) {
|
|
10006
10072
|
try {
|
|
10007
|
-
cached = JSON.parse(
|
|
10073
|
+
cached = JSON.parse(readFileSync19(join35(location, "package.json"), "utf8"));
|
|
10008
10074
|
} catch {
|
|
10009
10075
|
cached = null;
|
|
10010
10076
|
}
|
|
@@ -10176,7 +10242,7 @@ function printOutdatedResults(rows, forkPath) {
|
|
|
10176
10242
|
function printNotes(forkPath, unchecked) {
|
|
10177
10243
|
let held = [];
|
|
10178
10244
|
try {
|
|
10179
|
-
const updateConfig = readTopLevel(
|
|
10245
|
+
const updateConfig = readTopLevel(readFileSync19(join35(forkPath, "pnpm-workspace.yaml"), "utf8"), "updateConfig");
|
|
10180
10246
|
const ignored = updateConfig?.ignoreDependencies;
|
|
10181
10247
|
held = Array.isArray(ignored) ? ignored.map(String) : [];
|
|
10182
10248
|
} catch {
|
|
@@ -10300,7 +10366,7 @@ async function planEdits(forkPath, rows, selected, nodeImageSuffixes) {
|
|
|
10300
10366
|
const summary = /* @__PURE__ */ new Map();
|
|
10301
10367
|
const bumped = [];
|
|
10302
10368
|
const edit = (file, label, item, resolves, change) => {
|
|
10303
|
-
const entry = contents.get(file) ?? { content:
|
|
10369
|
+
const entry = contents.get(file) ?? { content: readFileSync19(join35(forkPath, file), "utf8"), resolves: false };
|
|
10304
10370
|
const next = change(entry.content);
|
|
10305
10371
|
if (next === entry.content) return;
|
|
10306
10372
|
contents.set(file, { content: next, resolves: entry.resolves || resolves });
|
|
@@ -10836,11 +10902,11 @@ function closePr(cwd, ref) {
|
|
|
10836
10902
|
}
|
|
10837
10903
|
|
|
10838
10904
|
// src/services/packages.ts
|
|
10839
|
-
import { existsSync as existsSync27, readFileSync as
|
|
10905
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "fs";
|
|
10840
10906
|
import { join as join38 } from "path";
|
|
10841
10907
|
|
|
10842
10908
|
// src/services/package-usage.ts
|
|
10843
|
-
import { existsSync as existsSync26, readFileSync as
|
|
10909
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, statSync as statSync4 } from "fs";
|
|
10844
10910
|
import { join as join37 } from "path";
|
|
10845
10911
|
var MAX_FILE_BYTES2 = 1024 * 1024;
|
|
10846
10912
|
function escapeRegExp2(value) {
|
|
@@ -10849,7 +10915,7 @@ function escapeRegExp2(value) {
|
|
|
10849
10915
|
function readSearchable(filePath) {
|
|
10850
10916
|
try {
|
|
10851
10917
|
if (statSync4(filePath).size > MAX_FILE_BYTES2) return "";
|
|
10852
|
-
const text2 =
|
|
10918
|
+
const text2 = readFileSync20(filePath, "utf8");
|
|
10853
10919
|
return text2.includes("\0") ? "" : text2;
|
|
10854
10920
|
} catch {
|
|
10855
10921
|
return "";
|
|
@@ -10869,7 +10935,7 @@ async function readScriptsAtRef(forkPath, ref, path) {
|
|
|
10869
10935
|
async function scriptSources(forkPath, upstreamRef, baseRef, path) {
|
|
10870
10936
|
let forkScripts;
|
|
10871
10937
|
try {
|
|
10872
|
-
forkScripts = JSON.parse(
|
|
10938
|
+
forkScripts = JSON.parse(readFileSync20(join37(forkPath, path), "utf8")).scripts ?? {};
|
|
10873
10939
|
} catch {
|
|
10874
10940
|
return [];
|
|
10875
10941
|
}
|
|
@@ -10883,7 +10949,7 @@ function binNames(forkPath, location, name) {
|
|
|
10883
10949
|
const manifest = join37(dir, "node_modules", name, "package.json");
|
|
10884
10950
|
if (!existsSync26(manifest)) continue;
|
|
10885
10951
|
try {
|
|
10886
|
-
const { bin } = JSON.parse(
|
|
10952
|
+
const { bin } = JSON.parse(readFileSync20(manifest, "utf8"));
|
|
10887
10953
|
if (typeof bin === "string") return [bareName];
|
|
10888
10954
|
return bin && typeof bin === "object" ? Object.keys(bin) : [];
|
|
10889
10955
|
} catch {
|
|
@@ -11064,7 +11130,7 @@ function setTopLevelKey(pkg, key, value) {
|
|
|
11064
11130
|
function readPackageJson(filePath) {
|
|
11065
11131
|
if (!existsSync27(filePath)) return null;
|
|
11066
11132
|
try {
|
|
11067
|
-
return JSON.parse(
|
|
11133
|
+
return JSON.parse(readFileSync21(filePath, "utf8"));
|
|
11068
11134
|
} catch (error) {
|
|
11069
11135
|
throw new Error(`cannot parse ${filePath}: ${errorMessage(error)}`);
|
|
11070
11136
|
}
|
|
@@ -11802,7 +11868,7 @@ import { readFile as readFile3 } from "fs/promises";
|
|
|
11802
11868
|
import { join as join40 } from "path";
|
|
11803
11869
|
|
|
11804
11870
|
// src/utils/coverage-utils.ts
|
|
11805
|
-
import { existsSync as existsSync28, readFileSync as
|
|
11871
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22, statSync as statSync5 } from "fs";
|
|
11806
11872
|
import { join as join39, relative as relative5, sep as sep3 } from "path";
|
|
11807
11873
|
var STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
11808
11874
|
function colorPct(pct) {
|
|
@@ -11828,7 +11894,7 @@ function printCoverageSummary(forkPath) {
|
|
|
11828
11894
|
if (existsSync28(file)) {
|
|
11829
11895
|
try {
|
|
11830
11896
|
mtimeMs = statSync5(file).mtimeMs;
|
|
11831
|
-
summary = JSON.parse(
|
|
11897
|
+
summary = JSON.parse(readFileSync22(file, "utf8"));
|
|
11832
11898
|
} catch {
|
|
11833
11899
|
problem2 = "could not read coverage-summary.json";
|
|
11834
11900
|
}
|