Files
Orkas/scripts/prepare-mac-native-deps.cjs
wyt baa9dd5ba0 Improve task-board controls and scoped connector authorization
Preserve keyboard focus and single activation across task-board refreshes. Add task-scoped grants, partial-permission recovery, validated CLI attachments, device-local project directories, and the public model catalog update.

Prompt confirmation:
  Confirmed-by: Requester in the current implementation task
  Scope: Apply the existing approved shared agent, skill, task, and connector
    capabilities within the documented open-source product boundaries.

Prompt audit:
- Keep: Commander and actor task identity, ordered execution, user input,
  project context, and tool-use responsibilities in the shared prompt
  templates and their existing assembly path. Type: resident.
  Why: These clauses determine behavior across ordinary turns; the update
  preserves the shared product's source-of-truth context and recovery
  ownership without adding an independent open-source policy layer.
- Keep: Project-task, scheduling, memory, connector setup, UI artifact,
  image/video composition, Office, and SEO workflows in their owning
  skills and referenced guides. Type: on-demand.
  Why: Load specialized procedures only for the corresponding work; retain
  source metadata and dependency ownership rather than duplicate those
  procedures in every actor prompt.
- Keep: Scoped tool schemas, queue/permission state, persisted recovery
  evidence, output storage, resource manifests, and deterministic validators
  at their existing host boundaries. Type: non-prompt enforcement.
  Why: Code and structured events own the invariants and prevent the prompt
  from becoming a second authorization, persistence, or execution engine.
- Drop: Hosted account, membership, cloud/relay, telemetry, internal debug,
  managed voice, watermark, welcome, and official updater instructions or
  affordances from the public execution paths. Type: source-only/on-demand.
  Why: Those paths require services absent from the open-source product;
  shared local operations and public API-key/BYO capabilities remain usable.

Validation: Recorded 11,721 deterministic tests, 538 resource tests, 1,248 native tests, five GUI checks and 198 reconciled desktop scenarios; final keyboard owner regressions also passed. Initial failed runs and classified warnings are retained in validation evidence.
2026-09-11 11:23:45 +08:00

208 lines
7.7 KiB
JavaScript

'use strict';
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { sharpPreparationFiles } = require('../bin/native-package-gate.cjs');
const {
ensureFile,
isMachArch,
markerMatches,
packagePath,
packageVersion,
readLockPackage,
scriptHashes,
writeMarker,
} = require('./native-prepare-cache.cjs');
const PC_DIR = path.resolve(__dirname, '..');
const LOCK_FILE = path.join(PC_DIR, 'package-lock.json');
const CACHE_HELPER = path.join(__dirname, 'native-prepare-cache.cjs');
const TARGET_PLATFORM = 'darwin';
function npmCmd(platform = process.platform) {
return platform === 'win32' ? 'npm.cmd' : 'npm';
}
function run(cwd, command, args, options = {}) {
const result = spawnSync(command, args, {
cwd,
stdio: options.stdio || 'inherit',
encoding: options.encoding,
shell: false,
});
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
}
return result;
}
function npmPack(cwd, spec) {
const result = run(cwd, npmCmd(), ['pack', spec, '--json'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'inherit'],
});
const packs = JSON.parse(result.stdout);
const filename = packs?.[0]?.filename;
if (!filename) {
throw new Error(`npm pack ${spec} did not report an output filename`);
}
return path.join(cwd, filename);
}
function ensureEsbuildPackage(platformName) {
const packageName = `@esbuild/${platformName}`;
const required = [path.join(PC_DIR, 'node_modules', '@esbuild', platformName, 'bin', 'esbuild')];
ensurePackageFromRegistry(packageName, required);
}
function allFilesExist(files) {
return files.every((file) => fs.existsSync(file) && fs.statSync(file).isFile());
}
function ensurePackageFromRegistry(packageName, requiredFiles = []) {
const version = readLockPackage(LOCK_FILE, packageName);
const targetDir = packagePath(PC_DIR, packageName);
if (packageVersion(PC_DIR, packageName) === version && allFilesExist(requiredFiles)) {
console.log(`[prepare-mac-native-deps] reusing ${packageName}@${version}`);
return;
}
console.log(`[prepare-mac-native-deps] ensuring ${packageName}@${version}`);
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'orkas-mac-native-deps-'));
try {
const tarball = npmPack(tmpDir, `${packageName}@${version}`);
fs.rmSync(targetDir, { recursive: true, force: true });
fs.mkdirSync(targetDir, { recursive: true });
const archiveName = path.basename(tarball);
const localTarball = path.join(targetDir, archiveName);
fs.copyFileSync(tarball, localTarball);
try {
run(targetDir, 'tar', ['-xzf', archiveName, '--strip-components=1']);
} finally {
fs.rmSync(localTarball, { force: true });
}
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}
function removeDirectories(parentDir, shouldRemove) {
if (!fs.existsSync(parentDir) || !fs.statSync(parentDir).isDirectory()) {
return;
}
for (const entry of fs.readdirSync(parentDir, { withFileTypes: true })) {
if (entry.isDirectory() && shouldRemove(entry.name)) {
fs.rmSync(path.join(parentDir, entry.name), { recursive: true, force: true });
}
}
}
function expectedState(targetArch) {
return {
schema: 1,
platform: TARGET_PLATFORM,
arch: targetArch,
scriptHashes: scriptHashes({
prepareMacNativeDeps: __filename,
nativePrepareCache: CACHE_HELPER,
nativePackageGate: require.resolve('../bin/native-package-gate.cjs'),
}),
packages: {
[`@esbuild/darwin-${targetArch}`]: readLockPackage(LOCK_FILE, `@esbuild/darwin-${targetArch}`),
[`sqlite-vec-darwin-${targetArch}`]: readLockPackage(LOCK_FILE, `sqlite-vec-darwin-${targetArch}`),
[`@napi-rs/canvas-darwin-${targetArch}`]: readLockPackage(LOCK_FILE, `@napi-rs/canvas-darwin-${targetArch}`),
[`@img/sharp-darwin-${targetArch}`]: readLockPackage(LOCK_FILE, `@img/sharp-darwin-${targetArch}`),
[`@img/sharp-libvips-darwin-${targetArch}`]: readLockPackage(LOCK_FILE, `@img/sharp-libvips-darwin-${targetArch}`),
},
};
}
function assertMachArch(label, file, targetArch) {
ensureFile(label, file);
if (!isMachArch(file, targetArch)) {
throw new Error(`${label} is not macOS ${targetArch}: ${file}`);
}
}
function main() {
const targetArch = process.argv[2] || process.env.ORKAS_TARGET_ARCH || process.arch;
if (!['arm64', 'x64'].includes(targetArch)) {
throw new Error(`[prepare-mac-native-deps] unsupported macOS arch: ${targetArch}`);
}
fs.mkdirSync(path.join(PC_DIR, 'node_modules', '@esbuild'), { recursive: true });
fs.mkdirSync(path.join(PC_DIR, 'node_modules', '@img'), { recursive: true });
const state = expectedState(targetArch);
const sharpVersion = state.packages[`@img/sharp-darwin-${targetArch}`];
const sharpFiles = sharpPreparationFiles('darwin', targetArch, sharpVersion);
const required = {
esbuild: path.join(PC_DIR, 'node_modules', '@esbuild', `darwin-${targetArch}`, 'bin', 'esbuild'),
sqliteVec: path.join(PC_DIR, 'node_modules', `sqlite-vec-darwin-${targetArch}`, 'vec0.dylib'),
canvas: path.join(PC_DIR, 'node_modules', '@napi-rs', `canvas-darwin-${targetArch}`, `skia.darwin-${targetArch}.node`),
sharp: path.join(PC_DIR, 'node_modules', sharpFiles.binding),
sharpVips: path.join(PC_DIR, 'node_modules', sharpFiles.cpp),
};
const requiredFiles = Object.values(required);
const targetFilesMatch = () => requiredFiles.every((file) => isMachArch(file, targetArch));
if (markerMatches(PC_DIR, state, requiredFiles, targetFilesMatch)) {
console.log(`[prepare-mac-native-deps] using cached macOS ${targetArch} native dependencies`);
return;
}
ensureEsbuildPackage(`darwin-${targetArch}`);
ensurePackageFromRegistry(`sqlite-vec-darwin-${targetArch}`, [required.sqliteVec]);
ensurePackageFromRegistry(`@napi-rs/canvas-darwin-${targetArch}`, [required.canvas]);
ensurePackageFromRegistry(`@img/sharp-darwin-${targetArch}`, [required.sharp]);
ensurePackageFromRegistry(`@img/sharp-libvips-darwin-${targetArch}`, [required.sharpVips]);
console.log(`[prepare-mac-native-deps] pruning non-${targetArch} native runtime packages`);
removeDirectories(path.join(PC_DIR, 'node_modules', '@esbuild'), (name) => name !== `darwin-${targetArch}`);
removeDirectories(path.join(PC_DIR, 'node_modules'), (name) => /^sqlite-vec-(?!darwin-)/i.test(name) || (/^sqlite-vec-darwin-/i.test(name) && name !== `sqlite-vec-darwin-${targetArch}`));
removeDirectories(path.join(PC_DIR, 'node_modules', '@napi-rs'), (name) => /^canvas-/i.test(name) && name !== `canvas-darwin-${targetArch}`);
removeDirectories(path.join(PC_DIR, 'node_modules', '@anush008'), (name) => /^tokenizers-/i.test(name) && name !== 'tokenizers-darwin-universal');
removeDirectories(path.join(PC_DIR, 'node_modules', '@img'), (name) => (
(/^sharp-(darwin|linux|win32)-/i.test(name) && name !== `sharp-darwin-${targetArch}`)
|| (/^sharp-libvips-/i.test(name) && name !== `sharp-libvips-darwin-${targetArch}`)
));
assertMachArch(
`macOS ${targetArch} esbuild runtime binary`,
required.esbuild,
targetArch,
);
assertMachArch(
`macOS ${targetArch} sqlite-vec runtime binary`,
required.sqliteVec,
targetArch,
);
assertMachArch(
`macOS ${targetArch} canvas runtime binary`,
required.canvas,
targetArch,
);
assertMachArch(
`macOS ${targetArch} sharp runtime binding`,
required.sharp,
targetArch,
);
assertMachArch(
`macOS ${targetArch} sharp libvips runtime`,
required.sharpVips,
targetArch,
);
writeMarker(PC_DIR, state);
}
module.exports = {
allFilesExist,
npmCmd,
removeDirectories,
};
if (require.main === module) main();