1
0

built-lib.e2e.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. import { existsSync } from 'node:fs'
  2. import { join } from 'node:path'
  3. import { fileURLToPath } from 'node:url'
  4. import { execa } from 'execa'
  5. import { describe, expect, it } from 'vitest'
  6. /**
  7. * Keyless built-artifact smoke: plain Node imports the package by name through its exports map,
  8. * then exercises type stripping, sibling `process.js` loading, bindings, and logs. Unit tests use
  9. * `src/process.ts`; this pins the downstream `lib/index.js` path. It skips when `lib/` is absent,
  10. * and CI runs it after the build.
  11. */
  12. const pkgDir = fileURLToPath(new URL('..', import.meta.url))
  13. const built = ['lib/index.js', 'lib/process.js'].every(file => existsSync(join(pkgDir, file)))
  14. && existsSync(join(pkgDir, '../code-runtime/lib/index.js'))
  15. describe.skipIf(!built)('built lib real load path (plain node)', () => {
  16. it('runs a TypeScript program with a binding through lib/index.js and its lib/process.js entry', async () => {
  17. const script = `
  18. const { Context } = await import('@deepseek-ai/cordis')
  19. const { NodeCodeRuntime } = await import('@deepseek-ai/dsh-code-runtime-node')
  20. const ctx = new Context()
  21. for (const name of ['session-projection', 'fs-local', 'subprocess-local', 'sandbox-local']) {
  22. const plugin = await import('@deepseek-ai/dsh-' + name)
  23. await ctx.plugin(plugin.default, {})
  24. }
  25. const { default: SandboxPolicy } = await import('@deepseek-ai/dsh-sandbox-policy')
  26. await ctx.plugin(SandboxPolicy, { mode: 'read-only' })
  27. await ctx.plugin(NodeCodeRuntime, {})
  28. const result = await ctx.codeRuntime.run(ctx.codeRuntime.resolve({
  29. program: 'const doubled: number = await tools.double({ n: 21 }); console.log("halfway", doubled); let failure; try { await tools.fail({}) } catch (error) { failure = { typed: error instanceof ToolCallError, name: error.name, toolName: error.toolName, message: error.message } } return { doubled, failure };',
  30. bindings: [{
  31. global: 'tools',
  32. functions: {
  33. double: async args => args.n * 2,
  34. fail: async () => { throw new Error('denied') },
  35. },
  36. errorClass: { name: 'ToolCallError', memberNameProperty: 'toolName' },
  37. }],
  38. }))
  39. await ctx.fiber.dispose()
  40. console.log(JSON.stringify(result))
  41. `
  42. const { exitCode, stdout, stderr } = await execa(process.execPath, ['--input-type=module', '-e', script], {
  43. cwd: pkgDir,
  44. stdin: 'ignore',
  45. timeout: 55_000,
  46. killSignal: 'SIGKILL',
  47. reject: false,
  48. })
  49. expect(exitCode, `stderr:\n${stderr}`).toBe(0)
  50. const lastLine = stdout.trim().split('\n').at(-1) ?? ''
  51. const result = JSON.parse(lastLine) as { value?: unknown; logs: string[]; error?: unknown }
  52. expect(result.error).toBeUndefined()
  53. expect(result.value).toEqual({
  54. doubled: 42,
  55. failure: { typed: true, name: 'ToolCallError', toolName: 'fail', message: 'denied' },
  56. })
  57. expect(result.logs).toContain('halfway 42')
  58. })
  59. })