installed-product-isolation.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. /** Experimental-package isolation for the installed default product's dependency graph. */
  2. import { existsSync, readFileSync, realpathSync } from 'node:fs'
  3. import { createRequire } from 'node:module'
  4. import { join } from 'node:path'
  5. import { OPTIONAL_BUNDLES } from '../../packages/boot/app-boot/src/profile.ts'
  6. interface InstalledManifest {
  7. name: string
  8. dependencies?: Record<string, string>
  9. optionalDependencies?: Record<string, string>
  10. peerDependencies?: Record<string, string>
  11. peerDependenciesMeta?: Record<string, { optional?: boolean }>
  12. }
  13. /**
  14. * Reject experimental dependencies reachable from one installed product entry.
  15. * Other packages installed beside the entry and development dependencies do not join its graph.
  16. * @param directory - installed entry package directory.
  17. * @returns number of distinct installed packages visited.
  18. */
  19. export function verifyInstalledProductIsolation(directory: string, optionalBundles: readonly string[] = OPTIONAL_BUNDLES): number {
  20. const visited = new Set<string>()
  21. const queue = [{ directory, chain: [] as string[] }]
  22. for (const item of queue) {
  23. const canonical = realpathSync(item.directory)
  24. if (visited.has(canonical)) continue
  25. visited.add(canonical)
  26. const manifest = JSON.parse(readFileSync(join(canonical, 'package.json'), 'utf8')) as InstalledManifest
  27. const chain = [...item.chain, manifest.name]
  28. rejectExperimental(manifest.name, chain)
  29. // The bundles the entry package ships switched off are installed beside the product, not required by it.
  30. const offered = item.chain.length === 0 ? new Set(optionalBundles) : new Set<string>()
  31. for (const section of ['dependencies', 'optionalDependencies', 'peerDependencies'] as const) {
  32. for (const [name, range] of Object.entries(manifest[section] ?? {})) {
  33. if (section === 'dependencies' && offered.has(name)) {
  34. if (installedPackage(canonical, name) === undefined) throw new Error(`optional bundle is missing: ${[...chain, name].join(' -> ')}`)
  35. continue
  36. }
  37. rejectExperimental(name, [...chain, name])
  38. if (range.startsWith('npm:')) {
  39. rejectExperimental(range.slice(4), [...chain, `${name} (${range})`])
  40. }
  41. const dependency = installedPackage(canonical, name)
  42. if (dependency === undefined) {
  43. if (section === 'optionalDependencies'
  44. || Object.hasOwn(manifest.optionalDependencies ?? {}, name)
  45. || section === 'peerDependencies' && manifest.peerDependenciesMeta?.[name]?.optional === true) continue
  46. throw new Error(`default product dependency is missing: ${[...chain, name].join(' -> ')}`)
  47. }
  48. queue.push({ directory: dependency, chain })
  49. }
  50. }
  51. }
  52. return visited.size
  53. }
  54. function rejectExperimental(name: string, chain: readonly string[]): void {
  55. if (name.startsWith('@deepseek-ai/dsh-experimental-')) {
  56. throw new Error(`default product includes an experimental package: ${chain.join(' -> ')}`)
  57. }
  58. }
  59. /** Resolve a dependency directory through the installed package's ancestor node_modules. */
  60. function installedPackage(from: string, name: string): string | undefined {
  61. const resolver = createRequire(join(from, 'package.json'))
  62. for (const directory of resolver.resolve.paths(name) ?? []) {
  63. const candidate = join(directory, name)
  64. if (existsSync(join(candidate, 'package.json'))) return candidate
  65. }
  66. return undefined
  67. }