| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import { existsSync } from 'node:fs'
- import { mkdir, rm, writeFile } from 'node:fs/promises'
- import { dirname, join, relative, resolve, sep } from 'node:path'
- import { spawnSync } from 'node:child_process'
- import { fileURLToPath } from 'node:url'
- import { loadEcosystemReleaseContext } from './lib/ecosystem-packages.mjs'
- const scriptDir = dirname(fileURLToPath(import.meta.url))
- const sourceRoot = resolve(scriptDir, '..')
- const args = process.argv.slice(2)
- const hasArg = name => args.includes(name)
- const readArg = (name, fallback) => {
- const direct = args.find(arg => arg.startsWith(`${name}=`))
- if (direct) {
- return direct.slice(name.length + 1)
- }
- const index = args.indexOf(name)
- return index >= 0 ? args[index + 1] : fallback
- }
- const npmSpec = readArg('--npm', process.env.FILE_VIEWER_NPM_SMOKE_NPM || 'npm@11.17.0')
- const smokeRoot = resolve(
- sourceRoot,
- readArg('--tmp-dir', process.env.FILE_VIEWER_NPM_SMOKE_DIR || '.release/npm-install-smoke')
- )
- const keepTemp = hasArg('--keep-temp')
- const registryOnly = hasArg('--registry-only')
- const tarballOnly = hasArg('--tarball-only')
- const installTargets = ['@file-viewer/vue3', '@file-viewer/preset-office']
- const smokeImports = ['@file-viewer/vue3', '@file-viewer/preset-office']
- const { entries } = await loadEcosystemReleaseContext(sourceRoot)
- const entryByPackageName = new Map(entries.map(entry => [entry.packageName, entry]))
- function assert(condition, message) {
- if (!condition) {
- throw new Error(message)
- }
- }
- function npmCommandArgs(commandArgs) {
- if (npmSpec === 'npm') {
- return { command: 'npm', args: commandArgs }
- }
- return { command: 'npx', args: ['--yes', npmSpec, ...commandArgs] }
- }
- function runNpm(commandArgs, cwd, options = {}) {
- const { command, args: npmArgs } = npmCommandArgs(commandArgs)
- return run(command, npmArgs, cwd, options)
- }
- function run(command, commandArgs, cwd = sourceRoot, options = {}) {
- console.log(`$ ${[command, ...commandArgs].join(' ')}`)
- const result = spawnSync(command, commandArgs, {
- cwd,
- encoding: 'utf8',
- stdio: options.capture ? 'pipe' : 'inherit',
- env: {
- ...process.env,
- npm_config_loglevel: process.env.npm_config_loglevel || 'error',
- npm_config_audit: 'false',
- npm_config_fund: 'false'
- },
- timeout: Number(process.env.FILE_VIEWER_NPM_SMOKE_TIMEOUT_MS || 600_000)
- })
- if (result.error) {
- throw new Error(
- [
- `Command failed in ${cwd}: ${command} ${commandArgs.join(' ')}`,
- result.error.message,
- result.stdout,
- result.stderr
- ].filter(Boolean).join('\n')
- )
- }
- if (result.status !== 0) {
- throw new Error(
- [
- `Command failed in ${cwd}: ${command} ${commandArgs.join(' ')}`,
- result.stdout,
- result.stderr
- ].filter(Boolean).join('\n')
- )
- }
- return result.stdout?.trim() || ''
- }
- function runtimeDependencyNames(packageJson) {
- return Object.keys({
- ...(packageJson.dependencies || {}),
- ...(packageJson.optionalDependencies || {})
- }).sort()
- }
- function collectLocalDependencyClosure(entry, collected = new Map()) {
- for (const dependencyName of runtimeDependencyNames(entry.packageJson)) {
- const dependencyEntry = entryByPackageName.get(dependencyName)
- if (!dependencyEntry || collected.has(dependencyEntry.packageName)) {
- continue
- }
- collected.set(dependencyEntry.packageName, dependencyEntry)
- collectLocalDependencyClosure(dependencyEntry, collected)
- }
- return collected
- }
- function collectTargetEntries(packageNames) {
- const collected = new Map()
- for (const packageName of packageNames) {
- const entry = entryByPackageName.get(packageName)
- assert(entry, `Missing ecosystem package entry for ${packageName}`)
- collected.set(entry.packageName, entry)
- collectLocalDependencyClosure(entry).forEach(dependencyEntry => {
- collected.set(dependencyEntry.packageName, dependencyEntry)
- })
- }
- return [...collected.values()]
- }
- function toFileSpec(fromDir, targetFile) {
- const relativePath = relative(fromDir, targetFile).split(sep).join('/')
- return `file:${relativePath.startsWith('.') ? relativePath : `./${relativePath}`}`
- }
- async function writeSmokeProject(caseRoot, dependencies) {
- await mkdir(caseRoot, { recursive: true })
- await writeFile(
- join(caseRoot, 'package.json'),
- `${JSON.stringify(
- {
- name: `file-viewer-npm-install-smoke-${relative(smokeRoot, caseRoot).replace(/[\\/]/g, '-')}`,
- private: true,
- type: 'module',
- scripts: {
- smoke: 'node smoke.mjs'
- },
- dependencies
- },
- null,
- 2
- )}\n`
- )
- await writeFile(
- join(caseRoot, 'smoke.mjs'),
- `${smokeImports.map((packageName, index) => `import * as package${index} from '${packageName}'`).join('\n')}
- ${smokeImports.map((_packageName, index) => `if (!package${index}) throw new Error('Missing smoke import ${index}')`).join('\n')}
- console.log('[npm-install-smoke] Imports passed.')
- `
- )
- }
- async function runRegistrySmoke() {
- const caseRoot = join(smokeRoot, 'registry')
- await rm(caseRoot, { recursive: true, force: true })
- await writeSmokeProject(
- caseRoot,
- Object.fromEntries(installTargets.map(packageName => [packageName, 'latest']))
- )
- runNpm(['install', '--ignore-scripts', '--no-audit', '--no-fund'], caseRoot)
- runNpm(['run', 'smoke'], caseRoot)
- console.log(`[npm-install-smoke] Registry install passed with ${npmSpec}.`)
- }
- async function runTarballSmoke() {
- const tarballDir = join(smokeRoot, 'tarballs')
- const caseRoot = join(smokeRoot, 'tarball')
- await rm(tarballDir, { recursive: true, force: true })
- await rm(caseRoot, { recursive: true, force: true })
- await mkdir(tarballDir, { recursive: true })
- const tarballByPackageName = new Map()
- for (const entry of collectTargetEntries(installTargets)) {
- run('pnpm', ['-C', entry.packageDir, 'pack', '--pack-destination', tarballDir], sourceRoot, {
- capture: true
- })
- const tarball = join(tarballDir, entry.tarballName)
- assert(existsSync(tarball), `Expected tarball was not created: ${tarball}`)
- tarballByPackageName.set(entry.packageName, tarball)
- }
- await writeSmokeProject(
- caseRoot,
- Object.fromEntries(
- [...tarballByPackageName.entries()].map(([packageName, tarball]) => [
- packageName,
- toFileSpec(caseRoot, tarball)
- ])
- )
- )
- runNpm(['install', '--ignore-scripts', '--no-audit', '--no-fund'], caseRoot)
- runNpm(['run', 'smoke'], caseRoot)
- console.log(`[npm-install-smoke] Local tgz dependency-closure install passed with ${npmSpec}.`)
- }
- if (!keepTemp) {
- await rm(smokeRoot, { recursive: true, force: true })
- }
- await mkdir(smokeRoot, { recursive: true })
- if (!tarballOnly) {
- await runRegistrySmoke()
- }
- if (!registryOnly) {
- await runTarballSmoke()
- }
- console.log('[npm-install-smoke] All requested npm install smoke checks passed.')
|