| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546 |
- import fs from 'node:fs/promises';
- import path from 'node:path';
- import crypto from 'node:crypto';
- const release = 'ksf-board-v3-20261112-r2';
- const origin = 'https://kangshifu.fmode.cn/';
- const output = 'work/board-v3-release-20261112';
- const sha = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
- const normal = bytes => bytes.includes(0) ? bytes : Buffer.from(bytes.toString('utf8').replaceAll('\r\n', '\n'));
- const graph = JSON.parse((await fs.readFile('work/board-deploy-20261112-modules-local.json', 'utf8')).replace(/^\uFEFF/, ''));
- if (graph.failures.length) throw Error('LOCAL_MODULE_GRAPH_FAILED');
- const paths = [...new Set([...graph.modules.map(url => new URL(url).pathname.slice(1)),
- 'src/assets/inter-latin.woff2', 'src/data/development-references.json'])];
- const allowed = /^(?:src\/(?!dev\/)[a-zA-Z0-9_./-]+\.(?:js|woff2|json)|lib\/vendor\/[a-zA-Z0-9_@./-]+\.js|skills\/ksf-workshop-runtime\/scripts\/(?:report-contract|course-deliverables)\.mjs)$/;
- if (paths.some(p => !allowed.test(p) || p.split('/').includes('..'))) throw Error('UNEXPECTED_BROWSER_DEPENDENCY');
- async function read(relative) {
- const url = new URL(relative, origin); url.searchParams.set('boardReleaseBaseline', release);
- const response = await fetch(url, { redirect: 'error', headers: { 'cache-control': 'no-cache' }, signal: AbortSignal.timeout(45000) });
- if (![200, 404].includes(response.status)) throw Error('BASELINE_HTTP:' + relative + ':' + response.status);
- return { status: response.status, type: response.headers.get('content-type') || '', bytes: Buffer.from(await response.arrayBuffer()) };
- }
- const home = await read('index.html');
- if (home.status !== 200 || sha(home.bytes) !== 'dfeb6a6d0b63e8e1190f9308bf6619a5ca523a5a2f91af5f7ca5ad5fc9642db3') throw Error('HOMEPAGE_BASELINE_CHANGED');
- await fs.mkdir(output + '/production-before', { recursive: true });
- await fs.writeFile(output + '/production-index.html', home.bytes);
- const files = []; let next = 0;
- await Promise.all(Array.from({ length: 4 }, async () => { while (next < paths.length) {
- const relative = paths[next++], bytes = normal(await fs.readFile(relative)), prior = await read(relative);
- const absent = prior.status === 404 || prior.type.includes('html') && prior.bytes.equals(home.bytes);
- if (!absent && /\.m?js$/.test(relative) && !/^(?:application|text)\/javascript\b/.test(prior.type)) throw Error('BASELINE_MIME:' + relative);
- if (!absent) {
- const target = output + '/production-before/' + relative;
- await fs.mkdir(path.dirname(target), { recursive: true }); await fs.writeFile(target, prior.bytes);
- }
- files.push({ path: relative, sha256: sha(bytes), priorNormalizedSha256: absent ? [] : [sha(normal(prior.bytes))],
- priorSha256: absent ? null : sha(prior.bytes), allowMissing: absent, changed: absent || sha(normal(prior.bytes)) !== sha(bytes) });
- } }));
- const rank = p => p === 'src/layout/kf-app.js' ? 4 : p.startsWith('src/layout/') ? 3 : p.startsWith('src/pages/') ? 2 : 1;
- files.sort((a, b) => rank(a.path) - rank(b.path) || a.path.localeCompare(b.path));
- const manifest = { version: 'ksf.board-local.publish.v1', release, origin, baselineReadAt: new Date().toISOString(), homepageSha256: sha(home.bytes), files };
- await fs.writeFile('deploy/board-v3-20261112.json', JSON.stringify(manifest, null, 2) + '\n');
- await fs.writeFile(output + '/baseline-summary.json', JSON.stringify({ release, productionWrites: 0, files: files.length,
- changed: files.filter(file => file.changed).map(file => file.path), added: files.filter(file => file.allowMissing).map(file => file.path),
- homepageSha256: manifest.homepageSha256 }, null, 2) + '\n');
- console.log(JSON.stringify({ release, files: files.length, changed: files.filter(file => file.changed).map(file => file.path),
- added: files.filter(file => file.allowMissing).map(file => file.path), productionWrites: 0 }, null, 2));
|