built-lib.e2e.ts 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import { existsSync } from 'node:fs'
  2. import { mkdtemp, mkdir, rm, writeFile, realpath } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { fileURLToPath, pathToFileURL } from 'node:url'
  6. import { execa } from 'execa'
  7. import { afterAll, beforeAll, describe, expect, it } from 'vitest'
  8. /**
  9. * Keyless built-artifact smoke: plain Node imports `@deepseek-ai/dsh-lsp` and
  10. * `@deepseek-ai/dsh-lsp-stdio` by name through their exports maps, spawns the fixture server, runs
  11. * one query (exercising real `Content-Length` framing over `lib/index.js`), and disposes (exercising
  12. * subprocess cleanup). Unit tests use `src/`; this pins the downstream `lib/` path. Skips when `lib/`
  13. * is absent; CI runs it after the build.
  14. */
  15. const pkgDir = fileURLToPath(new URL('..', import.meta.url))
  16. const seamLib = join(pkgDir, '../lsp/lib/index.js')
  17. const fsLib = join(pkgDir, '../../fs/fs-local/lib/index.js')
  18. const subprocessLib = join(pkgDir, '../../subprocess/subprocess-local/lib/index.js')
  19. const built = existsSync(join(pkgDir, 'lib/index.js')) && existsSync(seamLib) && existsSync(fsLib) && existsSync(subprocessLib)
  20. const fixtureServer = fileURLToPath(new URL('./fixture-server.ts', import.meta.url))
  21. let root: string
  22. let ws: string
  23. beforeAll(async () => {
  24. root = await realpath(await mkdtemp(join(tmpdir(), 'lsp-built-')))
  25. ws = join(root, 'ws')
  26. await mkdir(ws)
  27. await writeFile(join(ws, 'a.ts'), 'const x = 1\n')
  28. })
  29. afterAll(async () => {
  30. if (root) await rm(root, { recursive: true, force: true })
  31. })
  32. describe.skipIf(!built)('built lib real load path (plain node)', () => {
  33. it('runs a query through lib/index.js and disposes cleanly, framing over the base protocol', async () => {
  34. const location = JSON.stringify({ uri: pathToFileURL(join(ws, 'a.ts')).href, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 3 } } })
  35. const script = `
  36. const { Context } = await import('@deepseek-ai/cordis')
  37. const { default: Lsp } = await import('@deepseek-ai/dsh-lsp')
  38. const LspLocal = await import('@deepseek-ai/dsh-lsp-stdio')
  39. const { default: LocalFileSystem } = await import('@deepseek-ai/dsh-fs-local')
  40. const { default: LocalSubprocessRuntime } = await import('@deepseek-ai/dsh-subprocess-local')
  41. const ctx = new Context()
  42. await ctx.plugin(Lsp)
  43. await ctx.plugin(LocalSubprocessRuntime)
  44. await ctx.plugin(LocalFileSystem, { cwd: process.cwd() })
  45. await ctx.plugin(LspLocal, {
  46. servers: {
  47. fake: {
  48. command: ${JSON.stringify(process.execPath)},
  49. args: [${JSON.stringify(fixtureServer)}],
  50. env: { LSP_FAKE_DEF: ${JSON.stringify(location)} },
  51. extensionToLanguage: { '.ts': 'typescript' },
  52. },
  53. },
  54. })
  55. const result = await ctx.lsp.query({ operation: 'goToDefinition', filePath: 'a.ts', position: { line: 0, character: 6 }, workspaceRoot: ${JSON.stringify(ws)} })
  56. console.log(JSON.stringify(result))
  57. await ctx.fiber.dispose()
  58. `
  59. const { exitCode, stdout, stderr } = await execa(process.execPath, ['--input-type=module', '-e', script], {
  60. cwd: pkgDir,
  61. stdin: 'ignore',
  62. timeout: 55_000,
  63. killSignal: 'SIGKILL',
  64. reject: false,
  65. })
  66. expect(exitCode, `stderr:\n${stderr}`).toBe(0)
  67. const lastLine = stdout.trim().split('\n').at(-1) ?? ''
  68. const result = JSON.parse(lastLine) as { kind: string; locations: unknown[] }
  69. expect(result.kind).toBe('locations')
  70. expect(result.locations).toHaveLength(1)
  71. }, 60_000)
  72. })