package-graph.spec.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { afterEach, describe, expect, it } from 'vitest'
  5. import { renderModuleGraph } from './gen-module-graph.ts'
  6. import { collectPackageGraph } from './package-graph.ts'
  7. const roots: string[] = []
  8. afterEach(() => {
  9. for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
  10. })
  11. function fixture(packages: Readonly<Record<string, readonly string[]>>): string {
  12. const root = mkdtempSync(join(tmpdir(), 'dsh-package-graph-'))
  13. roots.push(root)
  14. for (const [name, dependencies] of Object.entries(packages)) {
  15. const directory = join(root, 'packages', 'client', name)
  16. mkdirSync(directory, { recursive: true })
  17. writeFileSync(join(directory, 'package.json'), `${JSON.stringify({
  18. name: `@deepseek-ai/dsh-${name}`,
  19. peerDependencies: Object.fromEntries(dependencies.map(dependency => [
  20. `@deepseek-ai/dsh-${dependency}`,
  21. 'workspace:^',
  22. ])),
  23. }, null, 2)}\n`)
  24. }
  25. return root
  26. }
  27. describe('collectPackageGraph', () => {
  28. it('orders packages after their dependencies', () => {
  29. const root = fixture({ application: ['feature'], feature: ['foundation'], foundation: [] })
  30. expect(collectPackageGraph(root, ['client'], 'fixture').map(pkg => pkg.short))
  31. .toEqual(['foundation', 'feature', 'application'])
  32. })
  33. it('keeps a dependency cycle together and before its consumers', () => {
  34. const root = fixture({ consumer: ['left'], left: ['right'], right: ['left'], foundation: [] })
  35. expect(collectPackageGraph(root, ['client'], 'fixture').map(pkg => pkg.short))
  36. .toEqual(['foundation', 'left', 'right', 'consumer'])
  37. })
  38. it('rejects a missing in-repo peer', () => {
  39. const root = fixture({ consumer: ['missing'] })
  40. expect(() => collectPackageGraph(root, ['client'], 'fixture'))
  41. .toThrow('fixture: @deepseek-ai/dsh-consumer references missing in-repo peer @deepseek-ai/dsh-missing')
  42. })
  43. })
  44. describe('renderModuleGraph', () => {
  45. it('renders the same peer edge in both generated languages', () => {
  46. const packages = [
  47. { short: 'provider', name: '@deepseek-ai/dsh-provider', group: 'core', rel: 'packages/core/provider', deps: [] },
  48. { short: 'consumer', name: '@deepseek-ai/dsh-consumer', group: 'core', rel: 'packages/core/consumer', deps: ['provider'] },
  49. ]
  50. const english = renderModuleGraph(packages, 'en')
  51. const chinese = renderModuleGraph(packages, 'zh')
  52. expect(english).toContain('# Shared-instance dependency graph')
  53. expect(chinese).toContain('# 共享实例依赖关系图')
  54. expect(chinese).toContain('[English](module-graph.md) | 中文')
  55. for (const output of [english, chinese]) {
  56. expect(output).toContain('pkg_consumer --> pkg_provider')
  57. expect(output).toContain('| [`consumer`](../packages/core/consumer) | `core` | [`provider`](../packages/core/provider) |')
  58. }
  59. })
  60. })