1
0

bundle-input-isolation.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /** Experimental ownership of the actual filesystem and package inputs supplied to a bundler. */
  2. import { existsSync, readFileSync, realpathSync } from 'node:fs'
  3. import { dirname, isAbsolute, relative, resolve, sep } from 'node:path'
  4. import { fileURLToPath } from 'node:url'
  5. /** Package identities are cached only within one build. */
  6. export class BundleInputIsolation {
  7. private readonly packages = new Map<string, string | undefined>()
  8. private readonly repository: string
  9. private readonly label: string
  10. constructor(repository: string, label: string) {
  11. this.repository = repository
  12. this.label = label
  13. }
  14. /** Discard package metadata before a new build or watch rebuild. */
  15. reset(): void { this.packages.clear() }
  16. /**
  17. * Require a recorded input to have non-experimental physical and package ownership.
  18. * @param id - bundler module id, filesystem path, or filesystem URL.
  19. */
  20. assertInput(id: string): void {
  21. this.checkInput(id, false)
  22. }
  23. /**
  24. * Check declared source-map ownership; dependency tarballs may omit their upstream sources.
  25. * @param id - source-map source resolved relative to its map.
  26. */
  27. assertSourceMapInput(id: string): void {
  28. this.checkInput(id, true)
  29. }
  30. private checkInput(id: string, allowMissing: boolean): void {
  31. if (/(?:^|[/:\u0000])@deepseek-ai\/dsh-experimental-[^/?#]+/.test(id)) {
  32. throw new Error(`${this.label}: experimental input ${id}`)
  33. }
  34. const file = physicalBundleInput(id)
  35. if (file === undefined) return
  36. const lexical = relative(resolve(this.repository, 'packages/experimental'), file)
  37. if (lexical === '' || lexical !== '..' && !lexical.startsWith(`..${sep}`) && !isAbsolute(lexical)) {
  38. throw new Error(`${this.label}: experimental input ${id}`)
  39. }
  40. let existing = file
  41. if (!allowMissing && !existsSync(file)) throw new Error(`${this.label}: input ${id} is missing`)
  42. while (!existsSync(existing)) {
  43. const parent = dirname(existing)
  44. if (parent === existing) throw new Error(`${this.label}: input ${id} has no existing filesystem root`)
  45. existing = parent
  46. }
  47. const canonical = resolve(realpathSync(existing), relative(existing, file))
  48. if (canonical !== file) this.checkInput(canonical, allowMissing)
  49. const name = this.packageName(dirname(canonical))
  50. if (name?.startsWith('@deepseek-ai/dsh-experimental-')) {
  51. throw new Error(`${this.label}: ${id} belongs to experimental package ${name}`)
  52. }
  53. }
  54. private packageName(directory: string): string | undefined {
  55. if (this.packages.has(directory)) return this.packages.get(directory)
  56. const manifest = resolve(directory, 'package.json')
  57. let name: string | undefined
  58. if (existsSync(manifest)) {
  59. const value: unknown = JSON.parse(readFileSync(manifest, 'utf8'))
  60. if (value !== null && typeof value === 'object' && 'name' in value && typeof value.name === 'string') name = value.name
  61. }
  62. if (name === undefined && dirname(directory) !== directory) name = this.packageName(dirname(directory))
  63. this.packages.set(directory, name)
  64. return name
  65. }
  66. }
  67. /**
  68. * Recover physical ownership from queries, filesystem URLs, and virtual path wrappers.
  69. * @param id - bundler module id or recorded filesystem input.
  70. * @returns absolute file path, or undefined for a virtual id without a physical path.
  71. */
  72. export function physicalBundleInput(id: string): string | undefined {
  73. let clean = id.replaceAll('\\', '/').split(/[?#]/, 1)[0] ?? ''
  74. clean = clean.replace(/^\u0000/, '')
  75. if (clean.startsWith('file://')) return fileURLToPath(clean)
  76. if (clean.startsWith('/@fs/')) {
  77. clean = clean.slice('/@fs/'.length)
  78. if (!isAbsolute(clean) && !/^[a-zA-Z]:\//.test(clean)) clean = `/${clean}`
  79. }
  80. if (!isAbsolute(clean) && !/^[a-zA-Z]:\//.test(clean)) {
  81. const wrapped = /:(\/.*|[a-zA-Z]:\/.*)$/.exec(clean)?.[1]
  82. if (wrapped === undefined) return undefined
  83. clean = wrapped
  84. }
  85. return resolve(clean)
  86. }