UNPKG

6.25 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.captureOutput = captureOutput;
7exports.runCommand = runCommand;
8exports.runHook = runHook;
9const core_1 = require("@oclif/core");
10const ansis_1 = __importDefault(require("ansis"));
11const debug_1 = __importDefault(require("debug"));
12const node_path_1 = require("node:path");
13const debug = (0, debug_1.default)('oclif-test');
14function traverseFilePathUntil(filename, predicate) {
15 let current = filename;
16 while (!predicate(current)) {
17 current = (0, node_path_1.dirname)(current);
18 }
19 return current;
20}
21function findRoot() {
22 return (process.env.OCLIF_TEST_ROOT ??
23 // eslint-disable-next-line unicorn/prefer-module
24 Object.values(require.cache).find((m) => m?.children.includes(module))?.filename ??
25 traverseFilePathUntil(
26 // eslint-disable-next-line unicorn/prefer-module
27 require.main?.path ?? module.path, (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn'))));
28}
29function makeLoadOptions(loadOpts) {
30 return loadOpts ?? { root: findRoot() };
31}
32/**
33 * Split a string into an array of strings, preserving quoted substrings
34 *
35 * @example
36 * splitString('foo bar --name "foo"') // ['foo bar', '--name', 'foo']
37 * splitString('foo bar --name "foo bar"') // ['foo bar', '--name', 'foo bar']
38 * splitString('foo bar --name="foo bar"') // ['foo bar', '--name=foo bar']
39 * splitString('foo bar --name=foo bar') // ['foo bar', '--name=foo', 'bar']
40 *
41 * @param str input string
42 * @returns array of strings with quotes removed
43 */
44function splitString(str) {
45 return (str.match(/(?:[^\s"]+|"[^"]*")+/g) ?? []).map((s) => s.replaceAll(/^"|"$|(?<==)"/g, ''));
46}
47/**
48 * Capture the stderr and stdout output of a function
49 * @param fn async function to run
50 * @param opts options
51 * - print: Whether to print the output to the console
52 * - stripAnsi: Whether to strip ANSI codes from the output
53 * @returns {Promise<CaptureResult<T>>} Captured output
54 * - error: Error object if the function throws an error
55 * - result: Result of the function if it returns a value and succeeds
56 * - stderr: Captured stderr output
57 * - stdout: Captured stdout output
58 */
59async function captureOutput(fn, opts) {
60 const print = opts?.print ?? false;
61 const stripAnsi = opts?.stripAnsi ?? true;
62 const originals = {
63 NODE_ENV: process.env.NODE_ENV,
64 stderr: process.stderr.write,
65 stdout: process.stdout.write,
66 };
67 const output = {
68 stderr: [],
69 stdout: [],
70 };
71 const toString = (str) => (stripAnsi ? ansis_1.default.strip(str.toString()) : str.toString());
72 const getStderr = () => output.stderr.map((b) => toString(b)).join('');
73 const getStdout = () => output.stdout.map((b) => toString(b)).join('');
74 const mock = (std) => (str, encoding, cb) => {
75 output[std].push(str);
76 if (print) {
77 if (encoding !== null && typeof encoding === 'function') {
78 cb = encoding;
79 encoding = undefined;
80 }
81 originals[std].apply(process[std], [str, encoding, cb]);
82 }
83 else if (typeof cb === 'function')
84 cb();
85 return true;
86 };
87 process.stdout.write = mock('stdout');
88 process.stderr.write = mock('stderr');
89 process.env.NODE_ENV = 'test';
90 try {
91 const result = await fn();
92 return {
93 result: result,
94 stderr: getStderr(),
95 stdout: getStdout(),
96 };
97 }
98 catch (error) {
99 return {
100 ...(error instanceof core_1.Errors.CLIError && { error: { ...error, message: toString(error.message) } }),
101 ...(error instanceof Error && { error: { ...error, message: toString(error.message) } }),
102 stderr: getStderr(),
103 stdout: getStdout(),
104 };
105 }
106 finally {
107 process.stderr.write = originals.stderr;
108 process.stdout.write = originals.stdout;
109 process.env.NODE_ENV = originals.NODE_ENV;
110 }
111}
112/**
113 * Capture the stderr and stdout output of a command in your CLI
114 * @param args Command arguments, e.g. `['my:command', '--flag']` or `'my:command --flag'`
115 * @param loadOpts options for loading oclif `Config`
116 * @param captureOpts options for capturing the output
117 * - print: Whether to print the output to the console
118 * - stripAnsi: Whether to strip ANSI codes from the output
119 * @returns {Promise<CaptureResult<T>>} Captured output
120 * - error: Error object if the command throws an error
121 * - result: Result of the command if it returns a value and succeeds
122 * - stderr: Captured stderr output
123 * - stdout: Captured stdout output
124 */
125async function runCommand(args, loadOpts, captureOpts) {
126 const loadOptions = makeLoadOptions(loadOpts);
127 const argsArray = splitString((Array.isArray(args) ? args : [args]).join(' '));
128 const [id, ...rest] = argsArray;
129 const finalArgs = id === '.' ? rest : argsArray;
130 debug('loadOpts: %O', loadOptions);
131 debug('args: %O', finalArgs);
132 return captureOutput(async () => (0, core_1.run)(finalArgs, loadOptions), captureOpts);
133}
134/**
135 * Capture the stderr and stdout output of a hook in your CLI
136 * @param hook Hook name
137 * @param options options to pass to the hook
138 * @param loadOpts options for loading oclif `Config`
139 * @param captureOpts options for capturing the output
140 * - print: Whether to print the output to the console
141 * - stripAnsi: Whether to strip ANSI codes from the output
142 * @returns {Promise<CaptureResult<T>>} Captured output
143 * - error: Error object if the hook throws an error
144 * - result: Result of the hook if it returns a value and succeeds
145 * - stderr: Captured stderr output
146 * - stdout: Captured stdout output
147 */
148async function runHook(hook, options, loadOpts, captureOpts) {
149 const loadOptions = makeLoadOptions(loadOpts);
150 debug('loadOpts: %O', loadOptions);
151 return captureOutput(async () => {
152 const config = await core_1.Config.load(loadOptions);
153 return config.runHook(hook, options);
154 }, captureOpts);
155}