verify-wrapper-repo-content.mjs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { existsSync } from 'node:fs'
  2. import { cp, mkdtemp, readFile, rm, stat } from 'node:fs/promises'
  3. import { dirname, join, resolve } from 'node:path'
  4. import { spawnSync } from 'node:child_process'
  5. import { tmpdir } from 'node:os'
  6. import { fileURLToPath } from 'node:url'
  7. const scriptDir = dirname(fileURLToPath(import.meta.url))
  8. const sourceRoot = resolve(scriptDir, '..')
  9. const args = process.argv.slice(2)
  10. function readArg(name, fallback) {
  11. const index = args.indexOf(name)
  12. return index >= 0 ? args[index + 1] : fallback
  13. }
  14. const host = readArg('--host', 'github')
  15. if (!['github', 'gitee'].includes(host)) {
  16. throw new Error(`Unsupported host ${host}. Use --host=github or --host=gitee.`)
  17. }
  18. const outputRoot = resolve(
  19. sourceRoot,
  20. readArg(
  21. '--out-dir',
  22. process.env.FILE_VIEWER_COMPONENT_REPO_DIR ||
  23. process.env.FILE_VIEWER_WRAPPER_REPO_DIR ||
  24. '.release/component-repos'
  25. )
  26. )
  27. const selectedIds = new Set(
  28. args
  29. .filter(arg => arg.startsWith('--id='))
  30. .map(arg => arg.slice('--id='.length))
  31. )
  32. const selectedPackages = new Set(
  33. args
  34. .filter(arg => arg.startsWith('--package='))
  35. .map(arg => arg.slice('--package='.length))
  36. )
  37. const wrapperManifest = JSON.parse(
  38. await readFile(join(sourceRoot, 'ecosystem', 'wrappers.json'), 'utf8')
  39. )
  40. const targets = [
  41. {
  42. id: 'core',
  43. kind: 'core',
  44. packageName: wrapperManifest.corePackage.packageName,
  45. repository: wrapperManifest.corePackage.repository,
  46. github: wrapperManifest.corePackage.github,
  47. gitee: wrapperManifest.corePackage.gitee
  48. },
  49. ...wrapperManifest.wrappers.map(wrapper => ({
  50. id: wrapper.id,
  51. kind: 'component',
  52. packageName: wrapper.packageName,
  53. repository: wrapper.repository,
  54. github: wrapper.github,
  55. gitee: wrapper.gitee
  56. }))
  57. ].filter(target => {
  58. if (selectedIds.size && !selectedIds.has(target.id)) {
  59. return false
  60. }
  61. if (selectedPackages.size && !selectedPackages.has(target.packageName)) {
  62. return false
  63. }
  64. return true
  65. })
  66. if (!targets.length) {
  67. throw new Error('No core/component repository content selected for verification.')
  68. }
  69. function run(command, commandArgs, cwd = sourceRoot, options = {}) {
  70. const result = spawnSync(command, commandArgs, {
  71. cwd,
  72. encoding: 'utf8',
  73. stdio: options.inherit ? 'inherit' : 'pipe',
  74. timeout: options.timeout ?? 30_000
  75. })
  76. if (result.status !== 0) {
  77. throw new Error(
  78. `Command failed: ${command} ${commandArgs.join(' ')}\n${result.stderr || result.stdout || result.error?.message || ''}`
  79. )
  80. }
  81. return result.stdout.trim()
  82. }
  83. async function assertDirectory(path, label = path) {
  84. if (!existsSync(path)) {
  85. throw new Error(`Missing directory: ${label}`)
  86. }
  87. const info = await stat(path)
  88. if (!info.isDirectory()) {
  89. throw new Error(`Not a directory: ${label}`)
  90. }
  91. }
  92. function shouldCopy(source) {
  93. const name = source.split('/').at(-1)
  94. return ![
  95. '.git',
  96. '.DS_Store',
  97. 'node_modules',
  98. 'dist',
  99. '.pnpm-store',
  100. '.npm-cache'
  101. ].includes(name)
  102. }
  103. async function copyComparableTree(from, to) {
  104. await cp(from, to, {
  105. recursive: true,
  106. force: true,
  107. filter: shouldCopy
  108. })
  109. await rm(join(to, 'package-repo-manifest.json'), { force: true })
  110. await rm(join(to, 'wrapper-repo-manifest.json'), { force: true })
  111. }
  112. async function readJson(path) {
  113. return JSON.parse(await readFile(path, 'utf8'))
  114. }
  115. async function verifyManifest(remoteDir, target) {
  116. const manifestName = target.kind === 'core' ? 'package-repo-manifest.json' : 'wrapper-repo-manifest.json'
  117. const manifestPath = join(remoteDir, manifestName)
  118. if (!existsSync(manifestPath)) {
  119. throw new Error(`${target.repository} is missing ${manifestName}`)
  120. }
  121. const manifest = await readJson(manifestPath)
  122. if (manifest.packageName !== target.packageName) {
  123. throw new Error(`${target.repository} manifest packageName ${manifest.packageName} !== ${target.packageName}`)
  124. }
  125. if (manifest.repository !== target.repository) {
  126. throw new Error(`${target.repository} manifest repository ${manifest.repository} !== ${target.repository}`)
  127. }
  128. if (manifest[host] !== target[host]) {
  129. throw new Error(`${target.repository} manifest ${host} ${manifest[host]} !== ${target[host]}`)
  130. }
  131. }
  132. const tempRoot = await mkdtemp(join(tmpdir(), 'file-viewer-wrapper-content-'))
  133. let verified = 0
  134. try {
  135. for (const target of targets) {
  136. const localDir = join(outputRoot, target.repository)
  137. await assertDirectory(localDir, `${target.repository} local export`)
  138. const remoteDir = join(tempRoot, `${target.repository}-remote`)
  139. run('git', ['clone', '--depth=1', `${target[host]}.git`, remoteDir], sourceRoot, { timeout: 60_000 })
  140. await verifyManifest(remoteDir, target)
  141. const comparableLocal = join(tempRoot, `${target.repository}-local`)
  142. const comparableRemote = join(tempRoot, `${target.repository}-remote-clean`)
  143. await copyComparableTree(localDir, comparableLocal)
  144. await copyComparableTree(remoteDir, comparableRemote)
  145. try {
  146. run('git', ['diff', '--no-index', '--quiet', comparableLocal, comparableRemote], sourceRoot, {
  147. timeout: 30_000
  148. })
  149. } catch (error) {
  150. const diff = spawnSync('git', ['diff', '--no-index', '--stat', comparableLocal, comparableRemote], {
  151. cwd: sourceRoot,
  152. encoding: 'utf8',
  153. stdio: 'pipe',
  154. timeout: 30_000
  155. })
  156. throw new Error(
  157. `${target.repository} ${host} content differs from local export.\n${diff.stdout || error.message}`
  158. )
  159. }
  160. verified += 1
  161. console.log(`${host}\t${target.id}\tcontent-ok\t${target[host]}`)
  162. }
  163. } finally {
  164. await rm(tempRoot, { recursive: true, force: true })
  165. }
  166. console.log(`Verified ${verified} ${host} core/component repository content trees.`)