clean.spec.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  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, 'root.tsbuildinfo'))
  35. write(join(root, 'packages/removed/ghost/node_modules/.bin/tool'))
  36. await new RepositoryCleaner(root).clean()
  37. expect(existsSync(join(root, 'products/shell/lib'))).toBe(false)
  38. expect(existsSync(join(root, 'products/shell/src/index.ts'))).toBe(true)
  39. expect(existsSync(join(root, '.typecheck'))).toBe(false)
  40. expect(existsSync(join(root, 'root.tsbuildinfo'))).toBe(false)
  41. expect(existsSync(join(root, 'packages/removed/ghost'))).toBe(false)
  42. })
  43. it('does not delete any target when a manifest-less package contains an unknown file', async () => {
  44. const root = fixture()
  45. addProject(root, 'products/shell')
  46. write(join(root, 'products/shell/lib/types/index.js'))
  47. write(join(root, 'packages/removed/ghost/notes.txt'))
  48. await expect(new RepositoryCleaner(root).clean()).rejects.toThrow('packages/removed/ghost/notes.txt')
  49. expect(existsSync(join(root, 'products/shell/lib'))).toBe(true)
  50. })
  51. it('removes the native Landlock entry output and solution build info', async () => {
  52. const root = fixture()
  53. const entry = 'native/landlock-run/packages/entry'
  54. addProject(root, entry, 'lib')
  55. write(join(root, entry, 'lib/index.js'))
  56. write(join(root, 'native/landlock-run/tsconfig.tsbuildinfo'))
  57. await new RepositoryCleaner(root).clean()
  58. expect(existsSync(join(root, entry, 'lib'))).toBe(false)
  59. expect(existsSync(join(root, entry, 'src/index.ts'))).toBe(true)
  60. expect(existsSync(join(root, 'native/landlock-run/tsconfig.tsbuildinfo'))).toBe(false)
  61. })
  62. it('refuses project outputs reached through a symlink outside the repository', async () => {
  63. const root = fixture()
  64. const externalProject = fixture()
  65. write(join(root, 'tsconfig.json'), JSON.stringify({ files: [], references: [{ path: './linked' }] }))
  66. write(join(externalProject, 'tsconfig.json'), JSON.stringify({
  67. compilerOptions: { composite: true, outDir: 'lib/types' },
  68. include: ['src'],
  69. }))
  70. write(join(externalProject, 'src/index.ts'), 'export {}\n')
  71. write(join(externalProject, 'lib/types/index.js'))
  72. symlinkSync(externalProject, join(root, 'linked'), process.platform === 'win32' ? 'junction' : 'dir')
  73. await expect(new RepositoryCleaner(root).clean()).rejects.toThrow('outside repository')
  74. expect(existsSync(join(externalProject, 'lib/types/index.js'))).toBe(true)
  75. })
  76. })