verify-built-package-invariants.mjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /** Verify every compiled companion through its staged package self-reference under plain Node. */
  2. import {
  3. copyFileSync,
  4. cpSync,
  5. existsSync,
  6. globSync,
  7. mkdirSync,
  8. mkdtempSync,
  9. readFileSync,
  10. rmSync,
  11. writeFileSync,
  12. } from 'node:fs'
  13. import { dirname, resolve } from 'node:path'
  14. import { pathToFileURL } from 'node:url'
  15. import { parseArgs } from 'node:util'
  16. const repositoryRoot = resolve(import.meta.dirname, '..')
  17. const { values: options } = parseArgs({
  18. args: process.argv.slice(2),
  19. options: { 'packages-root': { type: 'string' }, 'loader-url': { type: 'string' } },
  20. })
  21. const packagesRoot = resolve(options['packages-root'] ?? repositoryRoot)
  22. const loaderUrl = options['loader-url']
  23. ?? pathToFileURL(resolve(repositoryRoot, 'vendor/loader/lib/index.js')).href
  24. const failures = []
  25. const manifests = globSync('packages/*/*/package.json', { cwd: packagesRoot }).sort()
  26. let companionCount = 0
  27. const { default: Loader } = await import(loaderUrl)
  28. const loader = Object.create(Loader.prototype)
  29. for (const manifestPath of manifests) {
  30. const packageDir = dirname(resolve(packagesRoot, manifestPath))
  31. const manifest = JSON.parse(readFileSync(resolve(packagesRoot, manifestPath), 'utf8'))
  32. const packageName = manifest.name
  33. if (typeof packageName !== 'string' || packageName.length === 0) {
  34. failures.push(`${manifestPath}: missing package name`)
  35. continue
  36. }
  37. const invariantExport = manifest.exports?.['./invariant']
  38. if (invariantExport === undefined) continue
  39. companionCount += 1
  40. if (typeof invariantExport !== 'object'
  41. || invariantExport === null
  42. || invariantExport.default !== './lib/invariant.js'
  43. || !manifest.files?.includes('lib/invariant.js')) {
  44. failures.push(`${packageName}: manifest does not publish ./lib/invariant.js as ./invariant`)
  45. continue
  46. }
  47. // Keep the staged view below its owning package so Node reaches the real
  48. // pnpm dependency links. Junctioning node_modules elsewhere breaks pnpm's
  49. // relative workspace links on Windows. Copy the manifest-declared lib view
  50. // so a companion that imports an undeclared runtime chunk fails here.
  51. const stagedPackageDir = mkdtempSync(resolve(packageDir, '.dsh-built-invariant-'))
  52. try {
  53. copyFileSync(resolve(packageDir, 'package.json'), resolve(stagedPackageDir, 'package.json'))
  54. copyDeclaredLibFiles(packageDir, stagedPackageDir, manifest.files)
  55. const probePath = resolve(stagedPackageDir, 'probe.mjs')
  56. writeFileSync(
  57. probePath,
  58. `import * as companion from ${JSON.stringify(`${packageName}/invariant`)}\nexport default companion\n`,
  59. )
  60. const { default: companion } = await import(pathToFileURL(probePath).href)
  61. if ('default' in companion) throw new Error('companion has a default export')
  62. const unwrapped = loader.unwrapExports(companion)
  63. if (unwrapped !== companion) throw new Error('Loader collapsed the companion namespace')
  64. if (typeof unwrapped.name !== 'string') throw new Error('companion name is missing')
  65. if (!Array.isArray(unwrapped.inject) || !unwrapped.inject.includes('invariants')) {
  66. throw new Error('companion does not inject invariants')
  67. }
  68. if (typeof unwrapped.apply !== 'function') throw new Error('companion apply is missing')
  69. } catch (error) {
  70. failures.push(`${packageName}: ${error instanceof Error ? error.message : String(error)}`)
  71. } finally {
  72. rmSync(stagedPackageDir, { recursive: true, force: true })
  73. }
  74. }
  75. if (failures.length > 0) {
  76. console.error('verify-built-package-invariants: compiled companion failures:')
  77. for (const failure of failures) console.error(` ${failure}`)
  78. process.exit(1)
  79. }
  80. console.log(`verify-built-package-invariants: ${companionCount} compiled companion(s) passed plain-Node Loader checks.`)
  81. function copyDeclaredLibFiles(packageDir, stagedPackageDir, files) {
  82. for (const pattern of files) {
  83. if (!pattern.startsWith('lib/')) continue
  84. for (const relativePath of globSync(pattern, { cwd: packageDir })) {
  85. const source = resolve(packageDir, relativePath)
  86. if (!existsSync(source)) continue
  87. const target = resolve(stagedPackageDir, relativePath)
  88. mkdirSync(dirname(target), { recursive: true })
  89. cpSync(source, target, { recursive: true })
  90. }
  91. }
  92. }