github-repository-plugin.built.e2e.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { fileURLToPath } from 'node:url'
  5. import { startMockLlmServer } from '@deepseek-ai/dsh-llm-mock-server'
  6. import { execa } from 'execa'
  7. import { describe, expect, it } from 'vitest'
  8. const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
  9. const dshBin = join(repoRoot, 'apps/cli/lib/bin.js')
  10. const source = process.env.DSH_GITHUB_REPOSITORY_PLUGIN_SOURCE
  11. const required = process.env.DSH_REQUIRE_GITHUB_REPOSITORY_PLUGIN_E2E === '1'
  12. const enabled = required || source !== undefined
  13. describe.skipIf(!enabled)('dsh run GitHub repository Plugin installation', () => {
  14. it('installs a private exact GitHub source and exposes its skill to the model', async () => {
  15. expect(existsSync(dshBin), 'the repository Plugin acceptance must run the built dsh entry').toBe(true)
  16. expect(source, 'DSH_GITHUB_REPOSITORY_PLUGIN_SOURCE is required by this CI lane').toMatch(
  17. /^github:[^/\s#&]+\/[^/\s#&]+#[0-9a-f]{40}&path:\/.*\/\.dsh-plugin$/u,
  18. )
  19. const apiKey = 'github-repository-plugin-e2e-key'
  20. const server = await startMockLlmServer({
  21. sequence: ['success'],
  22. apiKey,
  23. successText: 'private GitHub repository Plugin reached dsh run',
  24. })
  25. const home = mkdtempSync(join(tmpdir(), 'dsh-github-repository-plugin-'))
  26. const patch = join(home, 'github-repository-plugin.cordis.patch.yml')
  27. writeFileSync(patch, [
  28. '- id: repository-plugins',
  29. ' config:',
  30. ' repositories:',
  31. ` - ${JSON.stringify(source)}`,
  32. '',
  33. ].join('\n'))
  34. try {
  35. const result = await execa(process.execPath, [
  36. dshBin,
  37. 'run',
  38. '--patch',
  39. patch,
  40. 'prove the private GitHub repository Plugin is active',
  41. ], {
  42. cwd: repoRoot,
  43. input: '',
  44. timeout: 120_000,
  45. killSignal: 'SIGKILL',
  46. reject: false,
  47. env: {
  48. ...process.env,
  49. DSH_HOME: home,
  50. DSH_TELEMETRY_DISABLED: '1',
  51. DEEPSEEK_API_KEY: apiKey,
  52. DEEPSEEK_BASE_URL: server.baseURL,
  53. },
  54. })
  55. if (result.timedOut) {
  56. throw new Error(`dsh GitHub repository Plugin run did not exit within 120s. stdout:\n${result.stdout}\nstderr:\n${result.stderr}`)
  57. }
  58. expect(result.exitCode, `${result.stderr}\nstdout:\n${result.stdout}`).toBe(0)
  59. expect(result.stdout).toBe('private GitHub repository Plugin reached dsh run')
  60. expect(server.requests.length).toBeGreaterThan(0)
  61. expect(JSON.stringify(server.requests.map(request => request.body))).toContain(
  62. 'Proves that dsh installed a private repository Plugin from an exact GitHub source.',
  63. )
  64. const cacheRoot = join(home, 'cache', 'repository-plugins')
  65. const generations = readdirSync(cacheRoot, { withFileTypes: true }).filter(entry => entry.isDirectory())
  66. expect(generations).toHaveLength(1)
  67. const installed = join(cacheRoot, generations[0]!.name, 'node_modules', 'repository')
  68. const manifest = JSON.parse(readFileSync(join(installed, 'package.json'), 'utf8')) as Record<string, unknown>
  69. expect(manifest).toMatchObject({
  70. name: 'dsh-github-repository-plugin-e2e-fixture',
  71. private: true,
  72. scripts: { prepack: 'dsh-plugin-prepare' },
  73. })
  74. expect(manifest).not.toHaveProperty('dependencies')
  75. expect(manifest).not.toHaveProperty('devDependencies')
  76. expect(readFileSync(join(installed, 'dsh-plugin-assets/skills/0/github-source-proof/SKILL.md'), 'utf8'))
  77. .toContain('This skill exists only in the GitHub repository source fixture.')
  78. expect(readFileSync(join(installed, 'dsh-plugin.mjs'), 'utf8')).toContain('dsh-repository-plugin')
  79. } finally {
  80. await server.close()
  81. rmSync(home, { recursive: true, force: true })
  82. }
  83. }, 130_000)
  84. })