Files
Orkas/scripts/codesign-adhoc.cjs
2026-08-11 16:21:45 +08:00

742 lines
28 KiB
JavaScript

/**
* electron-builder afterPack hook.
*
* Native dependency pruning and validation run for macOS and Windows. The
* optional macOS ad-hoc codesign fallback is only a local distribution helper:
* without a Developer ID identity, unsigned .app bundles copied from a DMG can
* be reported as damaged by Gatekeeper. Ad-hoc signing gives the bundle a
* complete signature structure so users can still right-click and open it.
*
* Set ORKAS_SKIP_ADHOC_CODESIGN=1 for deliberately unsigned packages.
*/
'use strict';
const { execSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const { verifyNativePackagePayload } = require('../bin/native-package-gate.cjs');
const {
verifyRuntimeRoot,
verifyWindowsVcAppLocalFiles,
verifyWindowsVcImportClosure,
} = require('../bin/runtime-gate.cjs');
const {
verifyEmbeddingModelRoot,
verifyMacLocalizedMetadataRoot,
} = require('../bin/packaged-resource-gate.cjs');
const {
verifyPackagedConnectorRuntime,
verifyPackagedEntrypointPayload,
} = require('../bin/packaged-entrypoint-gate.cjs');
const { verifyBuiltinRoot } = require('../bin/builtin-resource-gate.cjs');
const { verifyPackagedDependencyGraph } = require('../bin/packaged-dependency-gate.cjs');
const ARCH_NAMES = {
0: 'ia32',
1: 'x64',
2: 'armv7l',
3: 'arm64',
4: 'universal',
};
function removeIfExists(target) {
if (fs.existsSync(target)) fs.rmSync(target, { recursive: true, force: true });
}
function requiredFile(label, file) {
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
throw new Error(`[native-deps-gate] missing ${label}: ${file}`);
}
}
function listDirs(dir) {
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name);
}
function pruneOnnxRuntimePackage(pkgDir, targetPlatform, targetArch) {
const binDir = path.join(pkgDir, 'bin');
if (!fs.existsSync(binDir)) return;
const keepArch = targetArch === 'arm64' || targetArch === 'x64' ? targetArch : null;
for (const napiDirName of listDirs(binDir)) {
const napiDir = path.join(binDir, napiDirName);
for (const platformDirName of listDirs(napiDir)) {
if (platformDirName !== targetPlatform) {
removeIfExists(path.join(napiDir, platformDirName));
}
}
if (!keepArch) continue;
const platformDir = path.join(napiDir, targetPlatform);
for (const archDirName of listDirs(platformDir)) {
if (archDirName !== keepArch) {
removeIfExists(path.join(platformDir, archDirName));
}
}
// FastEmbed explicitly uses the CPU execution provider. DirectML is an
// unused 18 MB companion and must not silently become part of the payload.
if (targetPlatform === 'win32' && keepArch) {
removeIfExists(path.join(platformDir, keepArch, 'DirectML.dll'));
}
}
}
function targetEsbuildPackage(targetPlatform, targetArch) {
if (targetPlatform === 'darwin' && (targetArch === 'arm64' || targetArch === 'x64')) {
return `darwin-${targetArch}`;
}
if (targetPlatform === 'win32' && targetArch === 'x64') return 'win32-x64';
return null;
}
function pruneEsbuildPackage(nodeModules, targetPlatform, targetArch) {
const esbuildDir = path.join(nodeModules, '@esbuild');
if (!fs.existsSync(esbuildDir)) return;
const keepPackage = targetEsbuildPackage(targetPlatform, targetArch);
for (const dirName of listDirs(esbuildDir)) {
if (keepPackage && dirName !== keepPackage) {
removeIfExists(path.join(esbuildDir, dirName));
}
}
if (!keepPackage) return;
const bin = targetPlatform === 'win32'
? path.join(esbuildDir, keepPackage, 'esbuild.exe')
: path.join(esbuildDir, keepPackage, 'bin', 'esbuild');
if (!fs.existsSync(bin)) {
throw new Error(`[codesign-adhoc] missing ${targetPlatform} ${targetArch} esbuild runtime binary: ${bin}`);
}
// esbuild/bin/esbuild is what direct esbuild imports launch. Cross-packaging
// otherwise leaves the host binary here even when @esbuild has been pruned
// to the correct target package.
const launcher = path.join(nodeModules, 'esbuild', 'bin', 'esbuild');
fs.mkdirSync(path.dirname(launcher), { recursive: true });
fs.copyFileSync(bin, launcher);
if (targetPlatform !== 'win32') fs.chmodSync(launcher, 0o755);
}
function targetSqliteVecPackage(targetPlatform, targetArch) {
if (targetPlatform === 'darwin' && (targetArch === 'arm64' || targetArch === 'x64')) {
return `sqlite-vec-darwin-${targetArch}`;
}
if (targetPlatform === 'win32' && targetArch === 'x64') return 'sqlite-vec-windows-x64';
return null;
}
function pruneSqliteVecPackages(nodeModules, targetPlatform, targetArch) {
const keepPackage = targetSqliteVecPackage(targetPlatform, targetArch);
for (const parentDir of sqliteVecPackageParentDirs(nodeModules)) {
for (const dirName of listDirs(parentDir)) {
if (/^sqlite-vec-(darwin|linux|windows)-/i.test(dirName) && dirName !== keepPackage) {
removeIfExists(path.join(parentDir, dirName));
}
}
}
}
function targetCanvasPackage(targetPlatform, targetArch) {
if (targetPlatform === 'darwin' && (targetArch === 'arm64' || targetArch === 'x64')) {
return `canvas-darwin-${targetArch}`;
}
if (targetPlatform === 'win32' && targetArch === 'x64') return 'canvas-win32-x64-msvc';
if (targetPlatform === 'win32' && targetArch === 'arm64') return 'canvas-win32-arm64-msvc';
return null;
}
function pruneCanvasPackages(nodeModules, targetPlatform, targetArch) {
const keepPackage = targetCanvasPackage(targetPlatform, targetArch);
for (const parentDir of canvasPackageParentDirs(nodeModules)) {
for (const dirName of listDirs(parentDir)) {
if (/^canvas-/i.test(dirName) && dirName !== keepPackage) {
removeIfExists(path.join(parentDir, dirName));
}
}
}
}
function targetSharpPackages(targetPlatform, targetArch) {
if (targetPlatform === 'darwin' && (targetArch === 'arm64' || targetArch === 'x64')) {
return {
binding: `sharp-darwin-${targetArch}`,
libvips: `sharp-libvips-darwin-${targetArch}`,
};
}
if (targetPlatform === 'win32' && targetArch === 'x64') {
return { binding: 'sharp-win32-x64', libvips: null };
}
return { binding: null, libvips: null };
}
function pruneSharpPackages(nodeModules, targetPlatform, targetArch) {
const target = targetSharpPackages(targetPlatform, targetArch);
const imgDir = path.join(nodeModules, '@img');
for (const dirName of listDirs(imgDir)) {
const isBinding = /^sharp-(darwin|linux|win32)-/i.test(dirName);
const isLibvips = /^sharp-libvips-/i.test(dirName);
if ((isBinding && dirName !== target.binding) || (isLibvips && dirName !== target.libvips)) {
removeIfExists(path.join(imgDir, dirName));
}
}
}
function targetTokenizersPackage(targetPlatform, targetArch) {
if (targetPlatform === 'darwin') return 'tokenizers-darwin-universal';
if (targetPlatform === 'win32' && targetArch === 'x64') return 'tokenizers-win32-x64-msvc';
return null;
}
function pruneTokenizersPackages(nodeModules, targetPlatform, targetArch) {
const keepPackage = targetTokenizersPackage(targetPlatform, targetArch);
for (const parentDir of tokenizersPackageParentDirs(nodeModules)) {
for (const dirName of listDirs(parentDir)) {
if (/^tokenizers-/i.test(dirName) && dirName !== keepPackage) {
removeIfExists(path.join(parentDir, dirName));
}
}
}
}
function pruneBetterSqlite3BuildArtifacts(nodeModules) {
// @electron/rebuild compiles better-sqlite3's optional test extension next
// to the production binding. It is not loaded by Orkas and must not become
// an undeclared native payload merely because npmRebuild ran during pack.
removeIfExists(path.join(
nodeModules,
'better-sqlite3',
'build',
'Release',
'test_extension.node',
));
}
function packageDir(nodeModules, packageName) {
return path.join(nodeModules, ...packageName.split('/'));
}
function sqliteVecPackageParentDirs(nodeModules) {
return [
nodeModules,
path.join(nodeModules, 'sqlite-vec', 'node_modules'),
];
}
function sqliteVecPackageDir(nodeModules, packageName) {
for (const parentDir of sqliteVecPackageParentDirs(nodeModules)) {
const candidate = path.join(parentDir, packageName);
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
return candidate;
}
}
return path.join(nodeModules, packageName);
}
function canvasPackageParentDirs(nodeModules) {
return [
path.join(nodeModules, '@napi-rs'),
path.join(nodeModules, '@napi-rs', 'canvas', 'node_modules', '@napi-rs'),
];
}
function canvasPackageDir(nodeModules, packageName) {
for (const parentDir of canvasPackageParentDirs(nodeModules)) {
const candidate = path.join(parentDir, packageName);
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
return candidate;
}
}
return path.join(nodeModules, '@napi-rs', packageName);
}
function tokenizersPackageParentDirs(nodeModules) {
return [
path.join(nodeModules, '@anush008'),
path.join(nodeModules, '@anush008', 'tokenizers', 'node_modules', '@anush008'),
];
}
function tokenizersPackageDir(nodeModules, packageName) {
for (const parentDir of tokenizersPackageParentDirs(nodeModules)) {
const candidate = path.join(parentDir, packageName);
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
return candidate;
}
}
return path.join(nodeModules, '@anush008', packageName);
}
function requireOnlyPackages(parentDir, pattern, allowedPackages) {
const allowed = new Set(allowedPackages.filter(Boolean));
for (const dirName of listDirs(parentDir)) {
if (pattern.test(dirName) && !allowed.has(dirName)) {
throw new Error(`[native-deps-gate] unexpected native package for target: ${path.join(parentDir, dirName)}`);
}
}
}
function verifyOnnxRuntimePackage(pkgDir, targetPlatform, targetArch, verified) {
const binDir = path.join(pkgDir, 'bin');
if (!fs.existsSync(binDir)) return;
for (const napiDirName of listDirs(binDir)) {
const napiDir = path.join(binDir, napiDirName);
const platformDirs = listDirs(napiDir);
for (const platformDirName of platformDirs) {
if (platformDirName !== targetPlatform) {
throw new Error(`[native-deps-gate] unexpected onnxruntime platform ${platformDirName}: ${path.join(napiDir, platformDirName)}`);
}
}
const platformDir = path.join(napiDir, targetPlatform);
const archDirs = listDirs(platformDir);
for (const archDirName of archDirs) {
if (archDirName !== targetArch) {
throw new Error(`[native-deps-gate] unexpected onnxruntime arch ${archDirName}: ${path.join(platformDir, archDirName)}`);
}
}
requiredFile(
`onnxruntime ${targetPlatform}/${targetArch} binding`,
path.join(platformDir, targetArch, 'onnxruntime_binding.node'),
);
verified.push(`${path.relative(pkgDir, path.join(platformDir, targetArch))}`);
}
}
function verifyPackedNativePayload(nodeModules, targetPlatform, targetArch) {
const esbuildPackage = targetEsbuildPackage(targetPlatform, targetArch);
const sqlitePackage = targetSqliteVecPackage(targetPlatform, targetArch);
const canvasPackage = targetCanvasPackage(targetPlatform, targetArch);
const tokenizersPackage = targetTokenizersPackage(targetPlatform, targetArch);
const sharpPackages = targetSharpPackages(targetPlatform, targetArch);
const lock = readPackageLock();
const sharpVersion = lock.packages[`node_modules/@img/${sharpPackages.binding}`]?.version;
if (!sharpVersion) {
throw new Error(`[native-deps-gate] package-lock missing @img/${sharpPackages.binding}`);
}
const verified = [];
requireOnlyPackages(path.join(nodeModules, '@esbuild'), /^.+$/, [esbuildPackage]);
requireOnlyPackages(nodeModules, /^sqlite-vec-(darwin|linux|windows)-/i, [sqlitePackage]);
requireOnlyPackages(path.join(nodeModules, 'sqlite-vec', 'node_modules'), /^sqlite-vec-(darwin|linux|windows)-/i, [sqlitePackage]);
requireOnlyPackages(path.join(nodeModules, '@napi-rs'), /^canvas-/i, [canvasPackage]);
requireOnlyPackages(path.join(nodeModules, '@napi-rs', 'canvas', 'node_modules', '@napi-rs'), /^canvas-/i, [canvasPackage]);
requireOnlyPackages(path.join(nodeModules, '@anush008'), /^tokenizers-/i, [tokenizersPackage]);
requireOnlyPackages(path.join(nodeModules, '@anush008', 'tokenizers', 'node_modules', '@anush008'), /^tokenizers-/i, [tokenizersPackage]);
requireOnlyPackages(path.join(nodeModules, '@img'), /^sharp-(darwin|linux|win32)-/i, [sharpPackages.binding]);
requireOnlyPackages(path.join(nodeModules, '@img'), /^sharp-libvips-/i, [sharpPackages.libvips]);
if (targetPlatform !== 'darwin' && fs.existsSync(path.join(nodeModules, 'fsevents'))) {
throw new Error('[native-deps-gate] fsevents must not be included in non-macOS packages');
}
if (targetPlatform === 'darwin') {
requiredFile(
`${esbuildPackage} binary`,
path.join(packageDir(nodeModules, `@esbuild/${esbuildPackage}`), 'bin', 'esbuild'),
);
requiredFile(
`${sqlitePackage} binary`,
path.join(sqliteVecPackageDir(nodeModules, sqlitePackage), 'vec0.dylib'),
);
requiredFile(
`${canvasPackage} binary`,
path.join(canvasPackageDir(nodeModules, canvasPackage), `skia.darwin-${targetArch}.node`),
);
requiredFile(
`${tokenizersPackage} binary`,
path.join(tokenizersPackageDir(nodeModules, tokenizersPackage), 'tokenizers.darwin-universal.node'),
);
requiredFile(
`${sharpPackages.binding} binding`,
path.join(packageDir(nodeModules, `@img/${sharpPackages.binding}`), 'lib', `sharp-darwin-${targetArch}-${sharpVersion}.node`),
);
requiredFile(
`${sharpPackages.libvips} runtime`,
path.join(packageDir(nodeModules, `@img/${sharpPackages.libvips}`), 'lib', 'libvips-cpp.8.18.3.dylib'),
);
} else if (targetPlatform === 'win32') {
requiredFile(
`${esbuildPackage} binary`,
path.join(packageDir(nodeModules, `@esbuild/${esbuildPackage}`), 'esbuild.exe'),
);
requiredFile(
`${sqlitePackage} binary`,
path.join(sqliteVecPackageDir(nodeModules, sqlitePackage), 'vec0.dll'),
);
requiredFile(
`${canvasPackage} binary`,
path.join(canvasPackageDir(nodeModules, canvasPackage), 'skia.win32-x64-msvc.node'),
);
requiredFile(
`${tokenizersPackage} binary`,
path.join(tokenizersPackageDir(nodeModules, tokenizersPackage), 'tokenizers.win32-x64-msvc.node'),
);
requiredFile(
`${sharpPackages.binding} binding`,
path.join(packageDir(nodeModules, `@img/${sharpPackages.binding}`), 'lib', `sharp-win32-x64-${sharpVersion}.node`),
);
requiredFile(
`${sharpPackages.binding} libvips C++ runtime`,
path.join(packageDir(nodeModules, `@img/${sharpPackages.binding}`), 'lib', 'libvips-cpp-8.18.3.dll'),
);
requiredFile(
`${sharpPackages.binding} libvips runtime`,
path.join(packageDir(nodeModules, `@img/${sharpPackages.binding}`), 'lib', 'libvips-42.dll'),
);
}
requiredFile(
'better-sqlite3 Electron native binding',
path.join(nodeModules, 'better-sqlite3', 'build', 'Release', 'better_sqlite3.node'),
);
verifyPackageVersion(nodeModules, lock, `@esbuild/${esbuildPackage}`, verified);
verifyPackageVersionAtDir(sqliteVecPackageDir(nodeModules, sqlitePackage), lock, sqlitePackage, verified);
verifyPackageVersionAtDir(canvasPackageDir(nodeModules, canvasPackage), lock, `@napi-rs/${canvasPackage}`, verified);
verifyPackageVersionAtDir(tokenizersPackageDir(nodeModules, tokenizersPackage), lock, `@anush008/${tokenizersPackage}`, verified);
verifyPackageVersion(nodeModules, lock, `@img/${sharpPackages.binding}`, verified);
if (sharpPackages.libvips) verifyPackageVersion(nodeModules, lock, `@img/${sharpPackages.libvips}`, verified);
verifyPackageVersion(nodeModules, lock, 'better-sqlite3', verified);
for (const pkgDir of [
path.join(nodeModules, 'onnxruntime-node'),
path.join(nodeModules, 'fastembed', 'node_modules', 'onnxruntime-node'),
]) {
verifyOnnxRuntimePackage(pkgDir, targetPlatform, targetArch, verified);
}
verifyExistingPackageVersion(
path.join(nodeModules, 'onnxruntime-node'),
lock,
'node_modules/onnxruntime-node',
verified,
);
verifyExistingPackageVersion(
path.join(nodeModules, 'fastembed', 'node_modules', 'onnxruntime-node'),
lock,
'node_modules/fastembed/node_modules/onnxruntime-node',
verified,
);
return verified.filter(Boolean);
}
function appNodeModules(context, appPath) {
return path.join(appUnpackedRoot(context, appPath), 'node_modules');
}
function appUnpackedRoot(context, appPath) {
if (context.electronPlatformName === 'darwin') {
return path.join(appPath, 'Contents', 'Resources', 'app.asar.unpacked');
}
return path.join(context.appOutDir, 'resources', 'app.asar.unpacked');
}
function appResourcesDir(context, appPath) {
if (context.electronPlatformName === 'darwin') {
return path.join(appPath, 'Contents', 'Resources');
}
return path.join(context.appOutDir, 'resources');
}
function readJsonFile(label, file) {
requiredFile(label, file);
try {
return JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (err) {
throw new Error(`[native-deps-gate] invalid ${label}: ${file}: ${err.message}`);
}
}
function packageLockPath() {
return path.join(__dirname, '..', 'package-lock.json');
}
function packageLockEntryPath(packageName) {
return `node_modules/${packageName}`;
}
function readPackageLock() {
return readJsonFile('package lock', packageLockPath());
}
function lockPackageVersion(lock, packageName, lockPath = packageLockEntryPath(packageName)) {
const version = lock.packages?.[lockPath]?.version;
if (!version) {
throw new Error(`[native-deps-gate] package-lock.json missing ${lockPath}`);
}
return version;
}
function packageNameFromLockPath(lockPath) {
const marker = 'node_modules/';
const index = lockPath.lastIndexOf(marker);
return index >= 0 ? lockPath.slice(index + marker.length) : lockPath;
}
function lockPackageVersionByName(lock, packageName) {
const versions = new Set();
for (const [lockPath, entry] of Object.entries(lock.packages || {})) {
if (packageNameFromLockPath(lockPath) === packageName && entry?.version) {
versions.add(String(entry.version));
}
}
if (versions.size === 0) {
throw new Error(`[native-deps-gate] package-lock.json missing package ${packageName}`);
}
if (versions.size > 1) {
throw new Error(`[native-deps-gate] package-lock.json has multiple versions for ${packageName}: ${[...versions].join(', ')}`);
}
return [...versions][0];
}
function lockPackageVersionForPathOrName(lock, packageName, lockPath) {
const directVersion = lock.packages?.[lockPath]?.version;
if (directVersion) return String(directVersion);
return lockPackageVersionByName(lock, packageName);
}
function packageJsonPath(nodeModules, packageName) {
return path.join(nodeModules, ...packageName.split('/'), 'package.json');
}
function verifyPackageVersion(nodeModules, lock, packageName, verified, lockPath = packageLockEntryPath(packageName)) {
verifyPackageVersionAtDir(packageDir(nodeModules, packageName), lock, packageName, verified, lockPath);
}
function verifyPackageVersionAtDir(pkgDir, lock, packageName, verified, lockPath = packageLockEntryPath(packageName)) {
const pkg = readJsonFile(`${packageName} package.json`, path.join(pkgDir, 'package.json'));
const expected = lockPackageVersion(lock, packageName, lockPath);
if (String(pkg.version || '') !== String(expected)) {
throw new Error(`[native-deps-gate] ${packageName} version mismatch: packaged=${pkg.version || '(missing)'} lock=${expected}`);
}
verified.push(`${packageName}@${expected}`);
}
function verifyExistingPackageVersion(packageDir, lock, lockPath, verified) {
if (!fs.existsSync(packageDir) || !fs.statSync(packageDir).isDirectory()) return;
const pkg = readJsonFile(`${lockPath} package.json`, path.join(packageDir, 'package.json'));
const packageName = pkg.name || packageNameFromLockPath(lockPath);
const expected = lockPackageVersionForPathOrName(lock, packageName, lockPath);
if (String(pkg.version || '') !== String(expected)) {
throw new Error(`[native-deps-gate] ${lockPath} version mismatch: packaged=${pkg.version || '(missing)'} lock=${expected}`);
}
verified.push(`${lockPath}@${expected}`);
}
function verifyPackedRuntimePayload(context, appPath, targetPlatform, targetArch) {
const runtimeRoot = path.join(appResourcesDir(context, appPath), 'runtime');
// npm/npx companion verification (my node-runtime hardening) is preserved by
// verifyRuntimeRoot → verifyRuntimeDir → runtimeCompanionFiles (runtime-gate.cjs),
// which the remote extracted; it also keeps marker/pip-shim/arch/dir-allowlist checks.
const options = {
...(context.__orkasTestWhisperContract ? { whisperContract: context.__orkasTestWhisperContract } : {}),
...(context.__orkasTestWindowsVcContract ? { windowsVcContract: context.__orkasTestWindowsVcContract } : {}),
};
const verified = verifyRuntimeRoot(runtimeRoot, targetPlatform, targetArch, options);
if (targetPlatform === 'win32') {
verified.push(verifyWindowsVcAppLocalFiles(context.appOutDir, targetArch, options));
verified.push(verifyWindowsVcImportClosure(context.appOutDir, targetArch, {
...(context.__orkasTestWindowsVcContract ? { allowMinimalPe: true, allowNoVcImports: true } : {}),
}));
}
return verified;
}
function verifyPackedResourcePayload(context, appPath, targetPlatform) {
const resourcesDir = appResourcesDir(context, appPath);
const builtinRoot = context.__orkasTestBuiltinRoot || path.join(resourcesDir, 'builtin');
const verified = [
verifyEmbeddingModelRoot(path.join(resourcesDir, 'embedding-model')),
verifyBuiltinRoot(builtinRoot, context.__orkasTestBuiltinRoot ? { allowIgnoredJunk: true } : {}),
];
if (targetPlatform === 'darwin') {
verified.push(verifyMacLocalizedMetadataRoot(resourcesDir, { allowElectronResources: true }));
}
return verified;
}
function verifyPackedEntrypointPayload(context, appPath) {
const unpackedRoot = context.__orkasTestEntrypointRoot || appUnpackedRoot(context, appPath);
return [
...verifyPackagedEntrypointPayload(
unpackedRoot,
{
projectRoot: path.join(__dirname, '..'),
},
),
...verifyPackagedConnectorRuntime(unpackedRoot),
];
}
function verifyPackedDependencyPayload(context, appPath, targetPlatform, targetArch) {
const projectRoot = path.join(__dirname, '..');
const options = {
appAsar: path.join(appResourcesDir(context, appPath), 'app.asar'),
packageJsonFile: path.join(projectRoot, 'package.json'),
packageLockFile: path.join(projectRoot, 'package-lock.json'),
platform: targetPlatform,
arch: targetArch,
};
const result = context.__orkasTestDependencyGate
? context.__orkasTestDependencyGate(options)
: verifyPackagedDependencyGraph(options);
if (!result || typeof result.verificationEntry !== 'string' || !result.verificationEntry) {
throw new Error('[native-deps-gate] packaged dependency gate returned no verification entry');
}
return [result.verificationEntry];
}
function prunePackedNativePayload(context, appPath, targetPlatform, targetArch) {
const nodeModules = appNodeModules(context, appPath);
if (!fs.existsSync(nodeModules)) return [];
const packages = [
path.join(nodeModules, 'onnxruntime-node'),
path.join(nodeModules, 'fastembed', 'node_modules', 'onnxruntime-node'),
];
pruneEsbuildPackage(nodeModules, targetPlatform, targetArch);
pruneSqliteVecPackages(nodeModules, targetPlatform, targetArch);
pruneCanvasPackages(nodeModules, targetPlatform, targetArch);
pruneSharpPackages(nodeModules, targetPlatform, targetArch);
pruneTokenizersPackages(nodeModules, targetPlatform, targetArch);
pruneBetterSqlite3BuildArtifacts(nodeModules);
if (targetPlatform !== 'darwin') {
removeIfExists(path.join(nodeModules, 'fsevents'));
}
for (const pkgDir of packages) {
pruneOnnxRuntimePackage(pkgDir, targetPlatform, targetArch);
}
const verified = verifyPackedNativePayload(nodeModules, targetPlatform, targetArch);
verified.push(...verifyNativePackagePayload(nodeModules, targetPlatform, targetArch));
return verified;
}
function writeNativeGateMarker(context, targetPlatform, targetArch, verified) {
const marker = {
status: 'passed',
phase: 'post-package/pre-sign',
platform: targetPlatform,
arch: targetArch,
verified,
checkedAt: new Date().toISOString(),
};
fs.writeFileSync(
path.join(context.appOutDir, '.orkas-native-deps-verified.json'),
`${JSON.stringify(marker, null, 2)}\n`,
);
}
function ensureTrailingSlash(value) {
return value.endsWith('/') ? value : `${value}/`;
}
function publishEntries(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
if (typeof value === 'object') return [value];
return [];
}
function genericPublishUrl(context) {
const publish = context?.packager?.config?.publish;
for (const item of publishEntries(publish)) {
if (!item || typeof item !== 'object') continue;
if (item.provider === 'generic' && typeof item.url === 'string' && item.url.trim()) {
return ensureTrailingSlash(item.url.trim());
}
}
const envUrl = String(process.env.DEV_FEED_URL || process.env.PRODUCT_FEED_URL || '').trim();
if (envUrl) return ensureTrailingSlash(envUrl);
return '';
}
function updaterCacheDirName(context) {
const fromBuilder = context?.packager?.appInfo?.updaterCacheDirName;
if (typeof fromBuilder === 'string' && fromBuilder.trim()) return fromBuilder.trim();
return 'orkas-updater';
}
function writeMacAppUpdateConfig(context, appPath) {
if (context.electronPlatformName !== 'darwin') return;
const resourcesDir = path.join(appPath, 'Contents', 'Resources');
const configFile = path.join(resourcesDir, 'app-update.yml');
if (process.argv.includes('--prepackaged')) {
if (!fs.existsSync(configFile)) {
console.warn(`[app-update-config] missing in prepackaged app; not mutating signed bundle: ${configFile}`);
}
return;
}
const url = genericPublishUrl(context);
if (!url) {
console.warn('[app-update-config] no generic publish URL; app-update.yml was not written');
return;
}
fs.mkdirSync(resourcesDir, { recursive: true });
const body = [
'provider: generic',
`url: ${JSON.stringify(url)}`,
`updaterCacheDirName: ${updaterCacheDirName(context)}`,
'',
].join('\n');
fs.writeFileSync(configFile, body, 'utf8');
console.log(`[app-update-config] wrote ${configFile}`);
}
async function afterPack(context) {
const targetPlatform = context.electronPlatformName;
const targetArch = ARCH_NAMES[context.arch] || process.arch;
const appName = context.packager.appInfo.productFilename;
const appPath = targetPlatform === 'darwin'
? path.join(context.appOutDir, `${appName}.app`)
: context.appOutDir;
writeMacAppUpdateConfig(context, appPath);
console.log('==== Native dependency gate: post-package/pre-sign ====');
console.log(`[native-deps-gate] target=${targetPlatform}/${targetArch}`);
const verified = prunePackedNativePayload(context, appPath, targetPlatform, targetArch);
verified.push(...verifyPackedRuntimePayload(context, appPath, targetPlatform, targetArch));
verified.push(...verifyPackedResourcePayload(context, appPath, targetPlatform));
verified.push(...verifyPackedEntrypointPayload(context, appPath));
verified.push(...verifyPackedDependencyPayload(context, appPath, targetPlatform, targetArch));
writeNativeGateMarker(context, targetPlatform, targetArch, verified);
console.log(`[native-deps-gate] verified: ${verified.join(', ')}`);
console.log('[native-deps-gate] result=passed; signing may continue');
if (targetPlatform !== 'darwin') return;
if (process.env.ORKAS_SKIP_ADHOC_CODESIGN === '1') {
console.log('[codesign-adhoc] disabled by ORKAS_SKIP_ADHOC_CODESIGN=1; leaving app unsigned');
return;
}
if (process.env.ORKAS_FORCE_ADHOC_CODESIGN !== '1' && (process.env.CSC_LINK || process.env.CSC_NAME)) {
console.log('[codesign-adhoc] formal signing env detected; skipping ad-hoc signing');
return;
}
console.log(`[codesign-adhoc] signing ${appPath}`);
execSync(`codesign --force --deep --sign - "${appPath}"`, { stdio: 'inherit' });
// Fail the build if the fallback signature is invalid.
execSync(`codesign --verify --deep "${appPath}"`, { stdio: 'inherit' });
}
module.exports = afterPack;
module.exports.__test = {
pruneBetterSqlite3BuildArtifacts,
pruneEsbuildPackage,
pruneOnnxRuntimePackage,
};