materialize-cypress-reporter.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #!/usr/bin/env node
  2. 'use strict'
  3. /**
  4. * Materialize Cypress reporter packages (and their transitive dependencies)
  5. * into a real node_modules tree so that Cypress's Electron process (which uses
  6. * PackherdModuleLoader, NOT Yarn PnP hooks) can load them.
  7. *
  8. * Builds a NESTED node_modules structure (each package gets its own
  9. * node_modules/ for its deps) to avoid version conflicts from flat dedup.
  10. *
  11. * The target-dir must be OUTSIDE the PnP workspace root — otherwise PnP
  12. * intercepts requires from the materialized packages and blocks undeclared deps.
  13. *
  14. * Usage: yarn node scripts/materialize-cypress-reporter.js <target-dir>
  15. */
  16. const path = require('path')
  17. const fs = require('fs')
  18. const targetDir = process.argv[2]
  19. if (!targetDir) {
  20. console.error(
  21. 'Usage: yarn node scripts/materialize-cypress-reporter.js <target-dir>'
  22. )
  23. process.exit(1)
  24. }
  25. const pnp = require('module').findPnpApi(__filename)
  26. if (!pnp) {
  27. console.log('Not running under PnP — nothing to do.')
  28. process.exit(0)
  29. }
  30. let totalCopied = 0
  31. // PnP patches readFileSync/readdirSync/statSync but NOT cpSync.
  32. // Manual recursive copy to read from virtual/zip paths.
  33. function copyRecursive(src, dest) {
  34. const stat = fs.statSync(src)
  35. if (stat.isDirectory()) {
  36. fs.mkdirSync(dest, { recursive: true })
  37. for (const entry of fs.readdirSync(src)) {
  38. copyRecursive(path.join(src, entry), path.join(dest, entry))
  39. }
  40. } else {
  41. fs.mkdirSync(path.dirname(dest), { recursive: true })
  42. fs.writeFileSync(dest, fs.readFileSync(src))
  43. }
  44. }
  45. function resolvePkgDir(pkgName, issuer) {
  46. // Use resolveToUnqualified to get the package directory without going
  47. // through the package's exports map (which may not expose package.json).
  48. let pkgDir = pnp.resolveToUnqualified(pkgName, issuer + '/')
  49. if (pkgDir.endsWith('/')) pkgDir = pkgDir.slice(0, -1)
  50. return pkgDir
  51. }
  52. /**
  53. * Materialize a package and its deps into destDir/node_modules/pkgName.
  54. * Each package's own deps get nested under its own node_modules/.
  55. */
  56. function materializeRecursive(pkgName, issuer, destNodeModules, seen) {
  57. // Dedupe by destination path to avoid infinite loops, but allow the same
  58. // package to appear in multiple locations (nested node_modules).
  59. const key = `${destNodeModules}/${pkgName}`
  60. if (seen.has(key)) return
  61. seen.add(key)
  62. let pkgDir
  63. try {
  64. pkgDir = resolvePkgDir(pkgName, issuer)
  65. } catch {
  66. return // optional/missing dep
  67. }
  68. const dest = path.join(destNodeModules, pkgName)
  69. copyRecursive(pkgDir, dest)
  70. totalCopied++
  71. const pkg = JSON.parse(
  72. fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')
  73. )
  74. const deps = [
  75. ...Object.keys(pkg.dependencies || {}),
  76. ...Object.keys(pkg.peerDependencies || {}),
  77. ]
  78. if (deps.length > 0) {
  79. const nestedNodeModules = path.join(dest, 'node_modules')
  80. for (const dep of deps) {
  81. materializeRecursive(dep, pkgDir, nestedNodeModules, seen)
  82. }
  83. }
  84. }
  85. const rootNodeModules = path.resolve(targetDir, 'node_modules')
  86. const seen = new Set()
  87. const topLevel = [
  88. 'cypress-multi-reporters',
  89. 'mocha-junit-reporter',
  90. 'typescript',
  91. ]
  92. for (const pkg of topLevel) {
  93. try {
  94. materializeRecursive(pkg, process.cwd(), rootNodeModules, seen)
  95. } catch (e) {
  96. console.warn(`Warning: could not materialize ${pkg}: ${e.message}`)
  97. }
  98. }
  99. console.log(`Materialized ${totalCopied} packages into ${rootNodeModules}`)