670 lines
25 KiB
JavaScript
670 lines
25 KiB
JavaScript
import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'
|
|
import { createRequire } from 'node:module'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join } from 'node:path'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const electronBuilderConfig = require('../electron-builder.config.cjs')
|
|
const electronBuilderNativeRebuild = require('./electron-builder-native-rebuild.cjs')
|
|
const {
|
|
createPackagedRuntimeNodeModuleResources,
|
|
findAsarEntry,
|
|
prunePackagedNodePty,
|
|
prunePackagedParcelWatcher,
|
|
prunePackagedSherpaOnnx,
|
|
prunePackagedRuntimeTypeDeclarations,
|
|
prunePackagedZodSources,
|
|
verifyPackagedMainRuntimeDeps
|
|
} = require('../packaged-runtime-node-modules.cjs')
|
|
|
|
const electronBuilderConfigPath = require.resolve('../electron-builder.config.cjs')
|
|
const macNotaryEnvKeys = [
|
|
'APPLE_ID',
|
|
'APPLE_APP_SPECIFIC_PASSWORD',
|
|
'APPLE_TEAM_ID',
|
|
'APPLE_API_KEY',
|
|
'APPLE_API_KEY_ID',
|
|
'APPLE_API_ISSUER',
|
|
'APPLE_KEYCHAIN_PROFILE',
|
|
'APPLE_KEYCHAIN',
|
|
'ORCA_MAC_RELEASE'
|
|
]
|
|
const appleIdMacReleaseEnv = {
|
|
APPLE_APP_SPECIFIC_PASSWORD: 'app-password',
|
|
APPLE_ID: 'release@example.com',
|
|
APPLE_TEAM_ID: 'TEAMID',
|
|
ORCA_MAC_RELEASE: '1'
|
|
}
|
|
|
|
async function withElectronBuilderConfigEnv(env, callback) {
|
|
const previous = new Map(macNotaryEnvKeys.map((key) => [key, process.env[key]]))
|
|
try {
|
|
for (const key of macNotaryEnvKeys) {
|
|
delete process.env[key]
|
|
}
|
|
Object.assign(process.env, env)
|
|
delete require.cache[electronBuilderConfigPath]
|
|
return await callback(require('../electron-builder.config.cjs'))
|
|
} finally {
|
|
for (const key of macNotaryEnvKeys) {
|
|
const value = previous.get(key)
|
|
if (value === undefined) {
|
|
delete process.env[key]
|
|
} else {
|
|
process.env[key] = value
|
|
}
|
|
}
|
|
delete require.cache[electronBuilderConfigPath]
|
|
}
|
|
}
|
|
|
|
function createMacNotarizationContext(appOutDir) {
|
|
return {
|
|
appOutDir,
|
|
electronPlatformName: 'darwin',
|
|
packager: {
|
|
appInfo: {
|
|
productFilename: 'Orca'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function requiredMacServeSimDylibPaths(appOutDir) {
|
|
const resourcesDir = join(appOutDir, 'Orca.app', 'Contents', 'Resources')
|
|
return [
|
|
join(resourcesDir, 'serve-sim', 'dist', 'simcam', 'libSimCameraInjector.dylib'),
|
|
join(resourcesDir, 'node_modules', 'serve-sim', 'dist', 'simcam', 'libSimCameraInjector.dylib')
|
|
]
|
|
}
|
|
|
|
function optionalMacServeSimDylibPaths(appOutDir) {
|
|
const resourcesDir = join(appOutDir, 'Orca.app', 'Contents', 'Resources')
|
|
return [
|
|
join(
|
|
resourcesDir,
|
|
'app.asar.unpacked',
|
|
'node_modules',
|
|
'serve-sim',
|
|
'dist',
|
|
'simcam',
|
|
'libSimCameraInjector.dylib'
|
|
)
|
|
]
|
|
}
|
|
|
|
function macServeSimDylibPaths(appOutDir) {
|
|
return [...requiredMacServeSimDylibPaths(appOutDir), ...optionalMacServeSimDylibPaths(appOutDir)]
|
|
}
|
|
|
|
function createMacNotarizationHost({ existingPaths, output = { status: 'Accepted' } }) {
|
|
const calls = []
|
|
let temporaryDirectoryIndex = 0
|
|
const host = {
|
|
calls,
|
|
execFileSync: vi.fn((command, args, options = {}) => {
|
|
calls.push({ args, command, options })
|
|
if (command === 'xcrun') {
|
|
return typeof output === 'string' ? output : JSON.stringify(output)
|
|
}
|
|
return ''
|
|
}),
|
|
existsSync: vi.fn((targetPath) => existingPaths.has(targetPath)),
|
|
mkdtempSync: vi.fn((prefix) => `${prefix}${++temporaryDirectoryIndex}`),
|
|
rmSync: vi.fn()
|
|
}
|
|
return host
|
|
}
|
|
|
|
async function runMacAfterSignWithHost(config, appOutDir, host) {
|
|
return await config.__test.withMacNotarizationHost(host, async () => {
|
|
await config.afterSign(createMacNotarizationContext(appOutDir))
|
|
})
|
|
}
|
|
|
|
function expectNotaryCredentials(calls, credentials, expectedCallCount = 3) {
|
|
const notaryCalls = calls.filter(({ command }) => command === 'xcrun')
|
|
expect(notaryCalls).toHaveLength(expectedCallCount)
|
|
for (const notaryCall of notaryCalls) {
|
|
expect(notaryCall.args).toEqual(
|
|
expect.arrayContaining([
|
|
'notarytool',
|
|
'submit',
|
|
...credentials,
|
|
'--wait',
|
|
'--output-format',
|
|
'json'
|
|
])
|
|
)
|
|
}
|
|
}
|
|
|
|
describe('electron-builder config', () => {
|
|
it('excludes repo-only source trees from app.asar', () => {
|
|
expect(electronBuilderConfig.files).toEqual(
|
|
expect.arrayContaining([
|
|
'!src{,/**/*}',
|
|
'!config{,/**/*}',
|
|
'!docs{,/**/*}',
|
|
'!mobile{,/**/*}',
|
|
'!native{,/**/*}',
|
|
'!skills{,/**/*}',
|
|
'!tests{,/**/*}',
|
|
'!Casks{,/**/*}',
|
|
'!{AGENTS.md,CLAUDE.md,DEVELOPING.md,bundle-size-progress.md}',
|
|
'!out/**/*.test.js'
|
|
])
|
|
)
|
|
})
|
|
|
|
it('keeps runtime resources available through extraResources', () => {
|
|
expect(electronBuilderConfig.mac.extraResources).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
from: 'native/computer-use-macos/.build/release/Orca Computer Use.app',
|
|
to: 'Orca Computer Use.app'
|
|
})
|
|
])
|
|
)
|
|
expect(electronBuilderConfig.linux.extraResources).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
from: 'native/computer-use-linux/runtime.py',
|
|
to: 'computer-use-linux/runtime.py'
|
|
})
|
|
])
|
|
)
|
|
expect(electronBuilderConfig.win.extraResources).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
from: 'native/computer-use-windows/runtime.ps1',
|
|
to: 'computer-use-windows/runtime.ps1'
|
|
})
|
|
])
|
|
)
|
|
})
|
|
|
|
it('keeps the macOS afterSign hook inert outside macOS release builds', async () => {
|
|
for (const { context, env } of [
|
|
{ context: { electronPlatformName: 'darwin' }, env: {} },
|
|
{ context: { electronPlatformName: 'linux' }, env: { ORCA_MAC_RELEASE: '1' } }
|
|
]) {
|
|
await withElectronBuilderConfigEnv(env, async (config) => {
|
|
const host = createMacNotarizationHost({ existingPaths: new Set() })
|
|
|
|
await expect(
|
|
config.__test.withMacNotarizationHost(host, async () => {
|
|
await config.afterSign(context)
|
|
})
|
|
).resolves.toBeUndefined()
|
|
expect(host.execFileSync).not.toHaveBeenCalled()
|
|
})
|
|
}
|
|
})
|
|
|
|
it('submits all packaged serve-sim camera dylib copies in macOS releases', async () => {
|
|
await withElectronBuilderConfigEnv(appleIdMacReleaseEnv, async (config) => {
|
|
const appOutDir = join(tmpdir(), 'orca-electron-builder-test')
|
|
const dylibPaths = macServeSimDylibPaths(appOutDir)
|
|
const host = createMacNotarizationHost({ existingPaths: new Set(dylibPaths) })
|
|
|
|
await runMacAfterSignWithHost(config, appOutDir, host)
|
|
|
|
const dittoCalls = host.calls.filter(({ command }) => command === 'ditto')
|
|
const notaryCalls = host.calls.filter(({ command }) => command === 'xcrun')
|
|
expect(dittoCalls).toHaveLength(dylibPaths.length)
|
|
expect(notaryCalls).toHaveLength(dylibPaths.length)
|
|
expect(dittoCalls.map(({ options }) => options.cwd)).toEqual(
|
|
dylibPaths.map((dylibPath) => dirname(dylibPath))
|
|
)
|
|
expect(dittoCalls.every(({ options }) => options.timeout === 2 * 60 * 1000)).toBe(true)
|
|
expect(notaryCalls.every(({ options }) => options.timeout === 45 * 60 * 1000)).toBe(true)
|
|
expect(dittoCalls.map(({ args }) => args.at(-1))).toEqual(
|
|
notaryCalls.map(({ args }) => args[2])
|
|
)
|
|
expectNotaryCredentials(host.calls, [
|
|
'--apple-id',
|
|
'release@example.com',
|
|
'--password',
|
|
'app-password',
|
|
'--team-id',
|
|
'TEAMID'
|
|
])
|
|
expect(host.rmSync).toHaveBeenCalledTimes(dylibPaths.length)
|
|
})
|
|
})
|
|
|
|
it('does not require optional app.asar.unpacked serve-sim dylibs in macOS releases', async () => {
|
|
await withElectronBuilderConfigEnv(appleIdMacReleaseEnv, async (config) => {
|
|
const appOutDir = join(tmpdir(), 'orca-electron-builder-test')
|
|
const dylibPaths = requiredMacServeSimDylibPaths(appOutDir)
|
|
const host = createMacNotarizationHost({ existingPaths: new Set(dylibPaths) })
|
|
|
|
await runMacAfterSignWithHost(config, appOutDir, host)
|
|
|
|
const dittoCalls = host.calls.filter(({ command }) => command === 'ditto')
|
|
expect(dittoCalls).toHaveLength(dylibPaths.length)
|
|
expect(dittoCalls.map(({ options }) => options.cwd)).toEqual(
|
|
dylibPaths.map((dylibPath) => dirname(dylibPath))
|
|
)
|
|
expectNotaryCredentials(
|
|
host.calls,
|
|
['--apple-id', 'release@example.com', '--password', 'app-password', '--team-id', 'TEAMID'],
|
|
dylibPaths.length
|
|
)
|
|
expect(host.rmSync).toHaveBeenCalledTimes(dylibPaths.length)
|
|
})
|
|
})
|
|
|
|
it('fails before notarization when a packaged serve-sim camera dylib is missing', async () => {
|
|
await withElectronBuilderConfigEnv({ ORCA_MAC_RELEASE: '1' }, async (config) => {
|
|
const appOutDir = join(tmpdir(), 'orca-electron-builder-test')
|
|
const [firstDylibPath] = macServeSimDylibPaths(appOutDir)
|
|
const host = createMacNotarizationHost({ existingPaths: new Set([firstDylibPath]) })
|
|
|
|
await expect(
|
|
config.__test.notarizeMacServeSimCameraDylibsWithHost(
|
|
createMacNotarizationContext(appOutDir),
|
|
host
|
|
)
|
|
).rejects.toThrow('Missing serve-sim camera dylib for notarization')
|
|
|
|
expect(host.execFileSync).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
it('maps supported macOS notarization credential families to notarytool flags', async () => {
|
|
const cases = [
|
|
{
|
|
credentials: ['--key', '/tmp/AuthKey.p8', '--key-id', 'KEYID', '--issuer', 'issuer'],
|
|
env: {
|
|
APPLE_API_ISSUER: 'issuer',
|
|
APPLE_API_KEY: '/tmp/AuthKey.p8',
|
|
APPLE_API_KEY_ID: 'KEYID',
|
|
ORCA_MAC_RELEASE: '1'
|
|
}
|
|
},
|
|
{
|
|
credentials: [
|
|
'--apple-id',
|
|
'release@example.com',
|
|
'--password',
|
|
'app-password',
|
|
'--team-id',
|
|
'TEAMID'
|
|
],
|
|
env: {
|
|
APPLE_API_ISSUER: 'issuer',
|
|
APPLE_API_KEY: '/tmp/AuthKey.p8',
|
|
APPLE_API_KEY_ID: 'KEYID',
|
|
...appleIdMacReleaseEnv
|
|
}
|
|
},
|
|
{
|
|
credentials: ['--keychain', '/tmp/login.keychain-db', '--keychain-profile', 'orca-release'],
|
|
env: {
|
|
APPLE_KEYCHAIN: '/tmp/login.keychain-db',
|
|
APPLE_KEYCHAIN_PROFILE: 'orca-release',
|
|
ORCA_MAC_RELEASE: '1'
|
|
}
|
|
}
|
|
]
|
|
|
|
for (const { credentials, env } of cases) {
|
|
await withElectronBuilderConfigEnv(env, async (config) => {
|
|
const appOutDir = join(tmpdir(), 'orca-electron-builder-test')
|
|
const dylibPaths = macServeSimDylibPaths(appOutDir)
|
|
const host = createMacNotarizationHost({ existingPaths: new Set(dylibPaths) })
|
|
|
|
await runMacAfterSignWithHost(config, appOutDir, host)
|
|
expectNotaryCredentials(host.calls, credentials)
|
|
})
|
|
}
|
|
})
|
|
|
|
it('fails macOS standalone notarization on partial credentials and bad notary output', async () => {
|
|
for (const { env, message } of [
|
|
{
|
|
env: { APPLE_ID: 'release@example.com', ORCA_MAC_RELEASE: '1' },
|
|
message: 'APPLE_APP_SPECIFIC_PASSWORD env var needs to be set'
|
|
},
|
|
{
|
|
env: { APPLE_API_KEY: '/tmp/AuthKey.p8', APPLE_API_KEY_ID: 'KEYID', ORCA_MAC_RELEASE: '1' },
|
|
message: 'Env vars APPLE_API_KEY, APPLE_API_KEY_ID and APPLE_API_ISSUER need to be set'
|
|
}
|
|
]) {
|
|
await withElectronBuilderConfigEnv(env, async (config) => {
|
|
expect(() => config.__test.macNotarytoolCredentialArgs()).toThrow(message)
|
|
})
|
|
}
|
|
|
|
for (const { output, message } of [
|
|
{ message: 'Standalone binary notarization failed', output: { status: 'Invalid' } },
|
|
{ message: '', output: '{not-json' }
|
|
]) {
|
|
await withElectronBuilderConfigEnv(appleIdMacReleaseEnv, async (config) => {
|
|
const appOutDir = join(tmpdir(), 'orca-electron-builder-test')
|
|
const dylibPaths = macServeSimDylibPaths(appOutDir)
|
|
const host = createMacNotarizationHost({ existingPaths: new Set(dylibPaths), output })
|
|
|
|
const expectation = expect(
|
|
config.__test.notarizeMacServeSimCameraDylibsWithHost(
|
|
createMacNotarizationContext(appOutDir),
|
|
host
|
|
)
|
|
).rejects
|
|
await (message ? expectation.toThrow(message) : expectation.toThrow())
|
|
})
|
|
}
|
|
})
|
|
|
|
it('surfaces macOS standalone notarization subprocess failures without secrets', async () => {
|
|
await withElectronBuilderConfigEnv(appleIdMacReleaseEnv, async (config) => {
|
|
const appOutDir = join(tmpdir(), 'orca-electron-builder-test')
|
|
const dylibPaths = macServeSimDylibPaths(appOutDir)
|
|
const host = createMacNotarizationHost({ existingPaths: new Set(dylibPaths) })
|
|
host.execFileSync.mockImplementation((command, args, options = {}) => {
|
|
host.calls.push({ args, command, options })
|
|
if (command === 'xcrun') {
|
|
throw new Error(`Command failed: xcrun ${args.join(' ')}`)
|
|
}
|
|
return ''
|
|
})
|
|
|
|
try {
|
|
await config.__test.notarizeMacServeSimCameraDylibsWithHost(
|
|
createMacNotarizationContext(appOutDir),
|
|
host
|
|
)
|
|
throw new Error('Expected notarization to fail')
|
|
} catch (error) {
|
|
expect(error.message).toContain('Standalone binary notarization subprocess failed')
|
|
expect(error.message).not.toMatch(/release@example\.com|app-password|TEAMID/)
|
|
expect(error.message).toContain('[REDACTED]')
|
|
}
|
|
expect(host.rmSync).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
it('unpacks the compiled CommonJS boundary with CLI runtime files', () => {
|
|
expect(electronBuilderConfig.asarUnpack).toEqual(
|
|
expect.arrayContaining(['out/package.json', 'out/cli/**', 'out/shared/**'])
|
|
)
|
|
})
|
|
|
|
it('uses the multi-size icon source for Linux packages', () => {
|
|
expect(electronBuilderConfig.linux.icon).toBe('resources/build/icon.icns')
|
|
})
|
|
|
|
it('matches the Linux desktop entry to Electron window class', () => {
|
|
expect(electronBuilderConfig.linux.desktop.entry.StartupWMClass).toBe('orca')
|
|
})
|
|
|
|
it('uses AppImage and deb as local Linux targets without changing existing artifact names', () => {
|
|
expect(electronBuilderConfig.linux.target).toEqual(['AppImage', 'deb'])
|
|
expect(electronBuilderConfig.appImage.artifactName).toBe('orca-linux.${ext}')
|
|
expect(electronBuilderConfig.deb.artifactName).toBe('orca-ide_${version}_${arch}.${ext}')
|
|
expect(electronBuilderConfig.rpm).toMatchObject({
|
|
packageName: 'orca-ide',
|
|
artifactName: 'orca-ide-${version}.${arch}.${ext}'
|
|
})
|
|
})
|
|
|
|
it('uses a distinct AppImage name for Linux arm64 release uploads', () => {
|
|
const configPath = require.resolve('../electron-builder.config.cjs')
|
|
const original = process.env.ORCA_LINUX_ARM64_RELEASE
|
|
try {
|
|
delete require.cache[configPath]
|
|
process.env.ORCA_LINUX_ARM64_RELEASE = '1'
|
|
expect(require('../electron-builder.config.cjs').appImage.artifactName).toBe(
|
|
'orca-linux-arm64.${ext}'
|
|
)
|
|
} finally {
|
|
if (original === undefined) {
|
|
delete process.env.ORCA_LINUX_ARM64_RELEASE
|
|
} else {
|
|
process.env.ORCA_LINUX_ARM64_RELEASE = original
|
|
}
|
|
delete require.cache[configPath]
|
|
require('../electron-builder.config.cjs')
|
|
}
|
|
})
|
|
|
|
it('uses Orca native rebuild hook instead of electron-builder default rebuild', () => {
|
|
expect(electronBuilderConfig.beforeBuild).toBe(electronBuilderNativeRebuild)
|
|
expect(electronBuilderConfig.npmRebuild).toBe(true)
|
|
})
|
|
|
|
it('verifies packaged main runtime deps from Windows-style asar entries', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-runtime-deps-'))
|
|
try {
|
|
await writeFile(join(resourcesDir, 'app.asar'), '', 'utf8')
|
|
await mkdir(join(resourcesDir, 'node_modules', 'yaml'), { recursive: true })
|
|
await mkdir(join(resourcesDir, 'node_modules', 'zod'), { recursive: true })
|
|
|
|
const sources = new Map([
|
|
['out\\main\\index.js', 'const z = require("zod")'],
|
|
['out\\main\\agent-hooks\\managed-agent-hook-controls.js', 'const YAML = require("yaml")']
|
|
])
|
|
const asar = {
|
|
listPackage: () => [...sources.keys()].map((entry) => `\\${entry}`),
|
|
extractFile: (_asarPath, internalPath) => Buffer.from(sources.get(internalPath), 'utf8')
|
|
}
|
|
|
|
expect(() => verifyPackagedMainRuntimeDeps(resourcesDir, asar)).not.toThrow()
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('normalizes host-specific asar entry separators', () => {
|
|
expect(findAsarEntry(['\\out\\main\\index.js'], 'out/main/index.js')).toBe(
|
|
'\\out\\main\\index.js'
|
|
)
|
|
expect(findAsarEntry(['/out/main/index.js'], 'out/main/index.js')).toBe('/out/main/index.js')
|
|
})
|
|
|
|
it('prunes non-target node-pty prebuilds from packaged runtime resources', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-node-pty-prune-'))
|
|
try {
|
|
const prebuildsDir = join(resourcesDir, 'node_modules', 'node-pty', 'prebuilds')
|
|
await mkdir(join(prebuildsDir, 'darwin-arm64'), { recursive: true })
|
|
await mkdir(join(prebuildsDir, 'darwin-x64'), { recursive: true })
|
|
await mkdir(join(prebuildsDir, 'linux-x64'), { recursive: true })
|
|
await mkdir(join(prebuildsDir, 'win32-x64'), { recursive: true })
|
|
await mkdir(join(resourcesDir, 'node_modules', 'node-pty', 'third_party', 'conpty'), {
|
|
recursive: true
|
|
})
|
|
await mkdir(join(resourcesDir, 'node_modules', 'node-pty', 'deps', 'winpty'), {
|
|
recursive: true
|
|
})
|
|
|
|
prunePackagedNodePty(resourcesDir, 'darwin')
|
|
|
|
await expect(readdir(prebuildsDir).then((entries) => entries.sort())).resolves.toEqual([
|
|
'darwin-arm64',
|
|
'darwin-x64'
|
|
])
|
|
await expect(
|
|
readdir(join(resourcesDir, 'node_modules', 'node-pty', 'third_party'))
|
|
).resolves.toEqual([])
|
|
await expect(
|
|
readdir(join(resourcesDir, 'node_modules', 'node-pty', 'deps'))
|
|
).resolves.toEqual([])
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('copies the Windows node-pty ConPTY runtime beside the rebuilt addon', async () => {
|
|
for (const arch of ['x64', 'arm64']) {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), `orca-node-pty-conpty-${arch}-`))
|
|
try {
|
|
const nodePtyDir = join(resourcesDir, 'node_modules', 'node-pty')
|
|
const releaseDir = join(nodePtyDir, 'build', 'Release')
|
|
const conptyRoot = join(nodePtyDir, 'third_party', 'conpty', '0.1.0')
|
|
await mkdir(releaseDir, { recursive: true })
|
|
await writeFile(join(releaseDir, 'conpty.node'), 'native addon placeholder', 'utf8')
|
|
for (const sourceArch of ['x64', 'arm64']) {
|
|
const sourceDir = join(conptyRoot, `win10-${sourceArch}`)
|
|
await mkdir(sourceDir, { recursive: true })
|
|
await writeFile(join(sourceDir, 'conpty.dll'), `dll payload ${sourceArch}`, 'utf8')
|
|
await writeFile(
|
|
join(sourceDir, 'OpenConsole.exe'),
|
|
`console payload ${sourceArch}`,
|
|
'utf8'
|
|
)
|
|
}
|
|
|
|
prunePackagedNodePty(resourcesDir, 'win32', arch)
|
|
|
|
await expect(readFile(join(releaseDir, 'conpty', 'conpty.dll'), 'utf8')).resolves.toBe(
|
|
`dll payload ${arch}`
|
|
)
|
|
await expect(readFile(join(releaseDir, 'conpty', 'OpenConsole.exe'), 'utf8')).resolves.toBe(
|
|
`console payload ${arch}`
|
|
)
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
}
|
|
})
|
|
|
|
it('includes @parcel/watcher in the packaged runtime closure', () => {
|
|
// Why: the main process imports '@parcel/watcher' for filesystem change
|
|
// events; if it is absent from the packaged closure the serve host silently
|
|
// stops propagating file changes to clients (regression guard for #4851).
|
|
const packaged = createPackagedRuntimeNodeModuleResources()
|
|
const packagedTargets = packaged.map((resource) => resource.to)
|
|
expect(packagedTargets).toContain(join('node_modules', '@parcel', 'watcher'))
|
|
expect(
|
|
packagedTargets.some((target) =>
|
|
target.startsWith(join('node_modules', '@parcel', 'watcher-'))
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('prunes non-target @parcel/watcher platform subpackages from packaged runtime resources', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-parcel-watcher-prune-'))
|
|
try {
|
|
const parcelDir = join(resourcesDir, 'node_modules', '@parcel')
|
|
await mkdir(join(parcelDir, 'watcher'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-darwin-arm64'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-darwin-x64'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-linux-x64-glibc'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-linux-arm64-glibc'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-win32-x64'), { recursive: true })
|
|
|
|
prunePackagedParcelWatcher(resourcesDir, 'linux')
|
|
|
|
await expect(readdir(parcelDir).then((entries) => entries.sort())).resolves.toEqual([
|
|
'watcher',
|
|
'watcher-linux-arm64-glibc',
|
|
'watcher-linux-x64-glibc'
|
|
])
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('leaves unrelated @parcel/* runtime deps untouched when pruning the watcher', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-parcel-watcher-prune-unrelated-'))
|
|
try {
|
|
const parcelDir = join(resourcesDir, 'node_modules', '@parcel')
|
|
await mkdir(join(parcelDir, 'watcher'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-darwin-arm64'), { recursive: true })
|
|
await mkdir(join(parcelDir, 'watcher-linux-x64-glibc'), { recursive: true })
|
|
// A hypothetical future @parcel/* runtime dep that is NOT a watcher subpackage.
|
|
await mkdir(join(parcelDir, 'transformer-js'), { recursive: true })
|
|
|
|
prunePackagedParcelWatcher(resourcesDir, 'linux')
|
|
|
|
await expect(readdir(parcelDir).then((entries) => entries.sort())).resolves.toEqual([
|
|
'transformer-js',
|
|
'watcher',
|
|
'watcher-linux-x64-glibc'
|
|
])
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('prunes type declaration artifacts from packaged runtime node_modules', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-runtime-type-prune-'))
|
|
try {
|
|
const packageDir = join(resourcesDir, 'node_modules', 'example-package')
|
|
await mkdir(join(packageDir, 'dist'), { recursive: true })
|
|
await writeFile(join(packageDir, 'dist', 'index.cjs'), 'module.exports = {}', 'utf8')
|
|
await writeFile(join(packageDir, 'dist', 'index.d.ts'), 'export type Value = string', 'utf8')
|
|
await writeFile(join(packageDir, 'dist', 'index.d.cts'), 'export type Value = string', 'utf8')
|
|
await writeFile(join(packageDir, 'dist', 'index.d.mts.map'), '{}', 'utf8')
|
|
|
|
prunePackagedRuntimeTypeDeclarations(resourcesDir)
|
|
|
|
await expect(readdir(join(packageDir, 'dist'))).resolves.toEqual(['index.cjs'])
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('prunes duplicate darwin sherpa-onnx runtime dylib aliases', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-sherpa-prune-'))
|
|
try {
|
|
const packageDir = join(resourcesDir, 'node_modules', 'sherpa-onnx-darwin-arm64')
|
|
await mkdir(packageDir, { recursive: true })
|
|
await writeFile(join(packageDir, 'sherpa-onnx.node'), '', 'utf8')
|
|
await writeFile(join(packageDir, 'libonnxruntime.1.23.2.dylib'), '', 'utf8')
|
|
await writeFile(join(packageDir, 'libonnxruntime.dylib'), '', 'utf8')
|
|
|
|
prunePackagedSherpaOnnx(resourcesDir, 'darwin')
|
|
|
|
await expect(readdir(packageDir).then((entries) => entries.sort())).resolves.toEqual([
|
|
'libonnxruntime.1.23.2.dylib',
|
|
'sherpa-onnx.node'
|
|
])
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('prunes zod TypeScript sources from packaged runtime resources', async () => {
|
|
const resourcesDir = await mkdtemp(join(tmpdir(), 'orca-zod-prune-'))
|
|
try {
|
|
const packageDir = join(resourcesDir, 'node_modules', 'zod')
|
|
await mkdir(join(packageDir, 'src'), { recursive: true })
|
|
await writeFile(join(packageDir, 'index.cjs'), 'module.exports = {}', 'utf8')
|
|
await writeFile(join(packageDir, 'src', 'index.ts'), 'export const value = true', 'utf8')
|
|
|
|
prunePackagedZodSources(resourcesDir)
|
|
|
|
await expect(readdir(packageDir)).resolves.toEqual(['index.cjs'])
|
|
} finally {
|
|
await rm(resourcesDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'marks packaged Unix CLI launchers executable',
|
|
async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-electron-builder-config-'))
|
|
try {
|
|
const resourcesDir = join(root, 'linux-unpacked', 'resources')
|
|
const launcherPath = join(resourcesDir, 'bin', 'orca-ide')
|
|
await mkdir(join(resourcesDir, 'bin'), { recursive: true })
|
|
await mkdir(join(resourcesDir, 'node_modules', 'zod', 'src'), { recursive: true })
|
|
await writeFile(launcherPath, '#!/usr/bin/env bash\n', { encoding: 'utf8', mode: 0o644 })
|
|
|
|
await electronBuilderConfig.afterPack({
|
|
appOutDir: join(root, 'linux-unpacked'),
|
|
electronPlatformName: 'linux'
|
|
})
|
|
|
|
expect((await stat(launcherPath)).mode & 0o111).not.toBe(0)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
}
|
|
)
|
|
})
|