@salesforce/cli-plugins-testkit 2.4.3 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/execCmd.d.ts +52 -1
- package/lib/execCmd.js +122 -2
- package/package.json +1 -1
package/lib/execCmd.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { SpawnOptionsWithoutStdio } from 'child_process';
|
|
1
3
|
import { SfError } from '@salesforce/core';
|
|
2
4
|
import { Duration } from '@salesforce/kit';
|
|
3
|
-
import { AnyJson } from '@salesforce/ts-types';
|
|
5
|
+
import { AnyJson, Many } from '@salesforce/ts-types';
|
|
4
6
|
import { ExecOptions, ShellString } from 'shelljs';
|
|
5
7
|
declare type Collection = Record<string, AnyJson> | Array<Record<string, AnyJson>>;
|
|
6
8
|
export interface ExecCmdOptions extends ExecOptions {
|
|
@@ -108,4 +110,53 @@ export declare function execCmd<T = Collection>(cmd: string, options: ExecCmdOpt
|
|
|
108
110
|
async: true;
|
|
109
111
|
cli?: 'sf';
|
|
110
112
|
}): Promise<SfExecCmdResult<T>>;
|
|
113
|
+
export declare enum Interaction {
|
|
114
|
+
DOWN = "\u001B[B",
|
|
115
|
+
UP = "\u001B[A",
|
|
116
|
+
ENTER = "\r",
|
|
117
|
+
SELECT = " ",
|
|
118
|
+
Yes = "Y\r",
|
|
119
|
+
No = "N\r",
|
|
120
|
+
BACKSPACE = "\b"
|
|
121
|
+
}
|
|
122
|
+
export declare type InteractiveCommandExecutionResult = {
|
|
123
|
+
code: number | null;
|
|
124
|
+
stdout: string;
|
|
125
|
+
stderr: string;
|
|
126
|
+
duration: Duration;
|
|
127
|
+
};
|
|
128
|
+
export declare type InteractiveCommandExecutionOptions = {
|
|
129
|
+
ensureExitCode?: number;
|
|
130
|
+
} & SpawnOptionsWithoutStdio;
|
|
131
|
+
/**
|
|
132
|
+
* A map of questions and answers to be used in an interactive command.
|
|
133
|
+
*
|
|
134
|
+
* The questions are strings that will be used to match the question asked by the command.
|
|
135
|
+
*/
|
|
136
|
+
export declare type PromptAnswers = Record<string, Many<string>>;
|
|
137
|
+
/**
|
|
138
|
+
* Execute an interactive command.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```
|
|
142
|
+
* import { TestSession, execInteractiveCmd, Interaction } from '@salesforce/cli-plugins-testkit';
|
|
143
|
+
*
|
|
144
|
+
* const result = await execInteractiveCmd(
|
|
145
|
+
* 'dev generate plugin',
|
|
146
|
+
* {
|
|
147
|
+
* 'internal Salesforce team': Interaction.Yes,
|
|
148
|
+
* 'name of your new plugin': ['plugin-awesome', Interaction.ENTER],
|
|
149
|
+
* 'description for your plugin': ['a description', Interaction.ENTER],
|
|
150
|
+
* 'Select the existing "sf" commands you plan to extend': [
|
|
151
|
+
* Interaction.SELECT,
|
|
152
|
+
* Interaction.DOWN,
|
|
153
|
+
* Interaction.SELECT,
|
|
154
|
+
* Interaction.ENTER,
|
|
155
|
+
* ],
|
|
156
|
+
* },
|
|
157
|
+
* { cwd: session.dir, ensureExitCode: 0 }
|
|
158
|
+
* );
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare function execInteractiveCmd(command: string, answers: PromptAnswers, options?: InteractiveCommandExecutionOptions): Promise<InteractiveCommandExecutionResult>;
|
|
111
162
|
export {};
|
package/lib/execCmd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.execCmd = void 0;
|
|
3
|
+
exports.execInteractiveCmd = exports.Interaction = exports.execCmd = void 0;
|
|
4
4
|
/*
|
|
5
5
|
* Copyright (c) 2021, salesforce.com, inc.
|
|
6
6
|
* All rights reserved.
|
|
@@ -8,6 +8,7 @@ exports.execCmd = void 0;
|
|
|
8
8
|
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
9
9
|
*/
|
|
10
10
|
const fs = require("fs");
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
11
12
|
const path_1 = require("path");
|
|
12
13
|
const util_1 = require("util");
|
|
13
14
|
const kit_1 = require("@salesforce/kit");
|
|
@@ -66,7 +67,8 @@ const getExitCodeError = (cmd, expectedCode, output) => {
|
|
|
66
67
|
*/
|
|
67
68
|
const buildCmd = (cmdArgs, options) => {
|
|
68
69
|
const debug = (0, debug_1.default)('testkit:buildCmd');
|
|
69
|
-
const bin = kit_1.env.getString('TESTKIT_EXECUTABLE_PATH') ??
|
|
70
|
+
const bin = kit_1.env.getString('TESTKIT_EXECUTABLE_PATH') ??
|
|
71
|
+
(0, path_1.join)(process.cwd(), 'bin', process.platform === 'win32' ? 'dev.cmd' : 'dev');
|
|
70
72
|
const which = shelljs.which(bin);
|
|
71
73
|
let resolvedPath = (0, path_1.resolve)(bin);
|
|
72
74
|
// If which finds the path in the system path, use that.
|
|
@@ -159,4 +161,122 @@ function execCmd(cmd, options = DEFAULT_EXEC_OPTIONS) {
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
exports.execCmd = execCmd;
|
|
164
|
+
function toString(arrOrString) {
|
|
165
|
+
if (Array.isArray(arrOrString)) {
|
|
166
|
+
return arrOrString.join('');
|
|
167
|
+
}
|
|
168
|
+
return arrOrString;
|
|
169
|
+
}
|
|
170
|
+
function toArray(arrOrString) {
|
|
171
|
+
if (Array.isArray(arrOrString)) {
|
|
172
|
+
return arrOrString;
|
|
173
|
+
}
|
|
174
|
+
return [arrOrString];
|
|
175
|
+
}
|
|
176
|
+
var Interaction;
|
|
177
|
+
(function (Interaction) {
|
|
178
|
+
Interaction["DOWN"] = "\u001B[B";
|
|
179
|
+
Interaction["UP"] = "\u001B[A";
|
|
180
|
+
Interaction["ENTER"] = "\r";
|
|
181
|
+
Interaction["SELECT"] = " ";
|
|
182
|
+
Interaction["Yes"] = "Y\r";
|
|
183
|
+
Interaction["No"] = "N\r";
|
|
184
|
+
Interaction["BACKSPACE"] = "\b";
|
|
185
|
+
})(Interaction = exports.Interaction || (exports.Interaction = {}));
|
|
186
|
+
/**
|
|
187
|
+
* Execute an interactive command.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```
|
|
191
|
+
* import { TestSession, execInteractiveCmd, Interaction } from '@salesforce/cli-plugins-testkit';
|
|
192
|
+
*
|
|
193
|
+
* const result = await execInteractiveCmd(
|
|
194
|
+
* 'dev generate plugin',
|
|
195
|
+
* {
|
|
196
|
+
* 'internal Salesforce team': Interaction.Yes,
|
|
197
|
+
* 'name of your new plugin': ['plugin-awesome', Interaction.ENTER],
|
|
198
|
+
* 'description for your plugin': ['a description', Interaction.ENTER],
|
|
199
|
+
* 'Select the existing "sf" commands you plan to extend': [
|
|
200
|
+
* Interaction.SELECT,
|
|
201
|
+
* Interaction.DOWN,
|
|
202
|
+
* Interaction.SELECT,
|
|
203
|
+
* Interaction.ENTER,
|
|
204
|
+
* ],
|
|
205
|
+
* },
|
|
206
|
+
* { cwd: session.dir, ensureExitCode: 0 }
|
|
207
|
+
* );
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
async function execInteractiveCmd(command, answers, options = {}) {
|
|
211
|
+
const debug = (0, debug_1.default)('testkit:execInteractiveCmd');
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const bin = buildCmd('').trim();
|
|
214
|
+
const startTime = process.hrtime();
|
|
215
|
+
const child = (0, child_process_1.spawn)(bin, command.split(' '), { cwd: process.cwd(), ...options });
|
|
216
|
+
child.stdin.setDefaultEncoding('utf-8');
|
|
217
|
+
const seen = new Set();
|
|
218
|
+
const output = {
|
|
219
|
+
stdout: [],
|
|
220
|
+
stderr: [],
|
|
221
|
+
};
|
|
222
|
+
const scrollLimit = kit_1.env.getNumber('TESTKIT_SCROLL_LIMIT', 1000) ?? 1000;
|
|
223
|
+
let scrollCount = 0;
|
|
224
|
+
const handleData = (data, stream) => {
|
|
225
|
+
if (scrollCount > scrollLimit) {
|
|
226
|
+
reject(new Error(`Scroll limit of ${scrollLimit} reached`));
|
|
227
|
+
}
|
|
228
|
+
const current = data.toString();
|
|
229
|
+
debug(`${stream}: ${current}`);
|
|
230
|
+
output[stream].push(current);
|
|
231
|
+
const matchingQuestion = Object.keys(answers)
|
|
232
|
+
.filter((key) => !seen.has(key))
|
|
233
|
+
.find((key) => new RegExp(key).test(current));
|
|
234
|
+
if (!matchingQuestion)
|
|
235
|
+
return;
|
|
236
|
+
const answerString = toString(answers[matchingQuestion]);
|
|
237
|
+
const answerArray = toArray(answers[matchingQuestion]);
|
|
238
|
+
// If the answer includes a string that's NOT an Interactive enum value, then we need to scroll to it.
|
|
239
|
+
const scrollTarget = answerArray.find((answer) => !Object.values(Interaction).includes(answer));
|
|
240
|
+
const shouldScrollForAnswer = current.includes('❯') && scrollTarget;
|
|
241
|
+
if (shouldScrollForAnswer) {
|
|
242
|
+
const regex = /(?<=❯\s)(.*)/g;
|
|
243
|
+
const selected = (current.match(regex) ?? [''])[0].trim();
|
|
244
|
+
if (selected === scrollTarget) {
|
|
245
|
+
seen.add(matchingQuestion);
|
|
246
|
+
child.stdin.write(Interaction.ENTER);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
scrollCount += 1;
|
|
250
|
+
child.stdin.write(Interaction.DOWN);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
seen.add(matchingQuestion);
|
|
255
|
+
scrollCount = 0;
|
|
256
|
+
child.stdin.write(answerString);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
child.stdout.on('data', (data) => handleData(data, 'stdout'));
|
|
260
|
+
child.stderr.on('data', (data) => handleData(data, 'stderr'));
|
|
261
|
+
child.on('close', (code) => {
|
|
262
|
+
debug(`child process exited with code ${code}`);
|
|
263
|
+
child.stdin.end();
|
|
264
|
+
const result = {
|
|
265
|
+
code,
|
|
266
|
+
stdout: stripAnsi(output.stdout.join('\n')),
|
|
267
|
+
stderr: stripAnsi(output.stderr.join('\n')),
|
|
268
|
+
duration: hrtimeToMillisDuration(process.hrtime(startTime)),
|
|
269
|
+
};
|
|
270
|
+
if ((0, ts_types_1.isNumber)(options.ensureExitCode) && code !== options.ensureExitCode) {
|
|
271
|
+
reject(getExitCodeError(command, options.ensureExitCode, {
|
|
272
|
+
stdout: result.stdout,
|
|
273
|
+
stderr: result.stderr,
|
|
274
|
+
code: result.code,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
resolve(result);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
exports.execInteractiveCmd = execInteractiveCmd;
|
|
162
282
|
//# sourceMappingURL=execCmd.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/cli-plugins-testkit",
|
|
3
3
|
"description": "Provides test utilities to assist Salesforce CLI plug-in authors with writing non-unit tests (NUT).",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.1",
|
|
5
5
|
"author": "Salesforce",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "lib/index.js",
|