返回 CodeWhale
build-pet.mjs
根目录 / pet / scripts / build-pet.mjs
1 #!/usr/bin/env node
2 /** Local TypeScript build plus deterministic embedded-runtime packaging. */
3 import { readFile, writeFile, cp, mkdir } from 'node:fs/promises';
4 import { fileURLToPath } from 'node:url';
5 import { dirname } from 'node:path';
6 import { spawnSync } from 'node:child_process';
7 import ts from 'typescript';
8 const root = fileURLToPath(new URL('../', import.meta.url));
9 const result = spawnSync(process.execPath, [root + 'node_modules/typescript/bin/tsc', '-p', root + 'tsconfig.json'], { stdio: 'inherit' });
10 if (result.status !== 0) process.exit(result.status || 1);
11 for (const name of ['pet.html', 'shared.html', 'pet.css', 'whale-points.tsv']) await cp(root + 'public/' + name, root + 'dist/' + name);
12 const modules = new Map();
13 async function moduleFor(name) {
14 if (modules.has(name)) return;
15 if (!/^[a-z-]+$/.test(name)) throw new Error('Unexpected native module dependency.');
16 const source = await readFile(root + `src/core/${name}.ts`, 'utf8');
17 const js = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText;
18 modules.set(name, js);
19 for (const m of js.matchAll(/require\("\.\/([a-z-]+)\.js"\)/g)) await moduleFor(m[1]);
20 }
21 await moduleFor('pet-native');
22 // JavaScriptCore has no structuredClone host API. Pet inputs are validated JSON.
23 const bundle = `/* Generated from pet/src/core. Run npm --prefix pet run sync. */\n(function(global){\n'use strict';\nif(!global.structuredClone)global.structuredClone=value=>JSON.parse(JSON.stringify(value));\nconst factories={},cache={};\n`
24 + [...modules].map(([id, js]) => `factories[${JSON.stringify(id)}]=function(exports,require){\n${js}\n};\n`).join('')
25 + `function load(id){id=id.replace(/^\\.\\//,'').replace(/\\.js$/,'');if(cache[id])return cache[id];if(!factories[id])throw Error('Missing core module');const e=cache[id]={};factories[id](e,load);return e;}\nglobal.PetNative=load('pet-native').PetNative;\n})(globalThis);\n`;
26 await writeFile(root + 'dist/pet-native.js', bundle);
27 // Committed native copies are generated from src/core and public/. `--check`
28 // regenerates them in memory and fails on any byte difference, so a green
29 // `npm run check` cannot sit beside a stale embedded TUI/Apple/Android core.
30 const check = process.argv.includes('--check');
31 const generated = [];
32 if (check || process.argv.includes('--tui')) {
33 const destination = root + '../crates/tui/src/tui/pet_watch/';
34 generated.push([destination + 'pet-native.js', Buffer.from(bundle)],
35 [destination + 'shared.html', await readFile(root + 'public/shared.html')]);
36 }
37 if (process.argv.includes('--study')) {
38 const study = root + 'world/';
39 await mkdir(study, { recursive: true });
40 for (const name of ['pet.html', 'pet.css', 'whale-points.tsv', 'core', 'ui/pet.js', 'ui/storage.js']) {
41 await mkdir(study + name.split('/').slice(0, -1).join('/'), { recursive: true });
42 await cp(root + 'dist/' + name, study + name, { recursive: true });
43 }
44 // The original conformance viewer must not retain a stale browser core.
45 await mkdir(study + '../dist', { recursive: true });
46 await writeFile(study + '../dist/PetSim.js', "export * from '../world/core/pet-sim.js';\n");
47 const renderer = await readFile(study + '../web.ts', 'utf8');
48 await writeFile(study + '../dist/web.js', ts.transpileModule(renderer, {
49 compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ES2022 },
50 }).outputText.replace("'./PetSim.ts'", "'./PetSim.js'"));
51 }
52 if (check || process.argv.includes('--apple')) {
53 const { petDemoEvents } = await import('../dist/core/pet-demo.js');
54 const { compilePetTelemetry, encodePetJSONL } = await import('../dist/core/pet-telemetry.js');
55 generated.push([root + 'ios/Resources/pet-native.js', Buffer.from(bundle)],
56 [root + 'ios/Resources/demo.jsonl', Buffer.from(encodePetJSONL(compilePetTelemetry(petDemoEvents(), 80_000)))]);
57 }
58 if (check) {
59 const stale = [];
60 for (const [path, bytes] of generated) {
61 const current = await readFile(path).catch(error => { if (error.code === 'ENOENT') return undefined; throw error; });
62 if (!current || !bytes.equals(current)) stale.push(path.slice(root.length));
63 }
64 if (stale.length) {
65 console.error(`Generated native artifacts are stale: ${stale.join(', ')}. Run npm --prefix pet run sync and commit them.`);
66 process.exit(1);
67 }
68 } else {
69 for (const [path, bytes] of generated) { await mkdir(dirname(path), { recursive: true }); await writeFile(path, bytes); }
70 }
71 console.log(`Built pet viewer and native core (${modules.size} shared modules), entirely local${check ? '; generated native artifacts match' : ''}.`);
72
72 lines Plain Text