This commit is contained in:
98
scripts/check-sidecar-resources.mjs
Normal file
98
scripts/check-sidecar-resources.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* P4/D12 preflight — fail a Tauri build LOUDLY when the arch-parameterized
|
||||
* sidecar resources are missing.
|
||||
*
|
||||
* The tauri.conf.json beforeBuildCommand regenerates ONLY the sidecar JS
|
||||
* bundle (build-sidecar.mjs — arch-independent, safe to run arch-blind). The
|
||||
* Node runtime and native modules are arch-PARAMETERIZED (TARGET_ARCH) and
|
||||
* must be staged by the explicit npm scripts / CI steps that set it — the
|
||||
* hook must never regenerate them (an arch-blind re-run on a cross-arch
|
||||
* matrix leg clobbers the staged artifacts; P4 review HIGH). But the bundle
|
||||
* resources glob (`resources/*`) silently tolerates ABSENT files — a raw
|
||||
* `npx tauri build` on a fresh clone would package a binary with no Node
|
||||
* runtime at all. This check turns that silent miss into a hard stop.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const resourcesDir = path.join(root, 'app', 'src-tauri', 'resources');
|
||||
|
||||
const missing = [];
|
||||
|
||||
const nodeBinary = process.platform === 'win32' ? 'node.exe' : 'node';
|
||||
const nodePath = path.join(resourcesDir, nodeBinary);
|
||||
if (!fs.existsSync(nodePath)) {
|
||||
missing.push(`resources/${nodeBinary} (run: node scripts/bundle-node.mjs)`);
|
||||
} else {
|
||||
try {
|
||||
const bundledAbi = execFileSync(nodePath, ['-p', 'process.versions.modules'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
const currentAbi = process.versions.modules;
|
||||
if (bundledAbi !== currentAbi) {
|
||||
missing.push(
|
||||
`resources/${nodeBinary} ABI ${bundledAbi} does not match current Node ABI ${currentAbi} ` +
|
||||
'(run: node scripts/bundle-node.mjs with the same Node used for npm install/stage-sidecar-deps)',
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
missing.push(`resources/${nodeBinary} is not executable (${err.message})`);
|
||||
}
|
||||
}
|
||||
|
||||
const nativeDir = path.join(resourcesDir, 'native');
|
||||
const nativeEntries = fs.existsSync(nativeDir)
|
||||
? fs.readdirSync(nativeDir).filter((e) => e !== '.gitkeep' && e !== 'onnxruntime')
|
||||
: [];
|
||||
if (nativeEntries.length === 0) {
|
||||
missing.push('resources/native/* (run: node scripts/bundle-native-deps.mjs)');
|
||||
}
|
||||
|
||||
// Staged production node_modules for the esbuild-externalized packages
|
||||
// (better-sqlite3, @fastify/static, mammoth, …). The bundled service.js
|
||||
// `require()`s these by bare name and resolves them via NODE_PATH=resources/
|
||||
// node_modules (service.rs). Like the native deps above, staging is done by the
|
||||
// npm scripts / CI (stage-sidecar-deps.mjs), NOT the arch-blind beforeBuildCommand
|
||||
// hook — so a raw `npx tauri build` that skips those would package a sidecar that
|
||||
// dies with MODULE_NOT_FOUND on first boot. Probe a canonical external.
|
||||
const stagedDepsDir = path.join(resourcesDir, 'node_modules');
|
||||
if (!fs.existsSync(path.join(stagedDepsDir, 'better-sqlite3', 'package.json'))) {
|
||||
missing.push('resources/node_modules/* (run: node scripts/stage-sidecar-deps.mjs)');
|
||||
}
|
||||
|
||||
// External agents and hook management run directly from this staged payload;
|
||||
// none of these packages are available from npm in a packaged installation.
|
||||
const hookRuntimeEntries = [
|
||||
'@waggle/hive-mind-cli/dist/index.js',
|
||||
'@waggle/hive-mind-hooks-claude-code/dist/bin/claude-code-hooks-cli.js',
|
||||
'@waggle/hive-mind-hooks-claude-desktop/dist/bin/claude-desktop-hooks.js',
|
||||
'@waggle/hive-mind-hooks-codex/dist/bin/codex-hooks.js',
|
||||
'@waggle/hive-mind-hooks-codex-desktop/dist/bin/codex-desktop-hooks.js',
|
||||
'@waggle/hive-mind-hooks-cursor/dist/bin/cursor-hooks.js',
|
||||
'@waggle/hive-mind-hooks-hermes/dist/bin/hermes-hooks.js',
|
||||
'@waggle/hive-mind-hooks-openclaw/dist/bin/openclaw-hooks.js',
|
||||
];
|
||||
for (const entry of hookRuntimeEntries) {
|
||||
if (!fs.existsSync(path.join(stagedDepsDir, ...entry.split('/')))) {
|
||||
missing.push(`resources/node_modules/${entry} (run: node scripts/stage-sidecar-deps.mjs)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error('[check-sidecar-resources] FATAL — sidecar runtime artifacts missing:');
|
||||
for (const m of missing) console.error(` - ${m}`);
|
||||
console.error(
|
||||
'[check-sidecar-resources] Stage them with the bundle scripts (set TARGET_ARCH for\n' +
|
||||
'cross-arch builds) or use the npm tauri:build* scripts / CI, which run them for you.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('[check-sidecar-resources] OK — Node runtime + native deps + staged node_modules present');
|
||||
Reference in New Issue
Block a user