UNPKG

3.17 kBJavaScriptView Raw
1import { Errors, ux } from '@oclif/core';
2import makeDebug from 'debug';
3import { spawn as cpSpawn } from 'node:child_process';
4import { npmRunPathEnv } from 'npm-run-path';
5const debug = makeDebug('@oclif/plugin-plugins:spawn');
6export async function spawn(modulePath, args = [], { cwd, logLevel }) {
7 return new Promise((resolve, reject) => {
8 // On windows, the global path to npm could be .cmd, .exe, or .js. If it's a .js file, we need to run it with node.
9 if (process.platform === 'win32' && modulePath.endsWith('.js')) {
10 args.unshift(`"${modulePath}"`);
11 modulePath = 'node';
12 }
13 debug('modulePath', modulePath);
14 debug('args', args);
15 const spawned = cpSpawn(modulePath, args, {
16 cwd,
17 env: {
18 ...npmRunPathEnv(),
19 // Disable husky hooks because a plugin might be trying to install them, which will
20 // break the install since the install location isn't a .git directory.
21 HUSKY: '0',
22 },
23 stdio: 'pipe',
24 windowsVerbatimArguments: true,
25 ...(process.platform === 'win32' && modulePath.toLowerCase().endsWith('.cmd') && { shell: true }),
26 });
27 const possibleLastLinesOfNpmInstall = ['up to date', 'added'];
28 const stderr = [];
29 const stdout = [];
30 const loggedStderr = [];
31 const loggedStdout = [];
32 const shouldPrint = (str) => {
33 // For ux cleanliness purposes, don't print the final line of npm install output if
34 // the log level is 'notice' and there's no other output.
35 const noOtherOutput = loggedStderr.length === 0 && loggedStdout.length === 0;
36 const isLastLine = possibleLastLinesOfNpmInstall.some((line) => str.startsWith(line));
37 if (noOtherOutput && isLastLine && logLevel === 'notice') {
38 return false;
39 }
40 return logLevel !== 'silent';
41 };
42 spawned.stderr?.setEncoding('utf8');
43 spawned.stderr?.on('data', (d) => {
44 const output = d.toString().trim();
45 stderr.push(output);
46 if (shouldPrint(output)) {
47 loggedStderr.push(output);
48 ux.stdout(output);
49 }
50 else
51 debug(output);
52 });
53 spawned.stdout?.setEncoding('utf8');
54 spawned.stdout?.on('data', (d) => {
55 const output = d.toString().trim();
56 stdout.push(output);
57 if (shouldPrint(output)) {
58 loggedStdout.push(output);
59 ux.stdout(output);
60 }
61 else
62 debug(output);
63 });
64 spawned.on('error', reject);
65 spawned.on('exit', (code) => {
66 if (code === 0) {
67 resolve({ stderr, stdout });
68 }
69 else {
70 reject(new Errors.CLIError(`${modulePath} ${args.join(' ')} exited with code ${code}`, {
71 suggestions: ['Run with DEBUG=@oclif/plugin-plugins* to see debug output.'],
72 }));
73 }
74 });
75 });
76}