client-bundle-purity.spec.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. /**
  2. * Pins the client-bundle purity gate (tsdown preset resolveId classifier),
  3. * the build-time mirror of the module-edge rules: platform module-table
  4. * entries stay external, inline-safe wire layers inline, and every other
  5. * @deepseek-ai value import — including a bare plugin-package name and a
  6. * cross-plugin /client subpath — must fail the build loudly (cross-plugin
  7. * collaboration goes through cordis services, never module imports).
  8. */
  9. import { describe, expect, it } from 'vitest'
  10. import { CLIENT_EXTERNALS, clientBundle } from '../packages/client/tsdown.client.ts'
  11. type ResolveId = (source: string) => null | { id: string; external: boolean }
  12. function purityResolveId(): ResolveId {
  13. // libEntry is spelled at every call site (no default) so the
  14. // package-invariants text check can see the invariant entry per package.
  15. const configs = clientBundle('@deepseek-ai/dsh-client-test', ['lib/types/index.js', 'lib/types/invariant.js'])
  16. const plugins = (configs[1] as { plugins: { name: string; resolveId?: unknown }[] }).plugins
  17. const gate = plugins.find(p => p.name === 'dsh-client-bundle-purity')
  18. if (gate?.resolveId === undefined) throw new Error('purity plugin missing from client config')
  19. return gate.resolveId as ResolveId
  20. }
  21. describe('client bundle purity gate', () => {
  22. const resolveId = purityResolveId()
  23. it('leaves platform table entries and non-scoped specifiers alone', () => {
  24. expect(resolveId('@deepseek-ai/dsh-client-ui-slots')).toBeNull()
  25. expect(resolveId('@deepseek-ai/dsh-client-web-react')).toBeNull()
  26. expect(resolveId('@deepseek-ai/dsh-client-ui-primitives')).toBeNull()
  27. expect(resolveId('react')).toBeNull()
  28. expect(resolveId('zod')).toBeNull()
  29. })
  30. it('rejects retired table entries (web-react/store left the 8-entry seed)', () => {
  31. expect(() => resolveId('@deepseek-ai/dsh-client-web-react/store')).toThrow(/purity/)
  32. })
  33. it('lets inline-safe wire layers inline', () => {
  34. expect(resolveId('@deepseek-ai/dsh-host-apiproxy/api')).toBeNull()
  35. expect(resolveId('@deepseek-ai/dsh-session/surface')).toBeNull()
  36. expect(resolveId('@deepseek-ai/dsh-brand')).toBeNull()
  37. })
  38. it('throws on any other @deepseek-ai leak', () => {
  39. expect(() => resolveId('@deepseek-ai/dsh-agent')).toThrow(/purity/)
  40. expect(() => resolveId('@deepseek-ai/dsh-client-web')).toThrow(/purity/)
  41. })
  42. it('throws on cross-plugin value imports — bare plugin names and /client subpaths alike (the rewrite arm is gone)', () => {
  43. expect(() => resolveId('@deepseek-ai/dsh-client-connection')).toThrow(/purity/)
  44. expect(() => resolveId('@deepseek-ai/dsh-client-runtime')).toThrow(/purity/)
  45. expect(() => resolveId('@deepseek-ai/dsh-client-ui-layout/client')).toThrow(/purity/)
  46. })
  47. it('carries exactly one documented temporary exemption: runtime/client (store engine pending rehoming)', () => {
  48. expect(resolveId('@deepseek-ai/dsh-client-runtime/client')).toBeNull()
  49. const dshClientChannels = CLIENT_EXTERNALS.filter(
  50. entry => entry.startsWith('@deepseek-ai/') && entry.endsWith('/client'))
  51. expect(dshClientChannels).toEqual(['@deepseek-ai/dsh-client-runtime/client'])
  52. })
  53. })