1#!/usr/bin/env node 2/** 3 * Usage: 4 * test-npm-package.js [--install] [--rebuild] <source> <test-arg>+ 5 * 6 * Everything after the <source> directory gets passed to `npm run` to build 7 * the test command. 8 * 9 * If `--install` is passed, we'll run a full `npm install` before running the 10 * test suite. Same for `--rebuild` and `npm rebuild`. 11 * 12 * We always use the node used to spawn this script and the `npm` version 13 * bundled in `deps/npm`. 14 * 15 * If an additional `--logfile=<filename>` option is passed before `<source>`, 16 * the stdout output of the test script will be written to that file. 17 */ 18'use strict'; 19const { spawn, spawnSync } = require('child_process'); 20const { createHash } = require('crypto'); 21const { createWriteStream, mkdirSync, rmdirSync } = require('fs'); 22const path = require('path'); 23 24const common = require('../test/common'); 25const tmpDir = require('../test/common/tmpdir'); 26 27const projectDir = path.resolve(__dirname, '..'); 28const npmBin = path.join(projectDir, 'deps', 'npm', 'bin', 'npm-cli.js'); 29const nodePath = path.dirname(process.execPath); 30 31function spawnCopyDeepSync(source, destination) { 32 if (common.isWindows) { 33 mkdirSync(destination); // Prevent interactive prompt 34 return spawnSync('xcopy.exe', ['/E', source, destination]); 35 } 36 return spawnSync('cp', ['-r', `${source}/`, destination]); 37} 38 39function runNPMPackageTests({ srcDir, install, rebuild, testArgs, logfile }) { 40 // Make sure we don't conflict with concurrent test runs 41 const srcHash = createHash('md5').update(srcDir).digest('hex'); 42 tmpDir.path = `${tmpDir.path}.npm.${srcHash}`; 43 tmpDir.refresh(); 44 45 const npmCache = path.join(tmpDir.path, 'npm-cache'); 46 const npmPrefix = path.join(tmpDir.path, 'npm-prefix'); 47 const npmTmp = path.join(tmpDir.path, 'npm-tmp'); 48 const npmUserconfig = path.join(tmpDir.path, 'npm-userconfig'); 49 const pkgDir = path.join(tmpDir.path, 'pkg'); 50 51 spawnCopyDeepSync(srcDir, pkgDir); 52 53 const npmOptions = { 54 cwd: pkgDir, 55 env: Object.assign({}, process.env, { 56 'npm_config_cache': npmCache, 57 'npm_config_prefix': npmPrefix, 58 'npm_config_tmp': npmTmp, 59 'npm_config_userconfig': npmUserconfig, 60 }), 61 stdio: 'inherit', 62 }; 63 64 if (common.isWindows) { 65 npmOptions.env.home = tmpDir.path; 66 npmOptions.env.Path = `${nodePath};${process.env.Path}`; 67 } else { 68 npmOptions.env.HOME = tmpDir.path; 69 npmOptions.env.PATH = `${nodePath}:${process.env.PATH}`; 70 } 71 72 if (rebuild) { 73 spawnSync(process.execPath, [ 74 npmBin, 75 'rebuild', 76 ], npmOptions); 77 } 78 79 if (install) { 80 spawnSync(process.execPath, [ 81 npmBin, 82 'install', 83 '--ignore-scripts', 84 '--no-save', 85 ], npmOptions); 86 } 87 88 const testChild = spawn(process.execPath, [ 89 npmBin, 90 '--silent', 91 'run', 92 ...testArgs, 93 ], Object.assign({}, npmOptions, { stdio: 'pipe' })); 94 95 testChild.stdout.pipe(process.stdout); 96 testChild.stderr.pipe(process.stderr); 97 98 if (logfile) { 99 const logStream = createWriteStream(logfile); 100 testChild.stdout.pipe(logStream); 101 } 102 103 testChild.on('exit', () => { 104 tmpDir.refresh(); 105 rmdirSync(tmpDir.path); 106 }); 107} 108 109function parseArgs(args) { 110 let srcDir; 111 let rebuild = false; 112 let install = false; 113 let logfile = null; 114 const testArgs = []; 115 args.forEach((arg) => { 116 if (srcDir) { 117 testArgs.push(arg); 118 return; 119 } 120 121 if (arg === '--install') { 122 install = true; 123 } else if (arg === '--rebuild') { 124 rebuild = true; 125 } else if (arg[0] !== '-') { 126 srcDir = path.resolve(projectDir, arg); 127 } else if (arg.startsWith('--logfile=')) { 128 logfile = path.resolve(projectDir, arg.slice('--logfile='.length)); 129 } else { 130 throw new Error(`Unrecognized option ${arg}`); 131 } 132 }); 133 if (!srcDir) { 134 throw new Error('Expected a source directory'); 135 } 136 return { srcDir, install, rebuild, testArgs, logfile }; 137} 138 139runNPMPackageTests(parseArgs(process.argv.slice(2))); 140