build-approval.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344
  1. /** Approve pnpm's pending dependency scripts in the current profile's workspace settings. */
  2. import { readFile } from 'node:fs/promises'
  3. import { join } from 'node:path'
  4. import { isMap, isScalar, parseDocument } from 'yaml'
  5. import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
  6. async function readPolicy(dir: string) {
  7. let text: string
  8. try { text = await readFile(join(dir, 'pnpm-workspace.yaml'), 'utf8') }
  9. catch (error) {
  10. if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
  11. text = '{}\n'
  12. }
  13. const document = parseDocument(text)
  14. if (document.errors[0] !== undefined) throw document.errors[0]
  15. if (!isMap(document.contents)) throw new Error('pnpm-workspace.yaml must be a YAML mapping')
  16. const builds = document.get('allowBuilds')
  17. if (builds !== undefined && !isMap(builds)) throw new Error('allowBuilds must be a YAML mapping')
  18. const pending = isMap(builds) ? builds.items.flatMap(({ key, value }) =>
  19. isScalar(key) && typeof key.value === 'string' && !/[*?]/.test(key.value)
  20. && isScalar(value) && value.value === 'set this to true or false' ? [key.value] : []) : []
  21. return { document, pending }
  22. }
  23. /** Read package names left undecided by pnpm 11, including after installation cleanup.
  24. * @param dir Current profile directory.
  25. * @returns Exact package names awaiting a build decision; wildcard rules are excluded.
  26. */
  27. export async function readPendingBuilds(dir: string): Promise<string[]> {
  28. return (await readPolicy(dir)).pending
  29. }
  30. /** Persist approval without running scripts; the caller holds the profile manifest lock.
  31. * @param dir Current profile directory.
  32. * @param names Explicit package names from the pending build list.
  33. * @throws If any requested name is no longer pending; no approvals are written.
  34. */
  35. export async function approveBuilds(dir: string, names: readonly string[]): Promise<void> {
  36. const { document, pending } = await readPolicy(dir)
  37. if (names.some(name => !pending.includes(name))) throw new Error('Build approval changed; retry installation to refresh the pending packages')
  38. if (names.length === 0) return
  39. for (const name of names) document.setIn(['allowBuilds', name], true)
  40. await writeFileAtomic(join(dir, 'pnpm-workspace.yaml'), String(document), { mode: 0o600 })
  41. }