Coverage was over-reported twice, both times by measuring the wrong thing: once by scanning a plugin root instead of the served bundle set, once with a non-global regex that returned one hit per file instead of a character count. Both produced a confident "100%" while the interface was still Chinese, so the measurement is now part of the repo instead of being done by eye. - scripts/served.mjs resolves the boot HTML's combo URLs to the on-disk files the browser receives, searching lib/, client/, dist/ and the package root. - scripts/measure.mts counts Chinese on the runtime output, decoding \uXXXX and separating comment text from text that renders. - README documents the merge order, the comment dictionary, and both traps.
66 lines
2.4 KiB
JavaScript
66 lines
2.4 KiB
JavaScript
// Resolves the client bundles the browser actually receives.
|
|
//
|
|
// Reads a save of the boot HTML (the page dsh web serves at `/`) and writes the
|
|
// on-disk path of every bundle in its combo URLs. This exists because working
|
|
// from a plugin root instead of the served set silently under-measures: 55 of
|
|
// the 58 bundles live in the dsh install tree, not the profile tree, so a scan
|
|
// of the profile tree alone reports nearly everything translated while whole
|
|
// screens stay Chinese.
|
|
//
|
|
// Usage: node scripts/served.mjs <boot.html> [out.json]
|
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
|
|
const [, , htmlPath, outPath = "served.json"] = process.argv;
|
|
if (!htmlPath) {
|
|
console.error("usage: node scripts/served.mjs <boot.html> [out.json]");
|
|
process.exit(2);
|
|
}
|
|
|
|
const roots = (
|
|
process.env.C2E_BUNDLE_ROOTS ??
|
|
"/home/oiupoyt/.dsh/profiles/web/node_modules/:" +
|
|
"/home/oiupoyt/.npm-global/lib/node_modules/@deepseek-ai/dsh/node_modules/"
|
|
)
|
|
.split(/[:,]/)
|
|
.map((entry) => entry.trim())
|
|
.filter(Boolean)
|
|
.map((entry) => (entry.endsWith("/") ? entry : entry + "/"));
|
|
|
|
const html = readFileSync(htmlPath, "utf8");
|
|
const urls = [
|
|
...new Set((html.match(/plugins\/\?\?[^"'\\ ]+/g) ?? []).map((u) => u.replace(/&/g, "&"))),
|
|
];
|
|
|
|
const entries = new Set();
|
|
for (const url of urls) {
|
|
const list = url.slice(url.indexOf("??") + 2).split("&")[0];
|
|
for (const raw of list.split(",")) {
|
|
const entry = decodeURIComponent(raw.trim());
|
|
if (entry) entries.add(entry);
|
|
}
|
|
}
|
|
|
|
// A served URL is `<pkg>/client.js`; the built file may sit in lib/, client/,
|
|
// dist/ or beside package.json depending on the plugin's own build.
|
|
const SUFFIXES = ["", "lib/", "client/", "dist/"];
|
|
const served = [];
|
|
const missing = [];
|
|
for (const entry of entries) {
|
|
const tail = entry.slice(entry.lastIndexOf("/") + 1);
|
|
const head = entry.slice(0, entry.lastIndexOf("/") + 1);
|
|
let hit;
|
|
for (const root of roots) {
|
|
for (const dir of SUFFIXES) {
|
|
const candidate = root + head + dir + tail;
|
|
if (existsSync(candidate)) { hit = candidate; break; }
|
|
}
|
|
if (hit) break;
|
|
}
|
|
if (hit) served.push(hit);
|
|
else missing.push(entry);
|
|
}
|
|
|
|
writeFileSync(outPath, JSON.stringify(served, null, 1) + "\n");
|
|
console.log(`served: ${served.length} bundles resolved, ${missing.length} missing -> ${outPath}`);
|
|
for (const m of missing) console.log(` missing: ${m}`);
|
|
if (missing.length) process.exitCode = 1;
|