#!/usr/bin/env node /** * Usage: * test-npm-package.js [--install] [--rebuild] + * * Everything after the directory gets passed to `npm run` to build * the test command. * * If `--install` is passed, we'll run a full `npm install` before running the * test suite. Same for `--rebuild` and `npm rebuild`. * * We always use the node used to spawn this script and the `npm` version * bundled in `deps/npm`. * * If an additional `--logfile=` option is passed before ``, * the stdout output of the test script will be written to that file. */ 'use strict'; const { spawn, spawnSync } = require('child_process'); const { createHash } = require('crypto'); const { createWriteStream, mkdirSync, rmdirSync } = require('fs'); const path = require('path'); const common = require('../test/common'); const tmpDir = require('../test/common/tmpdir'); const projectDir = path.resolve(__dirname, '..'); const npmBin = path.join(projectDir, 'deps', 'npm', 'bin', 'npm-cli.js'); const nodePath = path.dirname(process.execPath); function spawnCopyDeepSync(source, destination) { if (common.isWindows) { mkdirSync(destination); // Prevent interactive prompt return spawnSync('xcopy.exe', ['/E', source, destination]); } else { return spawnSync('cp', ['-r', `${source}/`, destination]); } } function runNPMPackageTests({ srcDir, install, rebuild, testArgs, logfile }) { // Make sure we don't conflict with concurrent test runs const srcHash = createHash('md5').update(srcDir).digest('hex'); tmpDir.path = `${tmpDir.path}.npm.${srcHash}`; tmpDir.refresh(); const npmCache = path.join(tmpDir.path, 'npm-cache'); const npmPrefix = path.join(tmpDir.path, 'npm-prefix'); const npmTmp = path.join(tmpDir.path, 'npm-tmp'); const npmUserconfig = path.join(tmpDir.path, 'npm-userconfig'); const pkgDir = path.join(tmpDir.path, 'pkg'); spawnCopyDeepSync(srcDir, pkgDir); const npmOptions = { cwd: pkgDir, env: Object.assign({}, process.env, { 'npm_config_cache': npmCache, 'npm_config_prefix': npmPrefix, 'npm_config_tmp': npmTmp, 'npm_config_userconfig': npmUserconfig, }), stdio: 'inherit', }; if (common.isWindows) { npmOptions.env.home = tmpDir.path; npmOptions.env.Path = `${nodePath};${process.env.Path}`; } else { npmOptions.env.HOME = tmpDir.path; npmOptions.env.PATH = `${nodePath}:${process.env.PATH}`; } if (rebuild) { spawnSync(process.execPath, [ npmBin, 'rebuild', ], npmOptions); } if (install) { spawnSync(process.execPath, [ npmBin, 'install', '--ignore-scripts', '--no-save', ], npmOptions); } const testChild = spawn(process.execPath, [ npmBin, '--silent', 'run', ...testArgs, ], Object.assign({}, npmOptions, { stdio: 'pipe' })); testChild.stdout.pipe(process.stdout); testChild.stderr.pipe(process.stderr); if (logfile) { const logStream = createWriteStream(logfile); testChild.stdout.pipe(logStream); } testChild.on('exit', () => { tmpDir.refresh(); rmdirSync(tmpDir.path); }); } function parseArgs(args) { let srcDir; let rebuild = false; let install = false; let logfile = null; const testArgs = []; args.forEach((arg) => { if (srcDir) { testArgs.push(arg); return; } if (arg === '--install') { install = true; } else if (arg === '--rebuild') { rebuild = true; } else if (arg[0] !== '-') { srcDir = path.resolve(projectDir, arg); } else if (arg.startsWith('--logfile=')) { logfile = path.resolve(projectDir, arg.slice('--logfile='.length)); } else { throw new Error(`Unrecognized option ${arg}`); } }); if (!srcDir) { throw new Error('Expected a source directory'); } return { srcDir, install, rebuild, testArgs, logfile }; } runNPMPackageTests(parseArgs(process.argv.slice(2)));