Merge published comic changes and adapt navigation verification
Verify and deploy Tabularium / verify-and-deploy (pull_request) Failing after 5m28s
Verify and deploy Tabularium / verify-and-deploy (pull_request) Failing after 5m28s
This commit is contained in:
@@ -141,6 +141,8 @@ async function main() {
|
||||
"/",
|
||||
"/docs/00-endor-concepts-draft",
|
||||
"/figures/endor-architecture.svg",
|
||||
"/figures/endor-review-gate.webp",
|
||||
"/figures/endor-review-desk.webp",
|
||||
"/docs/pretext/01-chapter",
|
||||
"/materials/pretext/verify.cjs",
|
||||
"/materials/pretext/results.chrome156.json",
|
||||
@@ -190,6 +192,7 @@ async function main() {
|
||||
assert.equal(new URL(legacy.headers.get("location")!, base).pathname, `/docs/${doc.slug}`);
|
||||
}
|
||||
let home = "";
|
||||
let purpose = "";
|
||||
for (const route of routes) {
|
||||
const response = await request(route, { headers });
|
||||
const alias = (searchIndex as { sourceSlug: string }[]).some((doc) => route === `/docs/${doc.sourceSlug}`);
|
||||
@@ -197,6 +200,7 @@ async function main() {
|
||||
if (alias) continue;
|
||||
const html = await response.text();
|
||||
if (route === "/") home = html;
|
||||
if (route === "/docs/endor/endor-purpose-draft") purpose = html;
|
||||
if (route.startsWith("/docs/")) {
|
||||
assert.ok(html.includes('aria-label="文档选择"'), route);
|
||||
assert.ok(html.includes('<optgroup label="语言与运行时">'), route);
|
||||
@@ -208,6 +212,32 @@ async function main() {
|
||||
}
|
||||
assert.ok(home.includes("资料库"));
|
||||
assert.ok(home.includes("/docs/source-studies/pretext/overview"));
|
||||
// Inline preview and zoom dialog must both use the compressed browser asset.
|
||||
assert.equal(
|
||||
[...purpose.matchAll(/<img\b[^>]*\bsrc="\/figures\/endor-review-gate\.webp"/g)].length,
|
||||
2,
|
||||
"The comic preview and lightbox must use WebP",
|
||||
);
|
||||
assert.ok(purpose.includes('aria-label="上一张图片"'));
|
||||
assert.ok(purpose.includes('aria-label="下一张图片"'));
|
||||
assert.ok(purpose.includes('aria-label="显示第 1 张图片" aria-current="true"'));
|
||||
assert.ok(purpose.includes('aria-label="显示第 2 张图片"'));
|
||||
assert.equal(
|
||||
[...purpose.matchAll(/<img\b[^>]*\bsrc="\/figures\/endor-review-desk\.webp"/g)].length,
|
||||
0,
|
||||
"The second slide should not download until selected",
|
||||
);
|
||||
for (const name of ["endor-review-gate", "endor-review-desk"]) {
|
||||
assert.ok(!purpose.includes(`/figures/${name}.png`));
|
||||
const comic = await request(`/figures/${name}.webp`, { headers });
|
||||
assert.equal(comic.status, 200);
|
||||
assert.match(comic.headers.get("content-type") || "", /image\/webp/);
|
||||
assert.deepEqual(
|
||||
Buffer.from(await comic.arrayBuffer()),
|
||||
readFileSync(join(__dirname, `public/figures/${name}.webp`)),
|
||||
);
|
||||
assert.equal((await request(`/figures/${name}.png`, { headers })).status, 404);
|
||||
}
|
||||
const assets = new Set(
|
||||
[...home.matchAll(/(?:src|href)="(\/_next\/static\/[^" ]+)"/g)].map((m) =>
|
||||
m[1].replaceAll("&", "&"),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import sharp from "sharp";
|
||||
|
||||
const root = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const sources = path.join(root, "tools/figures/assets");
|
||||
const output = path.join(root, "apps/docs/public/figures");
|
||||
const check = process.argv.includes("--check");
|
||||
const policy = JSON.parse(await readFile(path.join(root, "content/image-policy.json"), "utf8"));
|
||||
|
||||
async function files(directory) {
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
return (await Promise.all(entries.map((entry) => {
|
||||
const name = path.join(directory, entry.name);
|
||||
return entry.isDirectory() ? files(name) : [name];
|
||||
}))).flat().sort();
|
||||
}
|
||||
|
||||
// Originals stay outside public; both inline and lightbox images use the WebP.
|
||||
for (const name of await files(path.join(root, "apps/docs/public"))) {
|
||||
if (/\.(?:png|jpe?g|tiff?|bmp)$/i.test(name)) {
|
||||
throw new Error(`Original raster exposed in public: ${path.relative(root, name)}. Move it to tools/figures/assets and run images.`);
|
||||
}
|
||||
}
|
||||
for (const name of await files(path.join(root, "content/docs"))) {
|
||||
if (!name.endsWith(".md")) continue;
|
||||
const content = await readFile(name, "utf8");
|
||||
const images = [
|
||||
...content.matchAll(/!\[[^\]]*\]\(<?([^\s)>]+)/g),
|
||||
...content.matchAll(/<img\b[^>]*\bsrc=["']([^"']+)/gi),
|
||||
];
|
||||
for (const [, source] of images) {
|
||||
if (/\.(?:png|jpe?g|tiff?|bmp)(?:[?#]|$)/i.test(source)) {
|
||||
throw new Error(`${path.relative(root, name)} embeds an original raster: ${source}. Use the compressed WebP.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await mkdir(output, { recursive: true });
|
||||
for (const source of await files(sources)) {
|
||||
if (!/\.(?:png|jpe?g|tiff?|bmp)$/i.test(source)) continue;
|
||||
const relative = path.relative(sources, source).replace(/\.[^.]+$/, ".webp");
|
||||
const destination = path.join(output, relative);
|
||||
const original = await readFile(source);
|
||||
const encoded = await sharp(original)
|
||||
.rotate()
|
||||
.resize({ width: policy.maxWidth, withoutEnlargement: true })
|
||||
.webp({ quality: policy.quality, effort: 6 })
|
||||
.toBuffer();
|
||||
if (encoded.length > policy.maxBytes) {
|
||||
throw new Error(`${relative}: ${encoded.length} bytes exceeds the ${policy.maxBytes}-byte image budget.`);
|
||||
}
|
||||
if (check) {
|
||||
const generated = await readFile(destination);
|
||||
if (!encoded.equals(generated)) throw new Error(`${relative} is stale. Regenerate browser images on the confirmed remote runner.`);
|
||||
} else {
|
||||
await mkdir(path.dirname(destination), { recursive: true });
|
||||
await writeFile(destination, encoded);
|
||||
}
|
||||
console.log(`${check ? "Verified" : "Generated"} ${relative}: ${original.length} → ${encoded.length} bytes (${(100 * (1 - encoded.length / original.length)).toFixed(1)}% smaller)`);
|
||||
}
|
||||
Reference in New Issue
Block a user