clean.spec.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { dirname, join } from 'node:path'
  4. import { afterEach, describe, expect, it } from 'vitest'
  5. import { RepositoryCleaner } from './clean.ts'
  6. const roots: string[] = []
  7. function fixture(): string {
  8. const root = mkdtempSync(join(tmpdir(), 'dsh-clean-'))
  9. roots.push(root)
  10. return root
  11. }
  12. function write(path: string, content = ''): void {
  13. mkdirSync(dirname(path), { recursive: true })
  14. writeFileSync(path, content)
  15. }
  16. function addProject(root: string, path: string, outDir = 'lib/types'): void {
  17. write(join(root, 'tsconfig.json'), JSON.stringify({ files: [], references: [{ path }] }))
  18. write(join(root, path, 'tsconfig.json'), JSON.stringify({
  19. compilerOptions: { composite: true, outDir },
  20. include: ['src'],
  21. }))
  22. write(join(root, path, 'src/index.ts'), 'export {}\n')
  23. }
  24. afterEach(() => {
  25. for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
  26. })
  27. describe('RepositoryCleaner', () => {
  28. it('derives live build outputs from project references and removes safe stale package residue', async () => {
  29. const root = fixture()
  30. addProject(root, 'products/shell')
  31. write(join(root, 'products/shell/lib/types/index.js'))
  32. write(join(root, 'products/shell/lib/index.js'))
  33. write(join(root, '.typecheck/legacy.tsbuildinfo'))
  34. write(join(root, '.dsh-build/client-build-environment.json'))
  35. write(join(root, 'root.tsbuildinfo'))
  36. write(join(root, 'packages/removed/ghost/node_modules/.bin/tool'))
  37. await new RepositoryCleaner(root).clean()
  38. expect(existsSync(join(root, 'products/shell/lib'))).toBe(false)
  39. expect(existsSync(join(root, 'products/shell/src/index.ts'))).toBe(true)
  40. expect(existsSync(join(root, '.typecheck'))).toBe(false)
  41. expect(existsSync(join(root, '.dsh-build'))).toBe(false)
  42. expect(existsSync(join(root, 'root.tsbuildinfo'))).toBe(false)
  43. expect(existsSync(join(root, 'packages/removed/ghost'))).toBe(false)
  44. })
  45. it('does not delete any target when a manifest-less package contains an unknown file', async () => {
  46. const root = fixture()
  47. addProject(root, 'products/shell')
  48. write(join(root, 'products/shell/lib/types/index.js'))
  49. write(join(root, 'packages/removed/ghost/notes.txt'))
  50. await expect(new RepositoryCleaner(root).clean()).rejects.toThrow('packages/removed/ghost/notes.txt')
  51. expect(existsSync(join(root, 'products/shell/lib'))).toBe(true)
  52. })
  53. it('removes the native Landlock entry output and solution build info', async () => {
  54. const root = fixture()
  55. const entry = 'native/landlock-run/packages/entry'
  56. addProject(root, entry, 'lib')
  57. write(join(root, entry, 'lib/index.js'))
  58. write(join(root, 'native/landlock-run/tsconfig.tsbuildinfo'))
  59. await new RepositoryCleaner(root).clean()
  60. expect(existsSync(join(root, entry, 'lib'))).toBe(false)
  61. expect(existsSync(join(root, entry, 'src/index.ts'))).toBe(true)
  62. expect(existsSync(join(root, 'native/landlock-run/tsconfig.tsbuildinfo'))).toBe(false)
  63. })
  64. it('refuses project outputs reached through a symlink outside the repository', async () => {
  65. const root = fixture()
  66. const externalProject = fixture()
  67. write(join(root, 'tsconfig.json'), JSON.stringify({ files: [], references: [{ path: './linked' }] }))
  68. write(join(externalProject, 'tsconfig.json'), JSON.stringify({
  69. compilerOptions: { composite: true, outDir: 'lib/types' },
  70. include: ['src'],
  71. }))
  72. write(join(externalProject, 'src/index.ts'), 'export {}\n')
  73. write(join(externalProject, 'lib/types/index.js'))
  74. symlinkSync(externalProject, join(root, 'linked'), process.platform === 'win32' ? 'junction' : 'dir')
  75. await expect(new RepositoryCleaner(root).clean()).rejects.toThrow('outside repository')
  76. expect(existsSync(join(externalProject, 'lib/types/index.js'))).toBe(true)
  77. })
  78. })