mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* build: share one pnpm virtual store across worktrees, off under CI
Each worktree carried a full node_modules; the global virtual store links
one slot per package version instead. pnpm keeps an explicit setting on
under CI, so a pnpmfile hook turns it off there: CI packages from,
caches and verifies a project-local node_modules.
* fix(build): keep shared electron and windows-process-tree dirs untouched under GVS
Every worktree runs Electron from the one shared electron package, so the
per-repo dist cache must not swap that dist and a foreign-arch install must
not land in it. The relay addon builder now repairs and compiles a private
copy instead of the shared windows-process-tree dir.
* fix(build): accept CLI runtime imports reached through an in-artifact store link
Under the global virtual store node_modules/<pkg> realpaths into a store
outside the repo. The check still rejects anything not reached through a
node_modules link inside the artifact, such as a stray ~/node_modules.
* docs: explain the global virtual store layout and the ~/node_modules hazard
* test: compare CLI closure paths with POSIX separators so the suite passes on Windows
* build: keep Windows on the normal pnpm layout unless ORCA_PNPM_GLOBAL_VIRTUAL_STORE=1
Store links are true symlinks on Windows, and installs without admin or
Developer Mode are unverified; CI still forces the normal layout first.
* fix(build): close the remaining shared-slot gaps under the global virtual store
- verify-skills-cli-runtime resolves each import from the importer's realpath,
as Node does, so an artifact-local copy can't mask a ~/node_modules load.
- A concurrent sibling install of the shared Electron dist is kept, not swapped out.
- rebuild:node goes through ensure-native-runtime, which builds a shared
node-pty beside the slot instead of node-gyp cleaning its build/ dir.
* fix(build): keep pnpm rebuild:node a forced rebuild
It replaced pnpm rebuild node-pty, which always rebuilt; without --force it
only rebuilt a node-pty that failed to load.
* docs: note that the store switches must stay set, since pnpm run relinks
* build: move the pnpmfile under config/ to keep the repo root allowlisted
pnpm-workspace.yaml points pnpm at it; the lockfile checksum is unchanged
because the content is.
* fix(build): enable the global virtual store from the pnpmfile, not the workspace file
pnpm exec derives NODE_PATH from the file setting, so even with the store
forced off under CI every child process got the root node_modules on its
NODE_PATH; a Windows rebuild fixture then resolved the real
@orca/windows-registry it deliberately omits. The hook now sets the store
both ways, and ORCA_PNPM_GLOBAL_VIRTUAL_STORE=0/1 is the one switch.
* test(pty): keep F3's concurrent spawns on the node-pty mock
Two concurrent first import('node-pty') calls race vitest's mock registry
and the loser loads the real addon under the mocked linux platform. It
passed only where a platform-agnostic build/Release happened to exist,
so it failed on every shared-store worktree.
* ci: fail PRs whose packages need a dependency they never declared
A new dependency_isolation job installs with the global virtual store
forced on (ORCA_PNPM_FORCE_GLOBAL_VIRTUAL_STORE_IN_CI), HOME and the store
in an empty temp dir, then runs pnpm tc and the undeclared-dependency test.
Postinstall now warns when a node_modules above the shared store, such as
~/node_modules, would mask the same class of bug locally.
* ci: set the isolation job's HOME and store through GITHUB_ENV
Job-level env cannot read the runner context, which made pr.yml invalid.
286 lines
11 KiB
JavaScript
286 lines
11 KiB
JavaScript
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
|
import { realpathSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, relative } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const {
|
|
collectRuntimeClosure,
|
|
runCli,
|
|
verifySkillsCliRuntime
|
|
} = require('./verify-skills-cli-runtime.cjs')
|
|
|
|
async function writeSkillsCliFixture(outDir, handlerSource) {
|
|
const cliDir = join(outDir, 'cli')
|
|
const handlerDir = join(cliDir, 'handlers')
|
|
await mkdir(handlerDir, { recursive: true })
|
|
await writeFile(join(cliDir, 'index.js'), "require('./handlers/skills')\n", 'utf8')
|
|
await writeFile(join(handlerDir, 'skills.js'), handlerSource, 'utf8')
|
|
}
|
|
|
|
describe('skills CLI runtime closure', () => {
|
|
it('runs after Electron composes the final output', async () => {
|
|
const packageJson = JSON.parse(
|
|
await readFile(new URL('../../package.json', import.meta.url), 'utf8')
|
|
)
|
|
for (const scriptName of ['build:desktop', 'build:desktop:ci', 'build:release']) {
|
|
const script = packageJson.scripts[scriptName].replace(
|
|
'pnpm run build:desktop:assets',
|
|
packageJson.scripts['build:desktop:assets']
|
|
)
|
|
expect(script).toContain('build:electron-vite')
|
|
expect(script).toContain('verify:built-skills-cli')
|
|
expect(script.indexOf('build:electron-vite')).toBeLessThan(
|
|
script.indexOf('verify:built-skills-cli')
|
|
)
|
|
}
|
|
})
|
|
|
|
it('reports the missing final-artifact import and its owner', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
await writeSkillsCliFixture(root, "require('../../main/codex-cli/command')\n")
|
|
|
|
expect(() => collectRuntimeClosure(root)).toThrow(
|
|
/missing runtime import "\.\.\/\.\.\/main\/codex-cli\/command" from cli\/handlers\/skills\.js/
|
|
)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('walks static and dynamic relative imports', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const sharedDir = join(root, 'shared')
|
|
await mkdir(sharedDir, { recursive: true })
|
|
await writeSkillsCliFixture(
|
|
root,
|
|
"require('../../shared/first.js'); import('../../shared/second.js')\n"
|
|
)
|
|
await writeFile(join(sharedDir, 'first.js'), '', 'utf8')
|
|
await writeFile(join(sharedDir, 'second.js'), '', 'utf8')
|
|
|
|
expect(
|
|
collectRuntimeClosure(root)
|
|
.map((file) => relative(realpathSync(root), file).split('\\').join('/'))
|
|
.sort()
|
|
).toEqual(['cli/handlers/skills.js', 'cli/index.js', 'shared/first.js', 'shared/second.js'])
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('ignores import-shaped text in comments and strings', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
await writeSkillsCliFixture(
|
|
root,
|
|
[
|
|
"// require('../../missing-comment.js')",
|
|
'const message = "import(\'../../missing-string.js\')"',
|
|
"const template = `require.resolve('../../missing-template.js')`"
|
|
].join('\n')
|
|
)
|
|
|
|
expect(collectRuntimeClosure(root)).toHaveLength(2)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('can inspect a cross-arch artifact without executing it', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
await writeSkillsCliFixture(root, '')
|
|
|
|
expect(verifySkillsCliRuntime(root, undefined, { executeCommands: false })).toEqual({
|
|
closureFiles: 2,
|
|
commands: 0
|
|
})
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('bounds command execution time', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
await writeSkillsCliFixture(root, 'setInterval(() => {}, 1_000)\n')
|
|
|
|
expect(() => runCli(root, [], 50)).toThrow(/ETIMEDOUT|terminated by SIGKILL/)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('rejects bare imports resolved outside the artifact', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const artifactRoot = join(root, 'artifact')
|
|
const outDir = join(artifactRoot, 'out')
|
|
const externalPackageDir = join(root, 'node_modules', 'external-package')
|
|
await mkdir(externalPackageDir, { recursive: true })
|
|
await writeSkillsCliFixture(outDir, "require('external-package')\n")
|
|
await writeFile(
|
|
join(externalPackageDir, 'package.json'),
|
|
JSON.stringify({ main: 'index.js' }),
|
|
'utf8'
|
|
)
|
|
await writeFile(join(externalPackageDir, 'index.js'), '', 'utf8')
|
|
|
|
expect(() => collectRuntimeClosure(outDir, artifactRoot)).toThrow(
|
|
/external-package.*resolved outside/s
|
|
)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('follows an in-artifact node_modules link into a shared store', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const artifactRoot = join(root, 'artifact')
|
|
const outDir = join(artifactRoot, 'out')
|
|
// pnpm's global virtual store: the artifact links to a slot that lives outside it.
|
|
const slotDir = join(root, 'store', 'linked-package')
|
|
await writeSkillsCliFixture(outDir, "require('linked-package')\n")
|
|
await mkdir(slotDir, { recursive: true })
|
|
await writeFile(join(slotDir, 'package.json'), JSON.stringify({ main: 'index.js' }), 'utf8')
|
|
await writeFile(join(slotDir, 'index.js'), "require('./inner')\n", 'utf8')
|
|
await writeFile(join(slotDir, 'inner.js'), '', 'utf8')
|
|
await mkdir(join(artifactRoot, 'node_modules'), { recursive: true })
|
|
await symlink(slotDir, join(artifactRoot, 'node_modules', 'linked-package'), 'junction')
|
|
|
|
const closure = collectRuntimeClosure(outDir, artifactRoot).map((file) =>
|
|
relative(realpathSync(artifactRoot), file).split('\\').join('/')
|
|
)
|
|
expect(closure).toContain('node_modules/linked-package/inner.js')
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it("resolves a linked package's imports from its store slot, as Node does", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const artifactRoot = join(root, 'artifact')
|
|
const outDir = join(artifactRoot, 'out')
|
|
const slotDir = join(root, 'store', 'linked-package')
|
|
await writeSkillsCliFixture(outDir, "require('linked-package')\n")
|
|
await mkdir(slotDir, { recursive: true })
|
|
await writeFile(join(slotDir, 'package.json'), JSON.stringify({ main: 'index.js' }), 'utf8')
|
|
await writeFile(join(slotDir, 'index.js'), "require('dep')\n", 'utf8')
|
|
// Node reaches the ancestor copy (like ~/node_modules) from the slot, not the artifact's own.
|
|
for (const dir of [
|
|
join(root, 'node_modules', 'dep'),
|
|
join(artifactRoot, 'node_modules', 'dep')
|
|
]) {
|
|
await mkdir(dir, { recursive: true })
|
|
await writeFile(join(dir, 'index.js'), '', 'utf8')
|
|
}
|
|
await symlink(slotDir, join(artifactRoot, 'node_modules', 'linked-package'), 'junction')
|
|
|
|
expect(() => collectRuntimeClosure(outDir, artifactRoot)).toThrow(/resolved outside/)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('rejects package dependencies resolved outside the artifact', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const artifactRoot = join(root, 'artifact')
|
|
const outDir = join(artifactRoot, 'out')
|
|
const packageDir = join(artifactRoot, 'node_modules', 'inside-package')
|
|
const externalPackageDir = join(root, 'node_modules', 'ancestor-dependency')
|
|
await writeSkillsCliFixture(outDir, "require('inside-package')\n")
|
|
await mkdir(packageDir, { recursive: true })
|
|
await mkdir(externalPackageDir, { recursive: true })
|
|
await writeFile(
|
|
join(packageDir, 'package.json'),
|
|
JSON.stringify({ main: 'index.js' }),
|
|
'utf8'
|
|
)
|
|
await writeFile(join(packageDir, 'index.js'), "require('ancestor-dependency')\n", 'utf8')
|
|
await writeFile(
|
|
join(externalPackageDir, 'package.json'),
|
|
JSON.stringify({ main: 'index.js' }),
|
|
'utf8'
|
|
)
|
|
await writeFile(join(externalPackageDir, 'index.js'), '', 'utf8')
|
|
|
|
expect(() => collectRuntimeClosure(outDir, artifactRoot)).toThrow(
|
|
/ancestor-dependency.*resolved outside/s
|
|
)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('allows absent dependencies declared optional by their package', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const artifactRoot = join(root, 'artifact')
|
|
const outDir = join(artifactRoot, 'out')
|
|
const packageDir = join(artifactRoot, 'node_modules', 'inside-package')
|
|
await writeSkillsCliFixture(outDir, "require('inside-package')\n")
|
|
await mkdir(packageDir, { recursive: true })
|
|
await writeFile(
|
|
join(packageDir, 'package.json'),
|
|
JSON.stringify({
|
|
main: 'index.js',
|
|
peerDependencies: { 'optional-native': '*' },
|
|
peerDependenciesMeta: { 'optional-native': { optional: true } }
|
|
}),
|
|
'utf8'
|
|
)
|
|
await writeFile(
|
|
join(packageDir, 'index.js'),
|
|
"try { require('optional-native') } catch {}\n",
|
|
'utf8'
|
|
)
|
|
|
|
expect(collectRuntimeClosure(outDir, artifactRoot)).toHaveLength(3)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('rejects optional dependencies resolved only outside the artifact', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'orca-skills-cli-closure-'))
|
|
try {
|
|
const artifactRoot = join(root, 'artifact')
|
|
const outDir = join(artifactRoot, 'out')
|
|
const packageDir = join(artifactRoot, 'node_modules', 'inside-package')
|
|
const externalPackageDir = join(root, 'node_modules', 'optional-native')
|
|
await writeSkillsCliFixture(outDir, "require('inside-package')\n")
|
|
await mkdir(packageDir, { recursive: true })
|
|
await mkdir(externalPackageDir, { recursive: true })
|
|
await writeFile(
|
|
join(packageDir, 'package.json'),
|
|
JSON.stringify({
|
|
main: 'index.js',
|
|
optionalDependencies: { 'optional-native': '*' }
|
|
}),
|
|
'utf8'
|
|
)
|
|
await writeFile(join(packageDir, 'index.js'), "require('optional-native')\n", 'utf8')
|
|
await writeFile(
|
|
join(externalPackageDir, 'package.json'),
|
|
JSON.stringify({ main: 'index.js' }),
|
|
'utf8'
|
|
)
|
|
await writeFile(join(externalPackageDir, 'index.js'), '', 'utf8')
|
|
|
|
expect(() => collectRuntimeClosure(outDir, artifactRoot)).toThrow(
|
|
/optional-native.*resolved outside/s
|
|
)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
})
|