smoke-runtime.ts 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. /** Boot the materialized target runtime without access to a user's Harness profile. */
  2. import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { DesktopHostProcess } from '../src/host-process.ts'
  6. import { createPluginProfile } from '../src/project-manager.ts'
  7. import type { DesktopRuntimeDescriptor } from '../src/runtime-tree.ts'
  8. /**
  9. * Prove the final resource tree boots and serves its matching Web frontend.
  10. * @param root - Materialized dsh resources.
  11. * @param node - Prepared target Electron executable.
  12. * @param runtime - Verified resource descriptor.
  13. */
  14. export async function smokeDesktopRuntime(root: string, node: string, runtime: DesktopRuntimeDescriptor): Promise<void> {
  15. const home = mkdtempSync(join(tmpdir(), 'dsh-desktop-smoke-'))
  16. const profile = join(home, 'profiles', 'desktop')
  17. const host = new DesktopHostProcess(node, root, profile, undefined, { ...process.env, DSH_HOME: home })
  18. try {
  19. createPluginProfile(profile)
  20. const pluginName = 'desktop-runtime-smoke-plugin'
  21. const plugin = join(profile, 'node_modules', pluginName)
  22. mkdirSync(plugin, { recursive: true })
  23. const cordis = runtime.sharedPackages.find(entry => entry.name === '@deepseek-ai/cordis')
  24. if (cordis === undefined) throw new Error('desktop runtime: missing shared Cordis package')
  25. writeFileSync(join(plugin, 'package.json'), JSON.stringify({
  26. name: pluginName, version: '1.0.0', type: 'module', exports: './index.js',
  27. peerDependencies: { '@deepseek-ai/cordis': cordis.version }, dsh: { bundle: { patch: './bundle.yml' } },
  28. }))
  29. writeFileSync(join(plugin, 'index.js'), `
  30. import { Context } from '@deepseek-ai/cordis'
  31. export function apply(ctx) {
  32. if (!(ctx instanceof Context)) throw new Error('desktop runtime: external plugin loaded another Cordis instance')
  33. ctx.effect(() => ctx.webServer.register({ kind: 'exact', path: '/desktop-smoke',
  34. handler(_request, response) { response.end('plugin route ready') } }))
  35. }
  36. `)
  37. writeFileSync(join(plugin, 'bundle.yml'), '- insert:\n - id: desktop-runtime-smoke-plugin\n name: desktop-runtime-smoke-plugin\n inject: [webServer]\n')
  38. const manifest = JSON.parse(readFileSync(join(profile, 'package.json'), 'utf8')) as {
  39. dependencies: Record<string, string>
  40. dsh: { profile: { bundles: string[] } }
  41. }
  42. manifest.dependencies[pluginName] = '1.0.0'
  43. manifest.dsh.profile.bundles.push(pluginName)
  44. writeFileSync(join(profile, 'package.json'), JSON.stringify(manifest))
  45. writeFileSync(join(profile, 'cordis.patch.yml'), '- id: webserver\n config:\n host: 127.0.0.1\n port: 0\n')
  46. const ready = await host.start()
  47. const login = await fetch(ready.url, { redirect: 'manual' })
  48. const cookie = login.headers.getSetCookie().map(value => value.split(';')[0]).join('; ')
  49. const response = await fetch(new URL('/', ready.url), { headers: { cookie } })
  50. if (response.status !== 200 || !(await response.text()).includes('<html')) {
  51. throw new Error('desktop runtime: packaged frontend smoke failed')
  52. }
  53. const pluginResponse = await fetch(new URL('/desktop-smoke', ready.url), { headers: { cookie } })
  54. if (await pluginResponse.text() !== 'plugin route ready') throw new Error('desktop runtime: plugin HTTP route failed')
  55. } finally {
  56. await host.stop()
  57. rmSync(home, { recursive: true, force: true })
  58. }
  59. }