fix(pet): fail npm run check when committed native artifacts drift

P01-05 (Refs #6557). The committed native copies of the TypeScript core
(crates/tui/src/tui/pet_watch/pet-native.js, crates/tui/.../shared.html,
pet/ios/Resources/pet-native.js and demo.jsonl, which Android also
packages) were only regenerated by `npm run sync`. `npm run check` never
compared them, so the owned build stayed green beside a stale embedded
core. The CI step `git diff --exit-code` after sync (pet.yml:37) also
omits pet_watch/shared.html, which the owner serves via include_str!.

scripts/build-pet.mjs gains `--check`: it builds dist as before,
regenerates every tracked native output in memory and exits 1 naming each
file whose bytes differ (or which is missing). Without --check the same
list is written, so sync behaviour is unchanged. `npm run check` now runs
the build with --check, and CI already runs `npm --prefix pet run check`.

Validation (pet/):
- npm run check: typecheck ok; "generated native artifacts match";
  ℹ tests 71 / pass 71 / fail 0
- Negative control: appended a comment to public/shared.html ->
  --check exit 1 "stale: ../crates/tui/src/tui/pet_watch/shared.html";
  the previous path (plain build) exit 0 on the same drift. Appended a
  comment to src/core/pet-world.ts -> --check exit 1 naming both
  pet-native.js copies. Both files restored byte-identically (cmp), rerun
  exit 0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-09-30 01:04:00 -07:00
1 parent 998a1811a3
commit 1fceabfe98
3 files changed
+29 -12

No files matched your search

+2 -2
View File
@@ -88,8 +88,8 @@ The Rust particle implementation lives in the product's
`crates/tui/src/tui/ambient_life/pet_sim.rs`; this package's runner imports it.
Swift and Kotlin particle ports must match its conformance digests. Generated
native bundles are committed so the product Rust build needs no Node compiler.
Run `npm run sync` after changing core source; the generated-byte check in CI
rejects a stale bundle. Local Whalesong consumers use aliases to these same
Run `npm run sync` after changing core source; `npm run check` (run by CI)
rejects a stale bundle or `shared.html` copy. Local Whalesong consumers use aliases to these same
canonical files, not separately maintained source copies.
The original Whalesong importer, signal model, schema and browser storage code
+1 -1
View File
@@ -11,7 +11,7 @@
"build": "node scripts/build-pet.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "node --test tests/*.test.mjs",
"check": "npm run typecheck && npm run build && npm test",
"check": "npm run typecheck && node scripts/build-pet.mjs --check && npm test",
"start": "python3 -m http.server 4632 --bind 127.0.0.1 --directory dist",
"sync": "node scripts/build-pet.mjs --tui --apple --study"
},
+26 -9
View File
@@ -2,6 +2,7 @@
/** Local TypeScript build plus deterministic embedded-runtime packaging. */
import { readFile, writeFile, cp, mkdir } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { spawnSync } from 'node:child_process';
import ts from 'typescript';
const root = fileURLToPath(new URL('../', import.meta.url));
@@ -23,11 +24,15 @@ const bundle = `/* Generated from pet/src/core. Run npm --prefix pet run sync. *
+ [...modules].map(([id, js]) => `factories[${JSON.stringify(id)}]=function(exports,require){\n${js}\n};\n`).join('')
+ `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`;
await writeFile(root + 'dist/pet-native.js', bundle);
if (process.argv.includes('--tui')) {
// Committed native copies are generated from src/core and public/. `--check`
// regenerates them in memory and fails on any byte difference, so a green
// `npm run check` cannot sit beside a stale embedded TUI/Apple/Android core.
const check = process.argv.includes('--check');
const generated = [];
if (check || process.argv.includes('--tui')) {
const destination = root + '../crates/tui/src/tui/pet_watch/';
await mkdir(destination, { recursive: true });
await writeFile(destination + 'pet-native.js', bundle);
await cp(root + 'public/shared.html', destination + 'shared.html');
generated.push([destination + 'pet-native.js', Buffer.from(bundle)],
[destination + 'shared.html', await readFile(root + 'public/shared.html')]);
}
if (process.argv.includes('--study')) {
const study = root + 'world/';
@@ -44,11 +49,23 @@ if (process.argv.includes('--study')) {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ES2022 },
}).outputText.replace("'./PetSim.ts'", "'./PetSim.js'"));
}
if (process.argv.includes('--apple')) {
if (check || process.argv.includes('--apple')) {
const { petDemoEvents } = await import('../dist/core/pet-demo.js');
const { compilePetTelemetry, encodePetJSONL } = await import('../dist/core/pet-telemetry.js');
await mkdir(root + 'ios/Resources', { recursive: true });
await writeFile(root + 'ios/Resources/pet-native.js', bundle);
await writeFile(root + 'ios/Resources/demo.jsonl', encodePetJSONL(compilePetTelemetry(petDemoEvents(), 80_000)));
generated.push([root + 'ios/Resources/pet-native.js', Buffer.from(bundle)],
[root + 'ios/Resources/demo.jsonl', Buffer.from(encodePetJSONL(compilePetTelemetry(petDemoEvents(), 80_000)))]);
}
console.log(`Built pet viewer and native core (${modules.size} shared modules), entirely local.`);
if (check) {
const stale = [];
for (const [path, bytes] of generated) {
const current = await readFile(path).catch(error => { if (error.code === 'ENOENT') return undefined; throw error; });
if (!current || !bytes.equals(current)) stale.push(path.slice(root.length));
}
if (stale.length) {
console.error(`Generated native artifacts are stale: ${stale.join(', ')}. Run npm --prefix pet run sync and commit them.`);
process.exit(1);
}
} else {
for (const [path, bytes] of generated) { await mkdir(dirname(path), { recursive: true }); await writeFile(path, bytes); }
}
console.log(`Built pet viewer and native core (${modules.size} shared modules), entirely local${check ? '; generated native artifacts match' : ''}.`);