build-approval.spec.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. /** Pending script permissions survive cleanup and preserve unrelated workspace settings. */
  2. import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
  3. import { join } from 'node:path'
  4. import { tmpdir } from 'node:os'
  5. import { expect, it, onTestFinished } from 'vitest'
  6. import { parse } from 'yaml'
  7. import { approveBuilds, readPendingBuilds } from '../src/build-approval.ts'
  8. function fixture(text?: string) {
  9. const dir = mkdtempSync(join(tmpdir(), 'build-approval-'))
  10. onTestFinished(() => { rmSync(dir, { recursive: true, force: true }) })
  11. const filename = join(dir, 'pnpm-workspace.yaml')
  12. if (text !== undefined) writeFileSync(filename, text)
  13. return { dir, filename }
  14. }
  15. it('approves only named pending packages and preserves comments, decisions and settings', async () => {
  16. const { dir, filename } = fixture('# profile settings\nother: &unrelated value\ncopy: *unrelated\nnodeLinker: hoisted\nallowBuilds:\n native: set this to true or false\n "@scope/other": set this to true or false\n trusted: true\n denied: false\n "@scope/*": set this to true or false\n')
  17. expect(await readPendingBuilds(dir)).toEqual(['native', '@scope/other'])
  18. await approveBuilds(dir, ['native'])
  19. const text = readFileSync(filename, 'utf8')
  20. expect(text).toContain('# profile settings')
  21. expect(parse(text)).toMatchObject({ nodeLinker: 'hoisted', allowBuilds: { native: true, trusted: true, denied: false } })
  22. expect(await readPendingBuilds(dir)).toEqual(['@scope/other'])
  23. })
  24. it.each(['missing', 'denied', '*', '--all'])('rejects an unlisted approval atomically: %s', async (name) => {
  25. const original = 'allowBuilds:\n native: set this to true or false\n denied: false\n'
  26. const { dir, filename } = fixture(original)
  27. await expect(approveBuilds(dir, ['native', name])).rejects.toThrow('stale-approval')
  28. expect(readFileSync(filename, 'utf8')).toBe(original)
  29. })
  30. it('preserves pnpm file dependency selectors verbatim', async () => {
  31. const name = '@scope/addon@file:../local addon'
  32. const { dir, filename } = fixture(`allowBuilds:\n '${name}': set this to true or false\n`)
  33. expect(await readPendingBuilds(dir)).toEqual([name])
  34. await approveBuilds(dir, [name])
  35. expect(parse(readFileSync(filename, 'utf8'))).toEqual({ allowBuilds: { [name]: true } })
  36. })
  37. it.each([undefined, '{}\n', 'nodeLinker: hoisted\n', 'allowBuilds: {}\n'])('has no pending approval without pnpm placeholders: %s', async (text) => {
  38. const { dir } = fixture(text)
  39. expect(await readPendingBuilds(dir)).toEqual([])
  40. await approveBuilds(dir, [])
  41. })
  42. it.each(['[', '[]\n', 'allowBuilds: false\n'])('rejects malformed workspace settings without rewriting them: %s', async (text) => {
  43. const { dir, filename } = fixture(text)
  44. await expect(readPendingBuilds(dir)).rejects.toThrow()
  45. await expect(approveBuilds(dir, ['native'])).rejects.toThrow()
  46. expect(readFileSync(filename, 'utf8')).toBe(text)
  47. })
  48. it('reports unreadable workspace settings', async () => {
  49. const { dir, filename } = fixture()
  50. mkdirSync(filename)
  51. await expect(readPendingBuilds(dir)).rejects.toThrow()
  52. })
  53. it.each([
  54. 'allowBuilds:\n native: &pending set this to true or false\n other: *pending\n',
  55. 'allowBuilds: &builds\n native: set this to true or false\nshared: *builds\n',
  56. 'shared: &pending set this to true or false\nallowBuilds:\n native: *pending\n',
  57. ])('rejects shared YAML approval nodes without changing permissions: %s', async (original) => {
  58. const { dir, filename } = fixture(original)
  59. await expect(approveBuilds(dir, ['native'])).rejects.toThrow()
  60. expect(readFileSync(filename, 'utf8')).toBe(original)
  61. })