99 lines
4.4 KiB
JavaScript
99 lines
4.4 KiB
JavaScript
#!/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');
|