|
|
@@ -3,12 +3,12 @@
|
|
|
* a child process, and the per-profile cache.
|
|
|
*/
|
|
|
|
|
|
-import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
|
|
|
+import { mkdirSync, mkdtempSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
|
import { tmpdir } from 'node:os'
|
|
|
import { join } from 'node:path'
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
import { PLUGIN_PROBE_DIR, PLUGIN_PROBE_FORMAT, probePackage, readProbeCache, writeProbeCache, type PluginProbe } from '../src/index.ts'
|
|
|
-import { parseProbeRecord } from '../src/probe.ts'
|
|
|
+import { cordisPackageDir, parseProbeRecord } from '../src/probe.ts'
|
|
|
import { parseChildReport, type ChildReport } from '../src/probe-report.ts'
|
|
|
|
|
|
const NAME = 'dsh-test-bin'
|
|
|
@@ -141,6 +141,46 @@ describe('probePackage', () => {
|
|
|
expect(own.reason).toContain('own copy of @deepseek-ai/cordis')
|
|
|
})
|
|
|
|
|
|
+ it('recognizes the harness\'s cordis through a link and through a packaged executable\'s proxy', async () => {
|
|
|
+ const harnessCordis = cordisPackageDir(import.meta.resolve('@deepseek-ai/cordis')) as string
|
|
|
+ const harnessEntry = import.meta.resolve('@deepseek-ai/cordis')
|
|
|
+ const { profileDir, installAnchor } = stage({
|
|
|
+ 'linked': { main: 'export function apply() {}\n', manifest: { peerDependencies: { '@deepseek-ai/cordis': '*' } } },
|
|
|
+ // A cordis directory whose manifest names no package: an unknown copy, not a second one.
|
|
|
+ 'unnamed': {
|
|
|
+ main: 'export function apply() {}\n',
|
|
|
+ manifest: { peerDependencies: { '@deepseek-ai/cordis': '*' } },
|
|
|
+ files: {
|
|
|
+ 'node_modules/@deepseek-ai/cordis/package.json': JSON.stringify({ version: '0.0.0', type: 'module', main: './index.js' }),
|
|
|
+ 'node_modules/@deepseek-ai/cordis/index.js': 'export const Context = class {}\n',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ 'proxied': {
|
|
|
+ main: 'export function apply() {}\n',
|
|
|
+ manifest: { peerDependencies: { '@deepseek-ai/cordis': '*' } },
|
|
|
+ files: {
|
|
|
+ // The proxy a packaged executable writes: a manifest naming its targets, and an entry that re-exports one.
|
|
|
+ 'node_modules/@deepseek-ai/cordis/package.json': JSON.stringify({
|
|
|
+ name: '@deepseek-ai/cordis', version: '0.0.0', type: 'module', exports: { '.': './entry-0.js' },
|
|
|
+ dsh: { moduleFallback: { targets: { '.': harnessEntry } } },
|
|
|
+ }),
|
|
|
+ 'node_modules/@deepseek-ai/cordis/entry-0.js': `export * from ${JSON.stringify(harnessEntry)}\n`,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })
|
|
|
+ // The profile links the harness's own cordis, as plain Node installs do.
|
|
|
+ mkdirSync(join(profileDir, 'node_modules', '@deepseek-ai'), { recursive: true })
|
|
|
+ symlinkSync(harnessCordis, join(profileDir, 'node_modules', '@deepseek-ai', 'cordis'), 'dir')
|
|
|
+ const linked = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'linked' })
|
|
|
+ expect(linked).toMatchObject({ kind: 'plugin', ok: true, cordisSameCopy: true })
|
|
|
+ const proxied = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'proxied' })
|
|
|
+ expect(proxied).toMatchObject({ kind: 'plugin', ok: true, cordisSameCopy: true })
|
|
|
+ const unnamed = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'unnamed' })
|
|
|
+ expect(unnamed).toMatchObject({ kind: 'plugin', ok: true, cordisSameCopy: null })
|
|
|
+ // A URL no cordis manifest encloses is an unknown copy, not a different one.
|
|
|
+ expect(cordisPackageDir(`file://${tmpdir()}/nowhere/index.js`)).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
it('probes a package with the minimal manifest and no main export', async () => {
|
|
|
const { profileDir, installAnchor } = stage({
|
|
|
'minimal': { manifest: { version: undefined } },
|
|
|
@@ -175,6 +215,38 @@ describe('probePackage', () => {
|
|
|
expect(chatty).toMatchObject({ kind: 'plugin', ok: true })
|
|
|
const lingers = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'lingers', timeoutMs: 5_000 })
|
|
|
expect(lingers).toMatchObject({ kind: 'plugin', ok: true })
|
|
|
+ // The probe settled on the child's close: nothing of the killed child is still open.
|
|
|
+ expect(process.getActiveResourcesInfo()).not.toContain('ChildProcess')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('takes only the message that echoes its token, hides credentials from the child, and keeps a stderr tail', async () => {
|
|
|
+ const { profileDir, installAnchor } = stage({
|
|
|
+ // A report forged at import, complete with the token's name: the token is
|
|
|
+ // gone from the environment and `process.send` from `process` by then.
|
|
|
+ 'forges': {
|
|
|
+ main: 'process.send?.({ token: process.env.DSH_PROBE_REPORT ?? "", cordis: null, main: { ok: true, isPlugin: true, configSchema: null }, addable: {} })\n'
|
|
|
+ + 'export const notAPlugin = 1\n',
|
|
|
+ manifest: { dsh: { title: 'Forges' } },
|
|
|
+ },
|
|
|
+ 'peeks': {
|
|
|
+ main: 'throw new Error("env=" + Object.keys(process.env).filter(k => k.startsWith("PROBE_TEST") || k === "DSH_PROBE_REPORT").sort().join(","))\n',
|
|
|
+ },
|
|
|
+ 'floods': { main: 'import { writeSync } from "node:fs"\nwriteSync(2, "x".repeat(200_000))\nwriteSync(2, "tail-marker")\nprocess.exit(3)\n' },
|
|
|
+ })
|
|
|
+ const forged = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'forges' })
|
|
|
+ expect(forged).toMatchObject({ kind: 'library', ok: true })
|
|
|
+ process.env.PROBE_TEST_SECRET = 'hidden'
|
|
|
+ process.env.PROBE_TEST_PLAIN = 'visible'
|
|
|
+ try {
|
|
|
+ const peeked = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'peeks' })
|
|
|
+ expect(peeked.reason).toContain('env=PROBE_TEST_PLAIN')
|
|
|
+ } finally {
|
|
|
+ delete process.env.PROBE_TEST_SECRET
|
|
|
+ delete process.env.PROBE_TEST_PLAIN
|
|
|
+ }
|
|
|
+ const flood = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'floods' }).then(() => undefined, (error: unknown) => error as Error)
|
|
|
+ expect(flood?.message).toMatch(/exited with 3 without a report: x+tail-marker$/)
|
|
|
+ expect(flood?.message.length).toBeLessThan(17_000)
|
|
|
})
|
|
|
|
|
|
it('kills a child that never reports, rejects an unrecognized report, and refuses an unresolvable package', async () => {
|
|
|
@@ -182,14 +254,15 @@ describe('probePackage', () => {
|
|
|
// Blocks the child's thread inside the import: an unsettled top-level await would make Node exit instead.
|
|
|
'hangs': { main: 'Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0)\nexport function apply() {}\n' },
|
|
|
'exits': { main: 'process.stderr.write("refusing to report"); process.exit(3)\n' },
|
|
|
- 'spoofs': { main: 'process.send({ nope: true }); process.exit(0)\n' },
|
|
|
+ // `process.send` is gone by the time the package runs; a message it could send would not carry the token anyway.
|
|
|
+ 'spoofs': { main: 'process.send?.({ nope: true }); process.exit(0)\n' },
|
|
|
})
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'hangs', timeoutMs: 300 }))
|
|
|
.rejects.toThrow(/timed out after 300ms/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'exits' }))
|
|
|
.rejects.toThrow(/exited with 3 without a report: refusing to report/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'spoofs' }))
|
|
|
- .rejects.toThrow(/reported an unrecognized value/)
|
|
|
+ .rejects.toThrow(/exited with 0 without a report/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'ghost' }))
|
|
|
.rejects.toThrow(/cannot resolve profile bundle "ghost"/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'exits', nodeExecutable: '/no/such/node' }))
|
|
|
@@ -248,11 +321,11 @@ describe('probe cache', () => {
|
|
|
describe('parseChildReport', () => {
|
|
|
it('accepts the child\'s message only with every field in place', () => {
|
|
|
const inspection = { ok: true, isPlugin: true, configSchema: null }
|
|
|
- const report: ChildReport = { cordis: null, main: inspection, addable: { 'pkg/x': { ...inspection, ok: false, error: 'boom' } } }
|
|
|
+ const report: ChildReport = { token: 't', cordis: null, main: inspection, addable: { 'pkg/x': { ...inspection, ok: false, error: 'boom' } } }
|
|
|
expect(parseChildReport(report)).toBe(report)
|
|
|
expect(parseChildReport({ ...report, cordis: 'file:///cordis/index.js' })).toBeDefined()
|
|
|
const broken: Record<string, unknown>[] = [
|
|
|
- { cordis: 1 }, { main: undefined }, { main: { ...inspection, ok: 'yes' } }, { main: { ...inspection, isPlugin: 'no' } },
|
|
|
+ { token: undefined }, { token: 1 }, { cordis: 1 }, { main: undefined }, { main: { ...inspection, ok: 'yes' } }, { main: { ...inspection, isPlugin: 'no' } },
|
|
|
{ main: { ok: true, isPlugin: true } }, { main: { ...inspection, error: 1 } }, { addable: [] }, { addable: { 'pkg/x': 1 } },
|
|
|
]
|
|
|
for (const fields of broken) expect(parseChildReport({ ...report, ...fields }), JSON.stringify(fields)).toBeUndefined()
|