gen-tsconfig-paths.spec.ts 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { readFileSync } from 'node:fs'
  2. import { resolve } from 'node:path'
  3. import { describe, expect, it } from 'vitest'
  4. import {
  5. collectPackageAliases,
  6. collectPackageNames,
  7. mappedSpecifiers,
  8. renderAliases,
  9. uncoveredPackages,
  10. writeRegion,
  11. } from './gen-tsconfig-paths.ts'
  12. const root = resolve(import.meta.dirname, '..')
  13. describe('generated tsconfig package aliases', () => {
  14. it('maps each package to its own source directory', () => {
  15. const aliases = collectPackageAliases()
  16. expect(aliases.length).toBeGreaterThan(100)
  17. const session = aliases.find(alias => alias.specifier === '@deepseek-ai/dsh-session')
  18. expect(session).toEqual({
  19. specifier: '@deepseek-ai/dsh-session',
  20. source: './packages/core/session/src',
  21. hasInvariant: true,
  22. })
  23. // Sorted, so a package added anywhere lands in a stable spot in the diff.
  24. expect([...aliases].sort((a, b) => a.specifier.localeCompare(b.specifier))).toEqual(aliases)
  25. // Only packages named after their directory: the rest carry hand-written
  26. // aliases, because the removed wildcards could never have resolved them.
  27. expect(aliases.some(alias => alias.specifier === '@deepseek-ai/dsh-typert-protocol')).toBe(false)
  28. })
  29. it('yields to a hand-written alias and closes without a trailing comma', () => {
  30. const aliases = [
  31. { specifier: '@deepseek-ai/dsh-a', source: './packages/g/a/src', hasInvariant: true },
  32. { specifier: '@deepseek-ai/dsh-b', source: './packages/g/b/src', hasInvariant: false },
  33. ]
  34. const body = renderAliases(aliases, new Set(['@deepseek-ai/dsh-a']))
  35. // The hand-written bare alias is skipped; its /invariant sibling is not.
  36. expect(body).toBe([
  37. ' "@deepseek-ai/dsh-a/invariant": ["./packages/g/a/src/invariant.ts"]',
  38. ' "@deepseek-ai/dsh-b": ["./packages/g/b/src"]',
  39. ].join(',\n'))
  40. expect(body.endsWith(',')).toBe(false)
  41. })
  42. it('replaces only the marked region', () => {
  43. const text = [
  44. '{ "before": 1,',
  45. ' // BEGIN generated package aliases — pnpm run gen-tsconfig-paths',
  46. ' "stale": ["gone"]',
  47. ' // END generated package aliases',
  48. ' "after": 2 }',
  49. ].join('\n')
  50. const next = writeRegion(text, ' "fresh": ["kept"]')
  51. expect(next).toContain('{ "before": 1,')
  52. expect(next).toContain(' "after": 2 }')
  53. expect(next).toContain('"fresh": ["kept"]')
  54. expect(next).not.toContain('stale')
  55. })
  56. it('refuses a config without the region markers', () => {
  57. expect(() => writeRegion('{}', '')).toThrow('missing the generated-region markers')
  58. })
  59. it('names a package that no alias covers', () => {
  60. // Deleting the group wildcards removed the fallback that used to resolve a
  61. // package nobody had aliased. A package whose name does not match its
  62. // directory is skipped by the generator, so without this check it would
  63. // resolve through the workspace symlink to built lib/types instead.
  64. expect(uncoveredPackages(
  65. ['@deepseek-ai/dsh-a', '@deepseek-ai/dsh-b'],
  66. new Set(['@deepseek-ai/dsh-a', '@deepseek-ai/dsh-a/invariant']),
  67. )).toEqual(['@deepseek-ai/dsh-b'])
  68. expect(uncoveredPackages(['@deepseek-ai/dsh-a'], new Set(['@deepseek-ai/dsh-a']))).toEqual([])
  69. })
  70. it('covers every workspace package in the committed config', () => {
  71. const config = readFileSync(resolve(root, 'tsconfig.base.json'), 'utf8')
  72. // Includes the packages the generator skips because their name does not
  73. // match their directory: those carry hand-written aliases.
  74. const names = collectPackageNames()
  75. expect(names).toContain('@deepseek-ai/dsh-typert-protocol')
  76. expect(uncoveredPackages(names, mappedSpecifiers(config))).toEqual([])
  77. })
  78. it('leaves no wildcard that probes every package group', () => {
  79. const config = readFileSync(resolve(root, 'tsconfig.base.json'), 'utf8')
  80. // These two listed one candidate per group, so resolving a package late in
  81. // the list cost a filesystem probe — and under tsx a decorated module
  82. // error — for every group before it.
  83. expect(config).not.toContain('"@deepseek-ai/dsh-*":')
  84. expect(config).not.toContain('"@deepseek-ai/dsh-*/invariant":')
  85. })
  86. })