gen-workflow-guest.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. /** Bundle the workflow guest as source data; --check rejects a stale committed module. */
  2. import { readFile, realpath, writeFile } from 'node:fs/promises'
  3. import { isBuiltin } from 'node:module'
  4. import { resolve } from 'node:path'
  5. import { parseArgs } from 'node:util'
  6. import { Rolldown } from 'tsdown'
  7. const GUEST_ENTRY = 'packages/workflow/workflow-ptc/src/guest.ts'
  8. const GUEST_SOURCE = 'packages/workflow/workflow-ptc/src/guest-source.ts'
  9. /**
  10. * Bundle typed guest sources without depending on workspace build outputs.
  11. * @param root - Repository root containing source aliases and the guest entry.
  12. * @returns One self-contained ESM module with only Node builtin imports.
  13. */
  14. export async function bundleWorkflowGuest(root: string): Promise<string> {
  15. root = await realpath(root)
  16. const bundle = await Rolldown.rolldown({
  17. cwd: root,
  18. input: resolve(root, GUEST_ENTRY),
  19. platform: 'node',
  20. tsconfig: resolve(root, 'tsconfig.base.json'),
  21. external: id => isBuiltin(id),
  22. // The guest imports pure helpers from package barrels, without mounting their services.
  23. treeshake: { moduleSideEffects: false },
  24. onLog(level, log, defaultHandler) {
  25. if (log.code === 'UNRESOLVED_IMPORT') throw new Error(`workflow guest has an unresolved import: ${log.message}`)
  26. defaultHandler(level, log)
  27. },
  28. })
  29. try {
  30. const { output } = await bundle.generate({ format: 'esm', codeSplitting: false, sourcemap: false })
  31. const chunk = output[0]
  32. if (output.length !== 1) {
  33. throw new Error('workflow guest must bundle into exactly one JavaScript module')
  34. }
  35. const external = [...chunk.imports, ...chunk.dynamicImports].filter(id => !isBuiltin(id))
  36. if (external.length > 0) throw new Error(`workflow guest imports files outside its module: ${external.join(', ')}`)
  37. return `${chunk.code.trimEnd()}\n//# sourceURL=dsh-workflow-guest.js\n`
  38. } finally {
  39. await bundle.close()
  40. }
  41. }
  42. /**
  43. * Write the guest-source module or reject a stale committed copy.
  44. * @param root - Repository root whose guest sources are bundled.
  45. * @param check - Verify without writing when true.
  46. */
  47. export async function generateWorkflowGuest(root: string, check: boolean): Promise<void> {
  48. const source = await bundleWorkflowGuest(root)
  49. const generated = [
  50. '/** Generated by scripts/gen-workflow-guest.ts. Do not edit directly. */',
  51. '',
  52. '/** Self-contained ESM guest transported into the mounted PTC runtime. */',
  53. `export const WORKFLOW_GUEST_SOURCE = ${JSON.stringify(source)}`,
  54. '',
  55. ].join('\n')
  56. const path = resolve(root, GUEST_SOURCE)
  57. if (check) {
  58. const current = await readFile(path, 'utf8')
  59. if (current !== generated) throw new Error('workflow guest source is stale; run pnpm gen-workflow-guest')
  60. } else {
  61. await writeFile(path, generated)
  62. }
  63. }
  64. if (import.meta.main) {
  65. const { values } = parseArgs({ options: { check: { type: 'boolean', default: false } } })
  66. await generateWorkflowGuest(resolve(import.meta.dirname, '..'), values.check)
  67. }