e2e-ai 1.0.0
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/README.md +339 -0
- package/agents/init-agent.md +75 -0
- package/agents/playwright-generator-agent.md +100 -0
- package/agents/qa-testcase-agent.md +81 -0
- package/agents/refactor-agent.md +55 -0
- package/agents/scenario-agent.md +94 -0
- package/agents/self-healing-agent.md +94 -0
- package/agents/transcript-agent.md +111 -0
- package/dist/cli-7hdsk36p.js +12500 -0
- package/dist/cli-mesq5m0a.js +57 -0
- package/dist/cli-wckvcay0.js +48 -0
- package/dist/cli.js +4292 -0
- package/dist/config/schema.js +9 -0
- package/dist/index-4fsmqzap.js +7073 -0
- package/dist/index.js +15 -0
- package/package.json +44 -0
- package/scripts/auth/setup-auth.mjs +118 -0
- package/scripts/codegen-env.mjs +337 -0
- package/scripts/exporters/zephyr-json-to-import-xml.ts +156 -0
- package/scripts/trace/replay-with-trace.mjs +95 -0
- package/scripts/trace/replay.config.ts +37 -0
- package/scripts/voice/merger.mjs +88 -0
- package/scripts/voice/recorder.mjs +54 -0
- package/scripts/voice/transcriber.mjs +52 -0
- package/templates/e2e-ai.context.example.md +93 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,4292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getPackageRoot,
|
|
4
|
+
getProjectRoot,
|
|
5
|
+
loadConfig
|
|
6
|
+
} from "./cli-mesq5m0a.js";
|
|
7
|
+
import"./cli-7hdsk36p.js";
|
|
8
|
+
import {
|
|
9
|
+
__commonJS,
|
|
10
|
+
__require,
|
|
11
|
+
__toESM
|
|
12
|
+
} from "./cli-wckvcay0.js";
|
|
13
|
+
|
|
14
|
+
// ../../node_modules/dotenv/package.json
|
|
15
|
+
var require_package = __commonJS((exports, module) => {
|
|
16
|
+
module.exports = {
|
|
17
|
+
name: "dotenv",
|
|
18
|
+
version: "17.2.3",
|
|
19
|
+
description: "Loads environment variables from .env file",
|
|
20
|
+
main: "lib/main.js",
|
|
21
|
+
types: "lib/main.d.ts",
|
|
22
|
+
exports: {
|
|
23
|
+
".": {
|
|
24
|
+
types: "./lib/main.d.ts",
|
|
25
|
+
require: "./lib/main.js",
|
|
26
|
+
default: "./lib/main.js"
|
|
27
|
+
},
|
|
28
|
+
"./config": "./config.js",
|
|
29
|
+
"./config.js": "./config.js",
|
|
30
|
+
"./lib/env-options": "./lib/env-options.js",
|
|
31
|
+
"./lib/env-options.js": "./lib/env-options.js",
|
|
32
|
+
"./lib/cli-options": "./lib/cli-options.js",
|
|
33
|
+
"./lib/cli-options.js": "./lib/cli-options.js",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
scripts: {
|
|
37
|
+
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
38
|
+
lint: "standard",
|
|
39
|
+
pretest: "npm run lint && npm run dts-check",
|
|
40
|
+
test: "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
|
|
41
|
+
"test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
|
|
42
|
+
prerelease: "npm test",
|
|
43
|
+
release: "standard-version"
|
|
44
|
+
},
|
|
45
|
+
repository: {
|
|
46
|
+
type: "git",
|
|
47
|
+
url: "git://github.com/motdotla/dotenv.git"
|
|
48
|
+
},
|
|
49
|
+
homepage: "https://github.com/motdotla/dotenv#readme",
|
|
50
|
+
funding: "https://dotenvx.com",
|
|
51
|
+
keywords: [
|
|
52
|
+
"dotenv",
|
|
53
|
+
"env",
|
|
54
|
+
".env",
|
|
55
|
+
"environment",
|
|
56
|
+
"variables",
|
|
57
|
+
"config",
|
|
58
|
+
"settings"
|
|
59
|
+
],
|
|
60
|
+
readmeFilename: "README.md",
|
|
61
|
+
license: "BSD-2-Clause",
|
|
62
|
+
devDependencies: {
|
|
63
|
+
"@types/node": "^18.11.3",
|
|
64
|
+
decache: "^4.6.2",
|
|
65
|
+
sinon: "^14.0.1",
|
|
66
|
+
standard: "^17.0.0",
|
|
67
|
+
"standard-version": "^9.5.0",
|
|
68
|
+
tap: "^19.2.0",
|
|
69
|
+
typescript: "^4.8.4"
|
|
70
|
+
},
|
|
71
|
+
engines: {
|
|
72
|
+
node: ">=12"
|
|
73
|
+
},
|
|
74
|
+
browser: {
|
|
75
|
+
fs: false
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ../../node_modules/dotenv/lib/main.js
|
|
81
|
+
var require_main = __commonJS((exports, module) => {
|
|
82
|
+
var fs = __require("fs");
|
|
83
|
+
var path = __require("path");
|
|
84
|
+
var os = __require("os");
|
|
85
|
+
var crypto = __require("crypto");
|
|
86
|
+
var packageJson = require_package();
|
|
87
|
+
var version = packageJson.version;
|
|
88
|
+
var TIPS = [
|
|
89
|
+
"\uD83D\uDD10 encrypt with Dotenvx: https://dotenvx.com",
|
|
90
|
+
"\uD83D\uDD10 prevent committing .env to code: https://dotenvx.com/precommit",
|
|
91
|
+
"\uD83D\uDD10 prevent building .env in docker: https://dotenvx.com/prebuild",
|
|
92
|
+
"\uD83D\uDCE1 add observability to secrets: https://dotenvx.com/ops",
|
|
93
|
+
"\uD83D\uDC65 sync secrets across teammates & machines: https://dotenvx.com/ops",
|
|
94
|
+
"\uD83D\uDDC2️ backup and recover secrets: https://dotenvx.com/ops",
|
|
95
|
+
"✅ audit secrets and track compliance: https://dotenvx.com/ops",
|
|
96
|
+
"\uD83D\uDD04 add secrets lifecycle management: https://dotenvx.com/ops",
|
|
97
|
+
"\uD83D\uDD11 add access controls to secrets: https://dotenvx.com/ops",
|
|
98
|
+
"\uD83D\uDEE0️ run anywhere with `dotenvx run -- yourcommand`",
|
|
99
|
+
"⚙️ specify custom .env file path with { path: '/custom/path/.env' }",
|
|
100
|
+
"⚙️ enable debug logging with { debug: true }",
|
|
101
|
+
"⚙️ override existing env vars with { override: true }",
|
|
102
|
+
"⚙️ suppress all logs with { quiet: true }",
|
|
103
|
+
"⚙️ write to custom object with { processEnv: myObject }",
|
|
104
|
+
"⚙️ load multiple .env files with { path: ['.env.local', '.env'] }"
|
|
105
|
+
];
|
|
106
|
+
function _getRandomTip() {
|
|
107
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
108
|
+
}
|
|
109
|
+
function parseBoolean(value) {
|
|
110
|
+
if (typeof value === "string") {
|
|
111
|
+
return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
|
|
112
|
+
}
|
|
113
|
+
return Boolean(value);
|
|
114
|
+
}
|
|
115
|
+
function supportsAnsi() {
|
|
116
|
+
return process.stdout.isTTY;
|
|
117
|
+
}
|
|
118
|
+
function dim(text) {
|
|
119
|
+
return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
|
|
120
|
+
}
|
|
121
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
122
|
+
function parse(src) {
|
|
123
|
+
const obj = {};
|
|
124
|
+
let lines = src.toString();
|
|
125
|
+
lines = lines.replace(/\r\n?/mg, `
|
|
126
|
+
`);
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
129
|
+
const key = match[1];
|
|
130
|
+
let value = match[2] || "";
|
|
131
|
+
value = value.trim();
|
|
132
|
+
const maybeQuote = value[0];
|
|
133
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
|
|
134
|
+
if (maybeQuote === '"') {
|
|
135
|
+
value = value.replace(/\\n/g, `
|
|
136
|
+
`);
|
|
137
|
+
value = value.replace(/\\r/g, "\r");
|
|
138
|
+
}
|
|
139
|
+
obj[key] = value;
|
|
140
|
+
}
|
|
141
|
+
return obj;
|
|
142
|
+
}
|
|
143
|
+
function _parseVault(options) {
|
|
144
|
+
options = options || {};
|
|
145
|
+
const vaultPath = _vaultPath(options);
|
|
146
|
+
options.path = vaultPath;
|
|
147
|
+
const result = DotenvModule.configDotenv(options);
|
|
148
|
+
if (!result.parsed) {
|
|
149
|
+
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
150
|
+
err.code = "MISSING_DATA";
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
const keys = _dotenvKey(options).split(",");
|
|
154
|
+
const length = keys.length;
|
|
155
|
+
let decrypted;
|
|
156
|
+
for (let i = 0;i < length; i++) {
|
|
157
|
+
try {
|
|
158
|
+
const key = keys[i].trim();
|
|
159
|
+
const attrs = _instructions(result, key);
|
|
160
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
161
|
+
break;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (i + 1 >= length) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return DotenvModule.parse(decrypted);
|
|
169
|
+
}
|
|
170
|
+
function _warn(message) {
|
|
171
|
+
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
172
|
+
}
|
|
173
|
+
function _debug(message) {
|
|
174
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
175
|
+
}
|
|
176
|
+
function _log(message) {
|
|
177
|
+
console.log(`[dotenv@${version}] ${message}`);
|
|
178
|
+
}
|
|
179
|
+
function _dotenvKey(options) {
|
|
180
|
+
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
181
|
+
return options.DOTENV_KEY;
|
|
182
|
+
}
|
|
183
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
184
|
+
return process.env.DOTENV_KEY;
|
|
185
|
+
}
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
function _instructions(result, dotenvKey) {
|
|
189
|
+
let uri;
|
|
190
|
+
try {
|
|
191
|
+
uri = new URL(dotenvKey);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error.code === "ERR_INVALID_URL") {
|
|
194
|
+
const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
195
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
const key = uri.password;
|
|
201
|
+
if (!key) {
|
|
202
|
+
const err = new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
203
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
const environment = uri.searchParams.get("environment");
|
|
207
|
+
if (!environment) {
|
|
208
|
+
const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
209
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
213
|
+
const ciphertext = result.parsed[environmentKey];
|
|
214
|
+
if (!ciphertext) {
|
|
215
|
+
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
216
|
+
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
return { ciphertext, key };
|
|
220
|
+
}
|
|
221
|
+
function _vaultPath(options) {
|
|
222
|
+
let possibleVaultPath = null;
|
|
223
|
+
if (options && options.path && options.path.length > 0) {
|
|
224
|
+
if (Array.isArray(options.path)) {
|
|
225
|
+
for (const filepath of options.path) {
|
|
226
|
+
if (fs.existsSync(filepath)) {
|
|
227
|
+
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
235
|
+
}
|
|
236
|
+
if (fs.existsSync(possibleVaultPath)) {
|
|
237
|
+
return possibleVaultPath;
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function _resolveHome(envPath) {
|
|
242
|
+
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
243
|
+
}
|
|
244
|
+
function _configVault(options) {
|
|
245
|
+
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
246
|
+
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
247
|
+
if (debug || !quiet) {
|
|
248
|
+
_log("Loading env from encrypted .env.vault");
|
|
249
|
+
}
|
|
250
|
+
const parsed = DotenvModule._parseVault(options);
|
|
251
|
+
let processEnv = process.env;
|
|
252
|
+
if (options && options.processEnv != null) {
|
|
253
|
+
processEnv = options.processEnv;
|
|
254
|
+
}
|
|
255
|
+
DotenvModule.populate(processEnv, parsed, options);
|
|
256
|
+
return { parsed };
|
|
257
|
+
}
|
|
258
|
+
function configDotenv(options) {
|
|
259
|
+
const dotenvPath = path.resolve(process.cwd(), ".env");
|
|
260
|
+
let encoding = "utf8";
|
|
261
|
+
let processEnv = process.env;
|
|
262
|
+
if (options && options.processEnv != null) {
|
|
263
|
+
processEnv = options.processEnv;
|
|
264
|
+
}
|
|
265
|
+
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
266
|
+
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
267
|
+
if (options && options.encoding) {
|
|
268
|
+
encoding = options.encoding;
|
|
269
|
+
} else {
|
|
270
|
+
if (debug) {
|
|
271
|
+
_debug("No encoding is specified. UTF-8 is used by default");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
let optionPaths = [dotenvPath];
|
|
275
|
+
if (options && options.path) {
|
|
276
|
+
if (!Array.isArray(options.path)) {
|
|
277
|
+
optionPaths = [_resolveHome(options.path)];
|
|
278
|
+
} else {
|
|
279
|
+
optionPaths = [];
|
|
280
|
+
for (const filepath of options.path) {
|
|
281
|
+
optionPaths.push(_resolveHome(filepath));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
let lastError;
|
|
286
|
+
const parsedAll = {};
|
|
287
|
+
for (const path2 of optionPaths) {
|
|
288
|
+
try {
|
|
289
|
+
const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
|
|
290
|
+
DotenvModule.populate(parsedAll, parsed, options);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
if (debug) {
|
|
293
|
+
_debug(`Failed to load ${path2} ${e.message}`);
|
|
294
|
+
}
|
|
295
|
+
lastError = e;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
299
|
+
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
300
|
+
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
301
|
+
if (debug || !quiet) {
|
|
302
|
+
const keysCount = Object.keys(populated).length;
|
|
303
|
+
const shortPaths = [];
|
|
304
|
+
for (const filePath of optionPaths) {
|
|
305
|
+
try {
|
|
306
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
307
|
+
shortPaths.push(relative);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
if (debug) {
|
|
310
|
+
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
311
|
+
}
|
|
312
|
+
lastError = e;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
_log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim(`-- tip: ${_getRandomTip()}`)}`);
|
|
316
|
+
}
|
|
317
|
+
if (lastError) {
|
|
318
|
+
return { parsed: parsedAll, error: lastError };
|
|
319
|
+
} else {
|
|
320
|
+
return { parsed: parsedAll };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function config(options) {
|
|
324
|
+
if (_dotenvKey(options).length === 0) {
|
|
325
|
+
return DotenvModule.configDotenv(options);
|
|
326
|
+
}
|
|
327
|
+
const vaultPath = _vaultPath(options);
|
|
328
|
+
if (!vaultPath) {
|
|
329
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
330
|
+
return DotenvModule.configDotenv(options);
|
|
331
|
+
}
|
|
332
|
+
return DotenvModule._configVault(options);
|
|
333
|
+
}
|
|
334
|
+
function decrypt(encrypted, keyStr) {
|
|
335
|
+
const key = Buffer.from(keyStr.slice(-64), "hex");
|
|
336
|
+
let ciphertext = Buffer.from(encrypted, "base64");
|
|
337
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
338
|
+
const authTag = ciphertext.subarray(-16);
|
|
339
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
340
|
+
try {
|
|
341
|
+
const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
|
|
342
|
+
aesgcm.setAuthTag(authTag);
|
|
343
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
344
|
+
} catch (error) {
|
|
345
|
+
const isRange = error instanceof RangeError;
|
|
346
|
+
const invalidKeyLength = error.message === "Invalid key length";
|
|
347
|
+
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
348
|
+
if (isRange || invalidKeyLength) {
|
|
349
|
+
const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
350
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
351
|
+
throw err;
|
|
352
|
+
} else if (decryptionFailed) {
|
|
353
|
+
const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
354
|
+
err.code = "DECRYPTION_FAILED";
|
|
355
|
+
throw err;
|
|
356
|
+
} else {
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function populate(processEnv, parsed, options = {}) {
|
|
362
|
+
const debug = Boolean(options && options.debug);
|
|
363
|
+
const override = Boolean(options && options.override);
|
|
364
|
+
const populated = {};
|
|
365
|
+
if (typeof parsed !== "object") {
|
|
366
|
+
const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
367
|
+
err.code = "OBJECT_REQUIRED";
|
|
368
|
+
throw err;
|
|
369
|
+
}
|
|
370
|
+
for (const key of Object.keys(parsed)) {
|
|
371
|
+
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
372
|
+
if (override === true) {
|
|
373
|
+
processEnv[key] = parsed[key];
|
|
374
|
+
populated[key] = parsed[key];
|
|
375
|
+
}
|
|
376
|
+
if (debug) {
|
|
377
|
+
if (override === true) {
|
|
378
|
+
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
379
|
+
} else {
|
|
380
|
+
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
processEnv[key] = parsed[key];
|
|
385
|
+
populated[key] = parsed[key];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return populated;
|
|
389
|
+
}
|
|
390
|
+
var DotenvModule = {
|
|
391
|
+
configDotenv,
|
|
392
|
+
_configVault,
|
|
393
|
+
_parseVault,
|
|
394
|
+
config,
|
|
395
|
+
decrypt,
|
|
396
|
+
parse,
|
|
397
|
+
populate
|
|
398
|
+
};
|
|
399
|
+
exports.configDotenv = DotenvModule.configDotenv;
|
|
400
|
+
exports._configVault = DotenvModule._configVault;
|
|
401
|
+
exports._parseVault = DotenvModule._parseVault;
|
|
402
|
+
exports.config = DotenvModule.config;
|
|
403
|
+
exports.decrypt = DotenvModule.decrypt;
|
|
404
|
+
exports.parse = DotenvModule.parse;
|
|
405
|
+
exports.populate = DotenvModule.populate;
|
|
406
|
+
module.exports = DotenvModule;
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// ../../node_modules/dotenv/lib/env-options.js
|
|
410
|
+
var require_env_options = __commonJS((exports, module) => {
|
|
411
|
+
var options = {};
|
|
412
|
+
if (process.env.DOTENV_CONFIG_ENCODING != null) {
|
|
413
|
+
options.encoding = process.env.DOTENV_CONFIG_ENCODING;
|
|
414
|
+
}
|
|
415
|
+
if (process.env.DOTENV_CONFIG_PATH != null) {
|
|
416
|
+
options.path = process.env.DOTENV_CONFIG_PATH;
|
|
417
|
+
}
|
|
418
|
+
if (process.env.DOTENV_CONFIG_QUIET != null) {
|
|
419
|
+
options.quiet = process.env.DOTENV_CONFIG_QUIET;
|
|
420
|
+
}
|
|
421
|
+
if (process.env.DOTENV_CONFIG_DEBUG != null) {
|
|
422
|
+
options.debug = process.env.DOTENV_CONFIG_DEBUG;
|
|
423
|
+
}
|
|
424
|
+
if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
|
|
425
|
+
options.override = process.env.DOTENV_CONFIG_OVERRIDE;
|
|
426
|
+
}
|
|
427
|
+
if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {
|
|
428
|
+
options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
|
|
429
|
+
}
|
|
430
|
+
module.exports = options;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// ../../node_modules/dotenv/lib/cli-options.js
|
|
434
|
+
var require_cli_options = __commonJS((exports, module) => {
|
|
435
|
+
var re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/;
|
|
436
|
+
module.exports = function optionMatcher(args) {
|
|
437
|
+
const options = args.reduce(function(acc, cur) {
|
|
438
|
+
const matches = cur.match(re);
|
|
439
|
+
if (matches) {
|
|
440
|
+
acc[matches[1]] = matches[2];
|
|
441
|
+
}
|
|
442
|
+
return acc;
|
|
443
|
+
}, {});
|
|
444
|
+
if (!("quiet" in options)) {
|
|
445
|
+
options.quiet = "true";
|
|
446
|
+
}
|
|
447
|
+
return options;
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// ../../node_modules/dotenv/config.js
|
|
452
|
+
var require_config = __commonJS(() => {
|
|
453
|
+
(function() {
|
|
454
|
+
require_main().config(Object.assign({}, require_env_options(), require_cli_options()(process.argv)));
|
|
455
|
+
})();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// ../../node_modules/commander/lib/error.js
|
|
459
|
+
var require_error = __commonJS((exports) => {
|
|
460
|
+
class CommanderError extends Error {
|
|
461
|
+
constructor(exitCode, code, message) {
|
|
462
|
+
super(message);
|
|
463
|
+
Error.captureStackTrace(this, this.constructor);
|
|
464
|
+
this.name = this.constructor.name;
|
|
465
|
+
this.code = code;
|
|
466
|
+
this.exitCode = exitCode;
|
|
467
|
+
this.nestedError = undefined;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
class InvalidArgumentError extends CommanderError {
|
|
472
|
+
constructor(message) {
|
|
473
|
+
super(1, "commander.invalidArgument", message);
|
|
474
|
+
Error.captureStackTrace(this, this.constructor);
|
|
475
|
+
this.name = this.constructor.name;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
exports.CommanderError = CommanderError;
|
|
479
|
+
exports.InvalidArgumentError = InvalidArgumentError;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ../../node_modules/commander/lib/argument.js
|
|
483
|
+
var require_argument = __commonJS((exports) => {
|
|
484
|
+
var { InvalidArgumentError } = require_error();
|
|
485
|
+
|
|
486
|
+
class Argument {
|
|
487
|
+
constructor(name, description) {
|
|
488
|
+
this.description = description || "";
|
|
489
|
+
this.variadic = false;
|
|
490
|
+
this.parseArg = undefined;
|
|
491
|
+
this.defaultValue = undefined;
|
|
492
|
+
this.defaultValueDescription = undefined;
|
|
493
|
+
this.argChoices = undefined;
|
|
494
|
+
switch (name[0]) {
|
|
495
|
+
case "<":
|
|
496
|
+
this.required = true;
|
|
497
|
+
this._name = name.slice(1, -1);
|
|
498
|
+
break;
|
|
499
|
+
case "[":
|
|
500
|
+
this.required = false;
|
|
501
|
+
this._name = name.slice(1, -1);
|
|
502
|
+
break;
|
|
503
|
+
default:
|
|
504
|
+
this.required = true;
|
|
505
|
+
this._name = name;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
if (this._name.length > 3 && this._name.slice(-3) === "...") {
|
|
509
|
+
this.variadic = true;
|
|
510
|
+
this._name = this._name.slice(0, -3);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
name() {
|
|
514
|
+
return this._name;
|
|
515
|
+
}
|
|
516
|
+
_concatValue(value, previous) {
|
|
517
|
+
if (previous === this.defaultValue || !Array.isArray(previous)) {
|
|
518
|
+
return [value];
|
|
519
|
+
}
|
|
520
|
+
return previous.concat(value);
|
|
521
|
+
}
|
|
522
|
+
default(value, description) {
|
|
523
|
+
this.defaultValue = value;
|
|
524
|
+
this.defaultValueDescription = description;
|
|
525
|
+
return this;
|
|
526
|
+
}
|
|
527
|
+
argParser(fn) {
|
|
528
|
+
this.parseArg = fn;
|
|
529
|
+
return this;
|
|
530
|
+
}
|
|
531
|
+
choices(values) {
|
|
532
|
+
this.argChoices = values.slice();
|
|
533
|
+
this.parseArg = (arg, previous) => {
|
|
534
|
+
if (!this.argChoices.includes(arg)) {
|
|
535
|
+
throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
|
|
536
|
+
}
|
|
537
|
+
if (this.variadic) {
|
|
538
|
+
return this._concatValue(arg, previous);
|
|
539
|
+
}
|
|
540
|
+
return arg;
|
|
541
|
+
};
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
argRequired() {
|
|
545
|
+
this.required = true;
|
|
546
|
+
return this;
|
|
547
|
+
}
|
|
548
|
+
argOptional() {
|
|
549
|
+
this.required = false;
|
|
550
|
+
return this;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function humanReadableArgName(arg) {
|
|
554
|
+
const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
|
|
555
|
+
return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
|
|
556
|
+
}
|
|
557
|
+
exports.Argument = Argument;
|
|
558
|
+
exports.humanReadableArgName = humanReadableArgName;
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// ../../node_modules/commander/lib/help.js
|
|
562
|
+
var require_help = __commonJS((exports) => {
|
|
563
|
+
var { humanReadableArgName } = require_argument();
|
|
564
|
+
|
|
565
|
+
class Help {
|
|
566
|
+
constructor() {
|
|
567
|
+
this.helpWidth = undefined;
|
|
568
|
+
this.minWidthToWrap = 40;
|
|
569
|
+
this.sortSubcommands = false;
|
|
570
|
+
this.sortOptions = false;
|
|
571
|
+
this.showGlobalOptions = false;
|
|
572
|
+
}
|
|
573
|
+
prepareContext(contextOptions) {
|
|
574
|
+
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
575
|
+
}
|
|
576
|
+
visibleCommands(cmd) {
|
|
577
|
+
const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
|
|
578
|
+
const helpCommand = cmd._getHelpCommand();
|
|
579
|
+
if (helpCommand && !helpCommand._hidden) {
|
|
580
|
+
visibleCommands.push(helpCommand);
|
|
581
|
+
}
|
|
582
|
+
if (this.sortSubcommands) {
|
|
583
|
+
visibleCommands.sort((a, b) => {
|
|
584
|
+
return a.name().localeCompare(b.name());
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return visibleCommands;
|
|
588
|
+
}
|
|
589
|
+
compareOptions(a, b) {
|
|
590
|
+
const getSortKey = (option) => {
|
|
591
|
+
return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
|
|
592
|
+
};
|
|
593
|
+
return getSortKey(a).localeCompare(getSortKey(b));
|
|
594
|
+
}
|
|
595
|
+
visibleOptions(cmd) {
|
|
596
|
+
const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
|
597
|
+
const helpOption = cmd._getHelpOption();
|
|
598
|
+
if (helpOption && !helpOption.hidden) {
|
|
599
|
+
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
|
|
600
|
+
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
|
|
601
|
+
if (!removeShort && !removeLong) {
|
|
602
|
+
visibleOptions.push(helpOption);
|
|
603
|
+
} else if (helpOption.long && !removeLong) {
|
|
604
|
+
visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
|
|
605
|
+
} else if (helpOption.short && !removeShort) {
|
|
606
|
+
visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (this.sortOptions) {
|
|
610
|
+
visibleOptions.sort(this.compareOptions);
|
|
611
|
+
}
|
|
612
|
+
return visibleOptions;
|
|
613
|
+
}
|
|
614
|
+
visibleGlobalOptions(cmd) {
|
|
615
|
+
if (!this.showGlobalOptions)
|
|
616
|
+
return [];
|
|
617
|
+
const globalOptions = [];
|
|
618
|
+
for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
|
|
619
|
+
const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden);
|
|
620
|
+
globalOptions.push(...visibleOptions);
|
|
621
|
+
}
|
|
622
|
+
if (this.sortOptions) {
|
|
623
|
+
globalOptions.sort(this.compareOptions);
|
|
624
|
+
}
|
|
625
|
+
return globalOptions;
|
|
626
|
+
}
|
|
627
|
+
visibleArguments(cmd) {
|
|
628
|
+
if (cmd._argsDescription) {
|
|
629
|
+
cmd.registeredArguments.forEach((argument) => {
|
|
630
|
+
argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
if (cmd.registeredArguments.find((argument) => argument.description)) {
|
|
634
|
+
return cmd.registeredArguments;
|
|
635
|
+
}
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
subcommandTerm(cmd) {
|
|
639
|
+
const args = cmd.registeredArguments.map((arg) => humanReadableArgName(arg)).join(" ");
|
|
640
|
+
return cmd._name + (cmd._aliases[0] ? "|" + cmd._aliases[0] : "") + (cmd.options.length ? " [options]" : "") + (args ? " " + args : "");
|
|
641
|
+
}
|
|
642
|
+
optionTerm(option) {
|
|
643
|
+
return option.flags;
|
|
644
|
+
}
|
|
645
|
+
argumentTerm(argument) {
|
|
646
|
+
return argument.name();
|
|
647
|
+
}
|
|
648
|
+
longestSubcommandTermLength(cmd, helper) {
|
|
649
|
+
return helper.visibleCommands(cmd).reduce((max, command) => {
|
|
650
|
+
return Math.max(max, this.displayWidth(helper.styleSubcommandTerm(helper.subcommandTerm(command))));
|
|
651
|
+
}, 0);
|
|
652
|
+
}
|
|
653
|
+
longestOptionTermLength(cmd, helper) {
|
|
654
|
+
return helper.visibleOptions(cmd).reduce((max, option) => {
|
|
655
|
+
return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
|
|
656
|
+
}, 0);
|
|
657
|
+
}
|
|
658
|
+
longestGlobalOptionTermLength(cmd, helper) {
|
|
659
|
+
return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
|
|
660
|
+
return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
|
|
661
|
+
}, 0);
|
|
662
|
+
}
|
|
663
|
+
longestArgumentTermLength(cmd, helper) {
|
|
664
|
+
return helper.visibleArguments(cmd).reduce((max, argument) => {
|
|
665
|
+
return Math.max(max, this.displayWidth(helper.styleArgumentTerm(helper.argumentTerm(argument))));
|
|
666
|
+
}, 0);
|
|
667
|
+
}
|
|
668
|
+
commandUsage(cmd) {
|
|
669
|
+
let cmdName = cmd._name;
|
|
670
|
+
if (cmd._aliases[0]) {
|
|
671
|
+
cmdName = cmdName + "|" + cmd._aliases[0];
|
|
672
|
+
}
|
|
673
|
+
let ancestorCmdNames = "";
|
|
674
|
+
for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
|
|
675
|
+
ancestorCmdNames = ancestorCmd.name() + " " + ancestorCmdNames;
|
|
676
|
+
}
|
|
677
|
+
return ancestorCmdNames + cmdName + " " + cmd.usage();
|
|
678
|
+
}
|
|
679
|
+
commandDescription(cmd) {
|
|
680
|
+
return cmd.description();
|
|
681
|
+
}
|
|
682
|
+
subcommandDescription(cmd) {
|
|
683
|
+
return cmd.summary() || cmd.description();
|
|
684
|
+
}
|
|
685
|
+
optionDescription(option) {
|
|
686
|
+
const extraInfo = [];
|
|
687
|
+
if (option.argChoices) {
|
|
688
|
+
extraInfo.push(`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
|
|
689
|
+
}
|
|
690
|
+
if (option.defaultValue !== undefined) {
|
|
691
|
+
const showDefault = option.required || option.optional || option.isBoolean() && typeof option.defaultValue === "boolean";
|
|
692
|
+
if (showDefault) {
|
|
693
|
+
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (option.presetArg !== undefined && option.optional) {
|
|
697
|
+
extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
|
|
698
|
+
}
|
|
699
|
+
if (option.envVar !== undefined) {
|
|
700
|
+
extraInfo.push(`env: ${option.envVar}`);
|
|
701
|
+
}
|
|
702
|
+
if (extraInfo.length > 0) {
|
|
703
|
+
return `${option.description} (${extraInfo.join(", ")})`;
|
|
704
|
+
}
|
|
705
|
+
return option.description;
|
|
706
|
+
}
|
|
707
|
+
argumentDescription(argument) {
|
|
708
|
+
const extraInfo = [];
|
|
709
|
+
if (argument.argChoices) {
|
|
710
|
+
extraInfo.push(`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
|
|
711
|
+
}
|
|
712
|
+
if (argument.defaultValue !== undefined) {
|
|
713
|
+
extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
|
|
714
|
+
}
|
|
715
|
+
if (extraInfo.length > 0) {
|
|
716
|
+
const extraDescription = `(${extraInfo.join(", ")})`;
|
|
717
|
+
if (argument.description) {
|
|
718
|
+
return `${argument.description} ${extraDescription}`;
|
|
719
|
+
}
|
|
720
|
+
return extraDescription;
|
|
721
|
+
}
|
|
722
|
+
return argument.description;
|
|
723
|
+
}
|
|
724
|
+
formatHelp(cmd, helper) {
|
|
725
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
726
|
+
const helpWidth = helper.helpWidth ?? 80;
|
|
727
|
+
function callFormatItem(term, description) {
|
|
728
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
729
|
+
}
|
|
730
|
+
let output = [
|
|
731
|
+
`${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
732
|
+
""
|
|
733
|
+
];
|
|
734
|
+
const commandDescription = helper.commandDescription(cmd);
|
|
735
|
+
if (commandDescription.length > 0) {
|
|
736
|
+
output = output.concat([
|
|
737
|
+
helper.boxWrap(helper.styleCommandDescription(commandDescription), helpWidth),
|
|
738
|
+
""
|
|
739
|
+
]);
|
|
740
|
+
}
|
|
741
|
+
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
742
|
+
return callFormatItem(helper.styleArgumentTerm(helper.argumentTerm(argument)), helper.styleArgumentDescription(helper.argumentDescription(argument)));
|
|
743
|
+
});
|
|
744
|
+
if (argumentList.length > 0) {
|
|
745
|
+
output = output.concat([
|
|
746
|
+
helper.styleTitle("Arguments:"),
|
|
747
|
+
...argumentList,
|
|
748
|
+
""
|
|
749
|
+
]);
|
|
750
|
+
}
|
|
751
|
+
const optionList = helper.visibleOptions(cmd).map((option) => {
|
|
752
|
+
return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
|
|
753
|
+
});
|
|
754
|
+
if (optionList.length > 0) {
|
|
755
|
+
output = output.concat([
|
|
756
|
+
helper.styleTitle("Options:"),
|
|
757
|
+
...optionList,
|
|
758
|
+
""
|
|
759
|
+
]);
|
|
760
|
+
}
|
|
761
|
+
if (helper.showGlobalOptions) {
|
|
762
|
+
const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
|
|
763
|
+
return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
|
|
764
|
+
});
|
|
765
|
+
if (globalOptionList.length > 0) {
|
|
766
|
+
output = output.concat([
|
|
767
|
+
helper.styleTitle("Global Options:"),
|
|
768
|
+
...globalOptionList,
|
|
769
|
+
""
|
|
770
|
+
]);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const commandList = helper.visibleCommands(cmd).map((cmd2) => {
|
|
774
|
+
return callFormatItem(helper.styleSubcommandTerm(helper.subcommandTerm(cmd2)), helper.styleSubcommandDescription(helper.subcommandDescription(cmd2)));
|
|
775
|
+
});
|
|
776
|
+
if (commandList.length > 0) {
|
|
777
|
+
output = output.concat([
|
|
778
|
+
helper.styleTitle("Commands:"),
|
|
779
|
+
...commandList,
|
|
780
|
+
""
|
|
781
|
+
]);
|
|
782
|
+
}
|
|
783
|
+
return output.join(`
|
|
784
|
+
`);
|
|
785
|
+
}
|
|
786
|
+
displayWidth(str) {
|
|
787
|
+
return stripColor(str).length;
|
|
788
|
+
}
|
|
789
|
+
styleTitle(str) {
|
|
790
|
+
return str;
|
|
791
|
+
}
|
|
792
|
+
styleUsage(str) {
|
|
793
|
+
return str.split(" ").map((word) => {
|
|
794
|
+
if (word === "[options]")
|
|
795
|
+
return this.styleOptionText(word);
|
|
796
|
+
if (word === "[command]")
|
|
797
|
+
return this.styleSubcommandText(word);
|
|
798
|
+
if (word[0] === "[" || word[0] === "<")
|
|
799
|
+
return this.styleArgumentText(word);
|
|
800
|
+
return this.styleCommandText(word);
|
|
801
|
+
}).join(" ");
|
|
802
|
+
}
|
|
803
|
+
styleCommandDescription(str) {
|
|
804
|
+
return this.styleDescriptionText(str);
|
|
805
|
+
}
|
|
806
|
+
styleOptionDescription(str) {
|
|
807
|
+
return this.styleDescriptionText(str);
|
|
808
|
+
}
|
|
809
|
+
styleSubcommandDescription(str) {
|
|
810
|
+
return this.styleDescriptionText(str);
|
|
811
|
+
}
|
|
812
|
+
styleArgumentDescription(str) {
|
|
813
|
+
return this.styleDescriptionText(str);
|
|
814
|
+
}
|
|
815
|
+
styleDescriptionText(str) {
|
|
816
|
+
return str;
|
|
817
|
+
}
|
|
818
|
+
styleOptionTerm(str) {
|
|
819
|
+
return this.styleOptionText(str);
|
|
820
|
+
}
|
|
821
|
+
styleSubcommandTerm(str) {
|
|
822
|
+
return str.split(" ").map((word) => {
|
|
823
|
+
if (word === "[options]")
|
|
824
|
+
return this.styleOptionText(word);
|
|
825
|
+
if (word[0] === "[" || word[0] === "<")
|
|
826
|
+
return this.styleArgumentText(word);
|
|
827
|
+
return this.styleSubcommandText(word);
|
|
828
|
+
}).join(" ");
|
|
829
|
+
}
|
|
830
|
+
styleArgumentTerm(str) {
|
|
831
|
+
return this.styleArgumentText(str);
|
|
832
|
+
}
|
|
833
|
+
styleOptionText(str) {
|
|
834
|
+
return str;
|
|
835
|
+
}
|
|
836
|
+
styleArgumentText(str) {
|
|
837
|
+
return str;
|
|
838
|
+
}
|
|
839
|
+
styleSubcommandText(str) {
|
|
840
|
+
return str;
|
|
841
|
+
}
|
|
842
|
+
styleCommandText(str) {
|
|
843
|
+
return str;
|
|
844
|
+
}
|
|
845
|
+
padWidth(cmd, helper) {
|
|
846
|
+
return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
|
|
847
|
+
}
|
|
848
|
+
preformatted(str) {
|
|
849
|
+
return /\n[^\S\r\n]/.test(str);
|
|
850
|
+
}
|
|
851
|
+
formatItem(term, termWidth, description, helper) {
|
|
852
|
+
const itemIndent = 2;
|
|
853
|
+
const itemIndentStr = " ".repeat(itemIndent);
|
|
854
|
+
if (!description)
|
|
855
|
+
return itemIndentStr + term;
|
|
856
|
+
const paddedTerm = term.padEnd(termWidth + term.length - helper.displayWidth(term));
|
|
857
|
+
const spacerWidth = 2;
|
|
858
|
+
const helpWidth = this.helpWidth ?? 80;
|
|
859
|
+
const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
|
|
860
|
+
let formattedDescription;
|
|
861
|
+
if (remainingWidth < this.minWidthToWrap || helper.preformatted(description)) {
|
|
862
|
+
formattedDescription = description;
|
|
863
|
+
} else {
|
|
864
|
+
const wrappedDescription = helper.boxWrap(description, remainingWidth);
|
|
865
|
+
formattedDescription = wrappedDescription.replace(/\n/g, `
|
|
866
|
+
` + " ".repeat(termWidth + spacerWidth));
|
|
867
|
+
}
|
|
868
|
+
return itemIndentStr + paddedTerm + " ".repeat(spacerWidth) + formattedDescription.replace(/\n/g, `
|
|
869
|
+
${itemIndentStr}`);
|
|
870
|
+
}
|
|
871
|
+
boxWrap(str, width) {
|
|
872
|
+
if (width < this.minWidthToWrap)
|
|
873
|
+
return str;
|
|
874
|
+
const rawLines = str.split(/\r\n|\n/);
|
|
875
|
+
const chunkPattern = /[\s]*[^\s]+/g;
|
|
876
|
+
const wrappedLines = [];
|
|
877
|
+
rawLines.forEach((line) => {
|
|
878
|
+
const chunks = line.match(chunkPattern);
|
|
879
|
+
if (chunks === null) {
|
|
880
|
+
wrappedLines.push("");
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
let sumChunks = [chunks.shift()];
|
|
884
|
+
let sumWidth = this.displayWidth(sumChunks[0]);
|
|
885
|
+
chunks.forEach((chunk) => {
|
|
886
|
+
const visibleWidth = this.displayWidth(chunk);
|
|
887
|
+
if (sumWidth + visibleWidth <= width) {
|
|
888
|
+
sumChunks.push(chunk);
|
|
889
|
+
sumWidth += visibleWidth;
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
wrappedLines.push(sumChunks.join(""));
|
|
893
|
+
const nextChunk = chunk.trimStart();
|
|
894
|
+
sumChunks = [nextChunk];
|
|
895
|
+
sumWidth = this.displayWidth(nextChunk);
|
|
896
|
+
});
|
|
897
|
+
wrappedLines.push(sumChunks.join(""));
|
|
898
|
+
});
|
|
899
|
+
return wrappedLines.join(`
|
|
900
|
+
`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function stripColor(str) {
|
|
904
|
+
const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
|
|
905
|
+
return str.replace(sgrPattern, "");
|
|
906
|
+
}
|
|
907
|
+
exports.Help = Help;
|
|
908
|
+
exports.stripColor = stripColor;
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// ../../node_modules/commander/lib/option.js
|
|
912
|
+
var require_option = __commonJS((exports) => {
|
|
913
|
+
var { InvalidArgumentError } = require_error();
|
|
914
|
+
|
|
915
|
+
class Option {
|
|
916
|
+
constructor(flags, description) {
|
|
917
|
+
this.flags = flags;
|
|
918
|
+
this.description = description || "";
|
|
919
|
+
this.required = flags.includes("<");
|
|
920
|
+
this.optional = flags.includes("[");
|
|
921
|
+
this.variadic = /\w\.\.\.[>\]]$/.test(flags);
|
|
922
|
+
this.mandatory = false;
|
|
923
|
+
const optionFlags = splitOptionFlags(flags);
|
|
924
|
+
this.short = optionFlags.shortFlag;
|
|
925
|
+
this.long = optionFlags.longFlag;
|
|
926
|
+
this.negate = false;
|
|
927
|
+
if (this.long) {
|
|
928
|
+
this.negate = this.long.startsWith("--no-");
|
|
929
|
+
}
|
|
930
|
+
this.defaultValue = undefined;
|
|
931
|
+
this.defaultValueDescription = undefined;
|
|
932
|
+
this.presetArg = undefined;
|
|
933
|
+
this.envVar = undefined;
|
|
934
|
+
this.parseArg = undefined;
|
|
935
|
+
this.hidden = false;
|
|
936
|
+
this.argChoices = undefined;
|
|
937
|
+
this.conflictsWith = [];
|
|
938
|
+
this.implied = undefined;
|
|
939
|
+
}
|
|
940
|
+
default(value, description) {
|
|
941
|
+
this.defaultValue = value;
|
|
942
|
+
this.defaultValueDescription = description;
|
|
943
|
+
return this;
|
|
944
|
+
}
|
|
945
|
+
preset(arg) {
|
|
946
|
+
this.presetArg = arg;
|
|
947
|
+
return this;
|
|
948
|
+
}
|
|
949
|
+
conflicts(names) {
|
|
950
|
+
this.conflictsWith = this.conflictsWith.concat(names);
|
|
951
|
+
return this;
|
|
952
|
+
}
|
|
953
|
+
implies(impliedOptionValues) {
|
|
954
|
+
let newImplied = impliedOptionValues;
|
|
955
|
+
if (typeof impliedOptionValues === "string") {
|
|
956
|
+
newImplied = { [impliedOptionValues]: true };
|
|
957
|
+
}
|
|
958
|
+
this.implied = Object.assign(this.implied || {}, newImplied);
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
env(name) {
|
|
962
|
+
this.envVar = name;
|
|
963
|
+
return this;
|
|
964
|
+
}
|
|
965
|
+
argParser(fn) {
|
|
966
|
+
this.parseArg = fn;
|
|
967
|
+
return this;
|
|
968
|
+
}
|
|
969
|
+
makeOptionMandatory(mandatory = true) {
|
|
970
|
+
this.mandatory = !!mandatory;
|
|
971
|
+
return this;
|
|
972
|
+
}
|
|
973
|
+
hideHelp(hide = true) {
|
|
974
|
+
this.hidden = !!hide;
|
|
975
|
+
return this;
|
|
976
|
+
}
|
|
977
|
+
_concatValue(value, previous) {
|
|
978
|
+
if (previous === this.defaultValue || !Array.isArray(previous)) {
|
|
979
|
+
return [value];
|
|
980
|
+
}
|
|
981
|
+
return previous.concat(value);
|
|
982
|
+
}
|
|
983
|
+
choices(values) {
|
|
984
|
+
this.argChoices = values.slice();
|
|
985
|
+
this.parseArg = (arg, previous) => {
|
|
986
|
+
if (!this.argChoices.includes(arg)) {
|
|
987
|
+
throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
|
|
988
|
+
}
|
|
989
|
+
if (this.variadic) {
|
|
990
|
+
return this._concatValue(arg, previous);
|
|
991
|
+
}
|
|
992
|
+
return arg;
|
|
993
|
+
};
|
|
994
|
+
return this;
|
|
995
|
+
}
|
|
996
|
+
name() {
|
|
997
|
+
if (this.long) {
|
|
998
|
+
return this.long.replace(/^--/, "");
|
|
999
|
+
}
|
|
1000
|
+
return this.short.replace(/^-/, "");
|
|
1001
|
+
}
|
|
1002
|
+
attributeName() {
|
|
1003
|
+
if (this.negate) {
|
|
1004
|
+
return camelcase(this.name().replace(/^no-/, ""));
|
|
1005
|
+
}
|
|
1006
|
+
return camelcase(this.name());
|
|
1007
|
+
}
|
|
1008
|
+
is(arg) {
|
|
1009
|
+
return this.short === arg || this.long === arg;
|
|
1010
|
+
}
|
|
1011
|
+
isBoolean() {
|
|
1012
|
+
return !this.required && !this.optional && !this.negate;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
class DualOptions {
|
|
1017
|
+
constructor(options) {
|
|
1018
|
+
this.positiveOptions = new Map;
|
|
1019
|
+
this.negativeOptions = new Map;
|
|
1020
|
+
this.dualOptions = new Set;
|
|
1021
|
+
options.forEach((option) => {
|
|
1022
|
+
if (option.negate) {
|
|
1023
|
+
this.negativeOptions.set(option.attributeName(), option);
|
|
1024
|
+
} else {
|
|
1025
|
+
this.positiveOptions.set(option.attributeName(), option);
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
this.negativeOptions.forEach((value, key) => {
|
|
1029
|
+
if (this.positiveOptions.has(key)) {
|
|
1030
|
+
this.dualOptions.add(key);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
valueFromOption(value, option) {
|
|
1035
|
+
const optionKey = option.attributeName();
|
|
1036
|
+
if (!this.dualOptions.has(optionKey))
|
|
1037
|
+
return true;
|
|
1038
|
+
const preset = this.negativeOptions.get(optionKey).presetArg;
|
|
1039
|
+
const negativeValue = preset !== undefined ? preset : false;
|
|
1040
|
+
return option.negate === (negativeValue === value);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function camelcase(str) {
|
|
1044
|
+
return str.split("-").reduce((str2, word) => {
|
|
1045
|
+
return str2 + word[0].toUpperCase() + word.slice(1);
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
function splitOptionFlags(flags) {
|
|
1049
|
+
let shortFlag;
|
|
1050
|
+
let longFlag;
|
|
1051
|
+
const shortFlagExp = /^-[^-]$/;
|
|
1052
|
+
const longFlagExp = /^--[^-]/;
|
|
1053
|
+
const flagParts = flags.split(/[ |,]+/).concat("guard");
|
|
1054
|
+
if (shortFlagExp.test(flagParts[0]))
|
|
1055
|
+
shortFlag = flagParts.shift();
|
|
1056
|
+
if (longFlagExp.test(flagParts[0]))
|
|
1057
|
+
longFlag = flagParts.shift();
|
|
1058
|
+
if (!shortFlag && shortFlagExp.test(flagParts[0]))
|
|
1059
|
+
shortFlag = flagParts.shift();
|
|
1060
|
+
if (!shortFlag && longFlagExp.test(flagParts[0])) {
|
|
1061
|
+
shortFlag = longFlag;
|
|
1062
|
+
longFlag = flagParts.shift();
|
|
1063
|
+
}
|
|
1064
|
+
if (flagParts[0].startsWith("-")) {
|
|
1065
|
+
const unsupportedFlag = flagParts[0];
|
|
1066
|
+
const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
|
|
1067
|
+
if (/^-[^-][^-]/.test(unsupportedFlag))
|
|
1068
|
+
throw new Error(`${baseError}
|
|
1069
|
+
- a short flag is a single dash and a single character
|
|
1070
|
+
- either use a single dash and a single character (for a short flag)
|
|
1071
|
+
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`);
|
|
1072
|
+
if (shortFlagExp.test(unsupportedFlag))
|
|
1073
|
+
throw new Error(`${baseError}
|
|
1074
|
+
- too many short flags`);
|
|
1075
|
+
if (longFlagExp.test(unsupportedFlag))
|
|
1076
|
+
throw new Error(`${baseError}
|
|
1077
|
+
- too many long flags`);
|
|
1078
|
+
throw new Error(`${baseError}
|
|
1079
|
+
- unrecognised flag format`);
|
|
1080
|
+
}
|
|
1081
|
+
if (shortFlag === undefined && longFlag === undefined)
|
|
1082
|
+
throw new Error(`option creation failed due to no flags found in '${flags}'.`);
|
|
1083
|
+
return { shortFlag, longFlag };
|
|
1084
|
+
}
|
|
1085
|
+
exports.Option = Option;
|
|
1086
|
+
exports.DualOptions = DualOptions;
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
// ../../node_modules/commander/lib/suggestSimilar.js
|
|
1090
|
+
var require_suggestSimilar = __commonJS((exports) => {
|
|
1091
|
+
var maxDistance = 3;
|
|
1092
|
+
function editDistance(a, b) {
|
|
1093
|
+
if (Math.abs(a.length - b.length) > maxDistance)
|
|
1094
|
+
return Math.max(a.length, b.length);
|
|
1095
|
+
const d = [];
|
|
1096
|
+
for (let i = 0;i <= a.length; i++) {
|
|
1097
|
+
d[i] = [i];
|
|
1098
|
+
}
|
|
1099
|
+
for (let j = 0;j <= b.length; j++) {
|
|
1100
|
+
d[0][j] = j;
|
|
1101
|
+
}
|
|
1102
|
+
for (let j = 1;j <= b.length; j++) {
|
|
1103
|
+
for (let i = 1;i <= a.length; i++) {
|
|
1104
|
+
let cost = 1;
|
|
1105
|
+
if (a[i - 1] === b[j - 1]) {
|
|
1106
|
+
cost = 0;
|
|
1107
|
+
} else {
|
|
1108
|
+
cost = 1;
|
|
1109
|
+
}
|
|
1110
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
1111
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
1112
|
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return d[a.length][b.length];
|
|
1117
|
+
}
|
|
1118
|
+
function suggestSimilar(word, candidates) {
|
|
1119
|
+
if (!candidates || candidates.length === 0)
|
|
1120
|
+
return "";
|
|
1121
|
+
candidates = Array.from(new Set(candidates));
|
|
1122
|
+
const searchingOptions = word.startsWith("--");
|
|
1123
|
+
if (searchingOptions) {
|
|
1124
|
+
word = word.slice(2);
|
|
1125
|
+
candidates = candidates.map((candidate) => candidate.slice(2));
|
|
1126
|
+
}
|
|
1127
|
+
let similar = [];
|
|
1128
|
+
let bestDistance = maxDistance;
|
|
1129
|
+
const minSimilarity = 0.4;
|
|
1130
|
+
candidates.forEach((candidate) => {
|
|
1131
|
+
if (candidate.length <= 1)
|
|
1132
|
+
return;
|
|
1133
|
+
const distance = editDistance(word, candidate);
|
|
1134
|
+
const length = Math.max(word.length, candidate.length);
|
|
1135
|
+
const similarity = (length - distance) / length;
|
|
1136
|
+
if (similarity > minSimilarity) {
|
|
1137
|
+
if (distance < bestDistance) {
|
|
1138
|
+
bestDistance = distance;
|
|
1139
|
+
similar = [candidate];
|
|
1140
|
+
} else if (distance === bestDistance) {
|
|
1141
|
+
similar.push(candidate);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
similar.sort((a, b) => a.localeCompare(b));
|
|
1146
|
+
if (searchingOptions) {
|
|
1147
|
+
similar = similar.map((candidate) => `--${candidate}`);
|
|
1148
|
+
}
|
|
1149
|
+
if (similar.length > 1) {
|
|
1150
|
+
return `
|
|
1151
|
+
(Did you mean one of ${similar.join(", ")}?)`;
|
|
1152
|
+
}
|
|
1153
|
+
if (similar.length === 1) {
|
|
1154
|
+
return `
|
|
1155
|
+
(Did you mean ${similar[0]}?)`;
|
|
1156
|
+
}
|
|
1157
|
+
return "";
|
|
1158
|
+
}
|
|
1159
|
+
exports.suggestSimilar = suggestSimilar;
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
// ../../node_modules/commander/lib/command.js
|
|
1163
|
+
var require_command = __commonJS((exports) => {
|
|
1164
|
+
var EventEmitter = __require("node:events").EventEmitter;
|
|
1165
|
+
var childProcess = __require("node:child_process");
|
|
1166
|
+
var path = __require("node:path");
|
|
1167
|
+
var fs = __require("node:fs");
|
|
1168
|
+
var process2 = __require("node:process");
|
|
1169
|
+
var { Argument, humanReadableArgName } = require_argument();
|
|
1170
|
+
var { CommanderError } = require_error();
|
|
1171
|
+
var { Help, stripColor } = require_help();
|
|
1172
|
+
var { Option, DualOptions } = require_option();
|
|
1173
|
+
var { suggestSimilar } = require_suggestSimilar();
|
|
1174
|
+
|
|
1175
|
+
class Command extends EventEmitter {
|
|
1176
|
+
constructor(name) {
|
|
1177
|
+
super();
|
|
1178
|
+
this.commands = [];
|
|
1179
|
+
this.options = [];
|
|
1180
|
+
this.parent = null;
|
|
1181
|
+
this._allowUnknownOption = false;
|
|
1182
|
+
this._allowExcessArguments = false;
|
|
1183
|
+
this.registeredArguments = [];
|
|
1184
|
+
this._args = this.registeredArguments;
|
|
1185
|
+
this.args = [];
|
|
1186
|
+
this.rawArgs = [];
|
|
1187
|
+
this.processedArgs = [];
|
|
1188
|
+
this._scriptPath = null;
|
|
1189
|
+
this._name = name || "";
|
|
1190
|
+
this._optionValues = {};
|
|
1191
|
+
this._optionValueSources = {};
|
|
1192
|
+
this._storeOptionsAsProperties = false;
|
|
1193
|
+
this._actionHandler = null;
|
|
1194
|
+
this._executableHandler = false;
|
|
1195
|
+
this._executableFile = null;
|
|
1196
|
+
this._executableDir = null;
|
|
1197
|
+
this._defaultCommandName = null;
|
|
1198
|
+
this._exitCallback = null;
|
|
1199
|
+
this._aliases = [];
|
|
1200
|
+
this._combineFlagAndOptionalValue = true;
|
|
1201
|
+
this._description = "";
|
|
1202
|
+
this._summary = "";
|
|
1203
|
+
this._argsDescription = undefined;
|
|
1204
|
+
this._enablePositionalOptions = false;
|
|
1205
|
+
this._passThroughOptions = false;
|
|
1206
|
+
this._lifeCycleHooks = {};
|
|
1207
|
+
this._showHelpAfterError = false;
|
|
1208
|
+
this._showSuggestionAfterError = true;
|
|
1209
|
+
this._savedState = null;
|
|
1210
|
+
this._outputConfiguration = {
|
|
1211
|
+
writeOut: (str) => process2.stdout.write(str),
|
|
1212
|
+
writeErr: (str) => process2.stderr.write(str),
|
|
1213
|
+
outputError: (str, write) => write(str),
|
|
1214
|
+
getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : undefined,
|
|
1215
|
+
getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : undefined,
|
|
1216
|
+
getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
|
|
1217
|
+
getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()),
|
|
1218
|
+
stripColor: (str) => stripColor(str)
|
|
1219
|
+
};
|
|
1220
|
+
this._hidden = false;
|
|
1221
|
+
this._helpOption = undefined;
|
|
1222
|
+
this._addImplicitHelpCommand = undefined;
|
|
1223
|
+
this._helpCommand = undefined;
|
|
1224
|
+
this._helpConfiguration = {};
|
|
1225
|
+
}
|
|
1226
|
+
copyInheritedSettings(sourceCommand) {
|
|
1227
|
+
this._outputConfiguration = sourceCommand._outputConfiguration;
|
|
1228
|
+
this._helpOption = sourceCommand._helpOption;
|
|
1229
|
+
this._helpCommand = sourceCommand._helpCommand;
|
|
1230
|
+
this._helpConfiguration = sourceCommand._helpConfiguration;
|
|
1231
|
+
this._exitCallback = sourceCommand._exitCallback;
|
|
1232
|
+
this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
|
|
1233
|
+
this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue;
|
|
1234
|
+
this._allowExcessArguments = sourceCommand._allowExcessArguments;
|
|
1235
|
+
this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
|
|
1236
|
+
this._showHelpAfterError = sourceCommand._showHelpAfterError;
|
|
1237
|
+
this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
|
|
1238
|
+
return this;
|
|
1239
|
+
}
|
|
1240
|
+
_getCommandAndAncestors() {
|
|
1241
|
+
const result = [];
|
|
1242
|
+
for (let command = this;command; command = command.parent) {
|
|
1243
|
+
result.push(command);
|
|
1244
|
+
}
|
|
1245
|
+
return result;
|
|
1246
|
+
}
|
|
1247
|
+
command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
|
|
1248
|
+
let desc = actionOptsOrExecDesc;
|
|
1249
|
+
let opts = execOpts;
|
|
1250
|
+
if (typeof desc === "object" && desc !== null) {
|
|
1251
|
+
opts = desc;
|
|
1252
|
+
desc = null;
|
|
1253
|
+
}
|
|
1254
|
+
opts = opts || {};
|
|
1255
|
+
const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
|
|
1256
|
+
const cmd = this.createCommand(name);
|
|
1257
|
+
if (desc) {
|
|
1258
|
+
cmd.description(desc);
|
|
1259
|
+
cmd._executableHandler = true;
|
|
1260
|
+
}
|
|
1261
|
+
if (opts.isDefault)
|
|
1262
|
+
this._defaultCommandName = cmd._name;
|
|
1263
|
+
cmd._hidden = !!(opts.noHelp || opts.hidden);
|
|
1264
|
+
cmd._executableFile = opts.executableFile || null;
|
|
1265
|
+
if (args)
|
|
1266
|
+
cmd.arguments(args);
|
|
1267
|
+
this._registerCommand(cmd);
|
|
1268
|
+
cmd.parent = this;
|
|
1269
|
+
cmd.copyInheritedSettings(this);
|
|
1270
|
+
if (desc)
|
|
1271
|
+
return this;
|
|
1272
|
+
return cmd;
|
|
1273
|
+
}
|
|
1274
|
+
createCommand(name) {
|
|
1275
|
+
return new Command(name);
|
|
1276
|
+
}
|
|
1277
|
+
createHelp() {
|
|
1278
|
+
return Object.assign(new Help, this.configureHelp());
|
|
1279
|
+
}
|
|
1280
|
+
configureHelp(configuration) {
|
|
1281
|
+
if (configuration === undefined)
|
|
1282
|
+
return this._helpConfiguration;
|
|
1283
|
+
this._helpConfiguration = configuration;
|
|
1284
|
+
return this;
|
|
1285
|
+
}
|
|
1286
|
+
configureOutput(configuration) {
|
|
1287
|
+
if (configuration === undefined)
|
|
1288
|
+
return this._outputConfiguration;
|
|
1289
|
+
Object.assign(this._outputConfiguration, configuration);
|
|
1290
|
+
return this;
|
|
1291
|
+
}
|
|
1292
|
+
showHelpAfterError(displayHelp = true) {
|
|
1293
|
+
if (typeof displayHelp !== "string")
|
|
1294
|
+
displayHelp = !!displayHelp;
|
|
1295
|
+
this._showHelpAfterError = displayHelp;
|
|
1296
|
+
return this;
|
|
1297
|
+
}
|
|
1298
|
+
showSuggestionAfterError(displaySuggestion = true) {
|
|
1299
|
+
this._showSuggestionAfterError = !!displaySuggestion;
|
|
1300
|
+
return this;
|
|
1301
|
+
}
|
|
1302
|
+
addCommand(cmd, opts) {
|
|
1303
|
+
if (!cmd._name) {
|
|
1304
|
+
throw new Error(`Command passed to .addCommand() must have a name
|
|
1305
|
+
- specify the name in Command constructor or using .name()`);
|
|
1306
|
+
}
|
|
1307
|
+
opts = opts || {};
|
|
1308
|
+
if (opts.isDefault)
|
|
1309
|
+
this._defaultCommandName = cmd._name;
|
|
1310
|
+
if (opts.noHelp || opts.hidden)
|
|
1311
|
+
cmd._hidden = true;
|
|
1312
|
+
this._registerCommand(cmd);
|
|
1313
|
+
cmd.parent = this;
|
|
1314
|
+
cmd._checkForBrokenPassThrough();
|
|
1315
|
+
return this;
|
|
1316
|
+
}
|
|
1317
|
+
createArgument(name, description) {
|
|
1318
|
+
return new Argument(name, description);
|
|
1319
|
+
}
|
|
1320
|
+
argument(name, description, fn, defaultValue) {
|
|
1321
|
+
const argument = this.createArgument(name, description);
|
|
1322
|
+
if (typeof fn === "function") {
|
|
1323
|
+
argument.default(defaultValue).argParser(fn);
|
|
1324
|
+
} else {
|
|
1325
|
+
argument.default(fn);
|
|
1326
|
+
}
|
|
1327
|
+
this.addArgument(argument);
|
|
1328
|
+
return this;
|
|
1329
|
+
}
|
|
1330
|
+
arguments(names) {
|
|
1331
|
+
names.trim().split(/ +/).forEach((detail) => {
|
|
1332
|
+
this.argument(detail);
|
|
1333
|
+
});
|
|
1334
|
+
return this;
|
|
1335
|
+
}
|
|
1336
|
+
addArgument(argument) {
|
|
1337
|
+
const previousArgument = this.registeredArguments.slice(-1)[0];
|
|
1338
|
+
if (previousArgument && previousArgument.variadic) {
|
|
1339
|
+
throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
|
|
1340
|
+
}
|
|
1341
|
+
if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
|
|
1342
|
+
throw new Error(`a default value for a required argument is never used: '${argument.name()}'`);
|
|
1343
|
+
}
|
|
1344
|
+
this.registeredArguments.push(argument);
|
|
1345
|
+
return this;
|
|
1346
|
+
}
|
|
1347
|
+
helpCommand(enableOrNameAndArgs, description) {
|
|
1348
|
+
if (typeof enableOrNameAndArgs === "boolean") {
|
|
1349
|
+
this._addImplicitHelpCommand = enableOrNameAndArgs;
|
|
1350
|
+
return this;
|
|
1351
|
+
}
|
|
1352
|
+
enableOrNameAndArgs = enableOrNameAndArgs ?? "help [command]";
|
|
1353
|
+
const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
|
|
1354
|
+
const helpDescription = description ?? "display help for command";
|
|
1355
|
+
const helpCommand = this.createCommand(helpName);
|
|
1356
|
+
helpCommand.helpOption(false);
|
|
1357
|
+
if (helpArgs)
|
|
1358
|
+
helpCommand.arguments(helpArgs);
|
|
1359
|
+
if (helpDescription)
|
|
1360
|
+
helpCommand.description(helpDescription);
|
|
1361
|
+
this._addImplicitHelpCommand = true;
|
|
1362
|
+
this._helpCommand = helpCommand;
|
|
1363
|
+
return this;
|
|
1364
|
+
}
|
|
1365
|
+
addHelpCommand(helpCommand, deprecatedDescription) {
|
|
1366
|
+
if (typeof helpCommand !== "object") {
|
|
1367
|
+
this.helpCommand(helpCommand, deprecatedDescription);
|
|
1368
|
+
return this;
|
|
1369
|
+
}
|
|
1370
|
+
this._addImplicitHelpCommand = true;
|
|
1371
|
+
this._helpCommand = helpCommand;
|
|
1372
|
+
return this;
|
|
1373
|
+
}
|
|
1374
|
+
_getHelpCommand() {
|
|
1375
|
+
const hasImplicitHelpCommand = this._addImplicitHelpCommand ?? (this.commands.length && !this._actionHandler && !this._findCommand("help"));
|
|
1376
|
+
if (hasImplicitHelpCommand) {
|
|
1377
|
+
if (this._helpCommand === undefined) {
|
|
1378
|
+
this.helpCommand(undefined, undefined);
|
|
1379
|
+
}
|
|
1380
|
+
return this._helpCommand;
|
|
1381
|
+
}
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
hook(event, listener) {
|
|
1385
|
+
const allowedValues = ["preSubcommand", "preAction", "postAction"];
|
|
1386
|
+
if (!allowedValues.includes(event)) {
|
|
1387
|
+
throw new Error(`Unexpected value for event passed to hook : '${event}'.
|
|
1388
|
+
Expecting one of '${allowedValues.join("', '")}'`);
|
|
1389
|
+
}
|
|
1390
|
+
if (this._lifeCycleHooks[event]) {
|
|
1391
|
+
this._lifeCycleHooks[event].push(listener);
|
|
1392
|
+
} else {
|
|
1393
|
+
this._lifeCycleHooks[event] = [listener];
|
|
1394
|
+
}
|
|
1395
|
+
return this;
|
|
1396
|
+
}
|
|
1397
|
+
exitOverride(fn) {
|
|
1398
|
+
if (fn) {
|
|
1399
|
+
this._exitCallback = fn;
|
|
1400
|
+
} else {
|
|
1401
|
+
this._exitCallback = (err) => {
|
|
1402
|
+
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1403
|
+
throw err;
|
|
1404
|
+
} else {}
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
return this;
|
|
1408
|
+
}
|
|
1409
|
+
_exit(exitCode, code, message) {
|
|
1410
|
+
if (this._exitCallback) {
|
|
1411
|
+
this._exitCallback(new CommanderError(exitCode, code, message));
|
|
1412
|
+
}
|
|
1413
|
+
process2.exit(exitCode);
|
|
1414
|
+
}
|
|
1415
|
+
action(fn) {
|
|
1416
|
+
const listener = (args) => {
|
|
1417
|
+
const expectedArgsCount = this.registeredArguments.length;
|
|
1418
|
+
const actionArgs = args.slice(0, expectedArgsCount);
|
|
1419
|
+
if (this._storeOptionsAsProperties) {
|
|
1420
|
+
actionArgs[expectedArgsCount] = this;
|
|
1421
|
+
} else {
|
|
1422
|
+
actionArgs[expectedArgsCount] = this.opts();
|
|
1423
|
+
}
|
|
1424
|
+
actionArgs.push(this);
|
|
1425
|
+
return fn.apply(this, actionArgs);
|
|
1426
|
+
};
|
|
1427
|
+
this._actionHandler = listener;
|
|
1428
|
+
return this;
|
|
1429
|
+
}
|
|
1430
|
+
createOption(flags, description) {
|
|
1431
|
+
return new Option(flags, description);
|
|
1432
|
+
}
|
|
1433
|
+
_callParseArg(target, value, previous, invalidArgumentMessage) {
|
|
1434
|
+
try {
|
|
1435
|
+
return target.parseArg(value, previous);
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
if (err.code === "commander.invalidArgument") {
|
|
1438
|
+
const message = `${invalidArgumentMessage} ${err.message}`;
|
|
1439
|
+
this.error(message, { exitCode: err.exitCode, code: err.code });
|
|
1440
|
+
}
|
|
1441
|
+
throw err;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
_registerOption(option) {
|
|
1445
|
+
const matchingOption = option.short && this._findOption(option.short) || option.long && this._findOption(option.long);
|
|
1446
|
+
if (matchingOption) {
|
|
1447
|
+
const matchingFlag = option.long && this._findOption(option.long) ? option.long : option.short;
|
|
1448
|
+
throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
|
|
1449
|
+
- already used by option '${matchingOption.flags}'`);
|
|
1450
|
+
}
|
|
1451
|
+
this.options.push(option);
|
|
1452
|
+
}
|
|
1453
|
+
_registerCommand(command) {
|
|
1454
|
+
const knownBy = (cmd) => {
|
|
1455
|
+
return [cmd.name()].concat(cmd.aliases());
|
|
1456
|
+
};
|
|
1457
|
+
const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
|
|
1458
|
+
if (alreadyUsed) {
|
|
1459
|
+
const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|");
|
|
1460
|
+
const newCmd = knownBy(command).join("|");
|
|
1461
|
+
throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
|
|
1462
|
+
}
|
|
1463
|
+
this.commands.push(command);
|
|
1464
|
+
}
|
|
1465
|
+
addOption(option) {
|
|
1466
|
+
this._registerOption(option);
|
|
1467
|
+
const oname = option.name();
|
|
1468
|
+
const name = option.attributeName();
|
|
1469
|
+
if (option.negate) {
|
|
1470
|
+
const positiveLongFlag = option.long.replace(/^--no-/, "--");
|
|
1471
|
+
if (!this._findOption(positiveLongFlag)) {
|
|
1472
|
+
this.setOptionValueWithSource(name, option.defaultValue === undefined ? true : option.defaultValue, "default");
|
|
1473
|
+
}
|
|
1474
|
+
} else if (option.defaultValue !== undefined) {
|
|
1475
|
+
this.setOptionValueWithSource(name, option.defaultValue, "default");
|
|
1476
|
+
}
|
|
1477
|
+
const handleOptionValue = (val, invalidValueMessage, valueSource) => {
|
|
1478
|
+
if (val == null && option.presetArg !== undefined) {
|
|
1479
|
+
val = option.presetArg;
|
|
1480
|
+
}
|
|
1481
|
+
const oldValue = this.getOptionValue(name);
|
|
1482
|
+
if (val !== null && option.parseArg) {
|
|
1483
|
+
val = this._callParseArg(option, val, oldValue, invalidValueMessage);
|
|
1484
|
+
} else if (val !== null && option.variadic) {
|
|
1485
|
+
val = option._concatValue(val, oldValue);
|
|
1486
|
+
}
|
|
1487
|
+
if (val == null) {
|
|
1488
|
+
if (option.negate) {
|
|
1489
|
+
val = false;
|
|
1490
|
+
} else if (option.isBoolean() || option.optional) {
|
|
1491
|
+
val = true;
|
|
1492
|
+
} else {
|
|
1493
|
+
val = "";
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
this.setOptionValueWithSource(name, val, valueSource);
|
|
1497
|
+
};
|
|
1498
|
+
this.on("option:" + oname, (val) => {
|
|
1499
|
+
const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
|
|
1500
|
+
handleOptionValue(val, invalidValueMessage, "cli");
|
|
1501
|
+
});
|
|
1502
|
+
if (option.envVar) {
|
|
1503
|
+
this.on("optionEnv:" + oname, (val) => {
|
|
1504
|
+
const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
|
|
1505
|
+
handleOptionValue(val, invalidValueMessage, "env");
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
return this;
|
|
1509
|
+
}
|
|
1510
|
+
_optionEx(config, flags, description, fn, defaultValue) {
|
|
1511
|
+
if (typeof flags === "object" && flags instanceof Option) {
|
|
1512
|
+
throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");
|
|
1513
|
+
}
|
|
1514
|
+
const option = this.createOption(flags, description);
|
|
1515
|
+
option.makeOptionMandatory(!!config.mandatory);
|
|
1516
|
+
if (typeof fn === "function") {
|
|
1517
|
+
option.default(defaultValue).argParser(fn);
|
|
1518
|
+
} else if (fn instanceof RegExp) {
|
|
1519
|
+
const regex = fn;
|
|
1520
|
+
fn = (val, def) => {
|
|
1521
|
+
const m = regex.exec(val);
|
|
1522
|
+
return m ? m[0] : def;
|
|
1523
|
+
};
|
|
1524
|
+
option.default(defaultValue).argParser(fn);
|
|
1525
|
+
} else {
|
|
1526
|
+
option.default(fn);
|
|
1527
|
+
}
|
|
1528
|
+
return this.addOption(option);
|
|
1529
|
+
}
|
|
1530
|
+
option(flags, description, parseArg, defaultValue) {
|
|
1531
|
+
return this._optionEx({}, flags, description, parseArg, defaultValue);
|
|
1532
|
+
}
|
|
1533
|
+
requiredOption(flags, description, parseArg, defaultValue) {
|
|
1534
|
+
return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
|
|
1535
|
+
}
|
|
1536
|
+
combineFlagAndOptionalValue(combine = true) {
|
|
1537
|
+
this._combineFlagAndOptionalValue = !!combine;
|
|
1538
|
+
return this;
|
|
1539
|
+
}
|
|
1540
|
+
allowUnknownOption(allowUnknown = true) {
|
|
1541
|
+
this._allowUnknownOption = !!allowUnknown;
|
|
1542
|
+
return this;
|
|
1543
|
+
}
|
|
1544
|
+
allowExcessArguments(allowExcess = true) {
|
|
1545
|
+
this._allowExcessArguments = !!allowExcess;
|
|
1546
|
+
return this;
|
|
1547
|
+
}
|
|
1548
|
+
enablePositionalOptions(positional = true) {
|
|
1549
|
+
this._enablePositionalOptions = !!positional;
|
|
1550
|
+
return this;
|
|
1551
|
+
}
|
|
1552
|
+
passThroughOptions(passThrough = true) {
|
|
1553
|
+
this._passThroughOptions = !!passThrough;
|
|
1554
|
+
this._checkForBrokenPassThrough();
|
|
1555
|
+
return this;
|
|
1556
|
+
}
|
|
1557
|
+
_checkForBrokenPassThrough() {
|
|
1558
|
+
if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
|
|
1559
|
+
throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
storeOptionsAsProperties(storeAsProperties = true) {
|
|
1563
|
+
if (this.options.length) {
|
|
1564
|
+
throw new Error("call .storeOptionsAsProperties() before adding options");
|
|
1565
|
+
}
|
|
1566
|
+
if (Object.keys(this._optionValues).length) {
|
|
1567
|
+
throw new Error("call .storeOptionsAsProperties() before setting option values");
|
|
1568
|
+
}
|
|
1569
|
+
this._storeOptionsAsProperties = !!storeAsProperties;
|
|
1570
|
+
return this;
|
|
1571
|
+
}
|
|
1572
|
+
getOptionValue(key) {
|
|
1573
|
+
if (this._storeOptionsAsProperties) {
|
|
1574
|
+
return this[key];
|
|
1575
|
+
}
|
|
1576
|
+
return this._optionValues[key];
|
|
1577
|
+
}
|
|
1578
|
+
setOptionValue(key, value) {
|
|
1579
|
+
return this.setOptionValueWithSource(key, value, undefined);
|
|
1580
|
+
}
|
|
1581
|
+
setOptionValueWithSource(key, value, source) {
|
|
1582
|
+
if (this._storeOptionsAsProperties) {
|
|
1583
|
+
this[key] = value;
|
|
1584
|
+
} else {
|
|
1585
|
+
this._optionValues[key] = value;
|
|
1586
|
+
}
|
|
1587
|
+
this._optionValueSources[key] = source;
|
|
1588
|
+
return this;
|
|
1589
|
+
}
|
|
1590
|
+
getOptionValueSource(key) {
|
|
1591
|
+
return this._optionValueSources[key];
|
|
1592
|
+
}
|
|
1593
|
+
getOptionValueSourceWithGlobals(key) {
|
|
1594
|
+
let source;
|
|
1595
|
+
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1596
|
+
if (cmd.getOptionValueSource(key) !== undefined) {
|
|
1597
|
+
source = cmd.getOptionValueSource(key);
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
return source;
|
|
1601
|
+
}
|
|
1602
|
+
_prepareUserArgs(argv, parseOptions) {
|
|
1603
|
+
if (argv !== undefined && !Array.isArray(argv)) {
|
|
1604
|
+
throw new Error("first parameter to parse must be array or undefined");
|
|
1605
|
+
}
|
|
1606
|
+
parseOptions = parseOptions || {};
|
|
1607
|
+
if (argv === undefined && parseOptions.from === undefined) {
|
|
1608
|
+
if (process2.versions?.electron) {
|
|
1609
|
+
parseOptions.from = "electron";
|
|
1610
|
+
}
|
|
1611
|
+
const execArgv = process2.execArgv ?? [];
|
|
1612
|
+
if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
|
|
1613
|
+
parseOptions.from = "eval";
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (argv === undefined) {
|
|
1617
|
+
argv = process2.argv;
|
|
1618
|
+
}
|
|
1619
|
+
this.rawArgs = argv.slice();
|
|
1620
|
+
let userArgs;
|
|
1621
|
+
switch (parseOptions.from) {
|
|
1622
|
+
case undefined:
|
|
1623
|
+
case "node":
|
|
1624
|
+
this._scriptPath = argv[1];
|
|
1625
|
+
userArgs = argv.slice(2);
|
|
1626
|
+
break;
|
|
1627
|
+
case "electron":
|
|
1628
|
+
if (process2.defaultApp) {
|
|
1629
|
+
this._scriptPath = argv[1];
|
|
1630
|
+
userArgs = argv.slice(2);
|
|
1631
|
+
} else {
|
|
1632
|
+
userArgs = argv.slice(1);
|
|
1633
|
+
}
|
|
1634
|
+
break;
|
|
1635
|
+
case "user":
|
|
1636
|
+
userArgs = argv.slice(0);
|
|
1637
|
+
break;
|
|
1638
|
+
case "eval":
|
|
1639
|
+
userArgs = argv.slice(1);
|
|
1640
|
+
break;
|
|
1641
|
+
default:
|
|
1642
|
+
throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
|
|
1643
|
+
}
|
|
1644
|
+
if (!this._name && this._scriptPath)
|
|
1645
|
+
this.nameFromFilename(this._scriptPath);
|
|
1646
|
+
this._name = this._name || "program";
|
|
1647
|
+
return userArgs;
|
|
1648
|
+
}
|
|
1649
|
+
parse(argv, parseOptions) {
|
|
1650
|
+
this._prepareForParse();
|
|
1651
|
+
const userArgs = this._prepareUserArgs(argv, parseOptions);
|
|
1652
|
+
this._parseCommand([], userArgs);
|
|
1653
|
+
return this;
|
|
1654
|
+
}
|
|
1655
|
+
async parseAsync(argv, parseOptions) {
|
|
1656
|
+
this._prepareForParse();
|
|
1657
|
+
const userArgs = this._prepareUserArgs(argv, parseOptions);
|
|
1658
|
+
await this._parseCommand([], userArgs);
|
|
1659
|
+
return this;
|
|
1660
|
+
}
|
|
1661
|
+
_prepareForParse() {
|
|
1662
|
+
if (this._savedState === null) {
|
|
1663
|
+
this.saveStateBeforeParse();
|
|
1664
|
+
} else {
|
|
1665
|
+
this.restoreStateBeforeParse();
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
saveStateBeforeParse() {
|
|
1669
|
+
this._savedState = {
|
|
1670
|
+
_name: this._name,
|
|
1671
|
+
_optionValues: { ...this._optionValues },
|
|
1672
|
+
_optionValueSources: { ...this._optionValueSources }
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
restoreStateBeforeParse() {
|
|
1676
|
+
if (this._storeOptionsAsProperties)
|
|
1677
|
+
throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
|
|
1678
|
+
- either make a new Command for each call to parse, or stop storing options as properties`);
|
|
1679
|
+
this._name = this._savedState._name;
|
|
1680
|
+
this._scriptPath = null;
|
|
1681
|
+
this.rawArgs = [];
|
|
1682
|
+
this._optionValues = { ...this._savedState._optionValues };
|
|
1683
|
+
this._optionValueSources = { ...this._savedState._optionValueSources };
|
|
1684
|
+
this.args = [];
|
|
1685
|
+
this.processedArgs = [];
|
|
1686
|
+
}
|
|
1687
|
+
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
1688
|
+
if (fs.existsSync(executableFile))
|
|
1689
|
+
return;
|
|
1690
|
+
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
1691
|
+
const executableMissing = `'${executableFile}' does not exist
|
|
1692
|
+
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
1693
|
+
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
|
|
1694
|
+
- ${executableDirMessage}`;
|
|
1695
|
+
throw new Error(executableMissing);
|
|
1696
|
+
}
|
|
1697
|
+
_executeSubCommand(subcommand, args) {
|
|
1698
|
+
args = args.slice();
|
|
1699
|
+
let launchWithNode = false;
|
|
1700
|
+
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1701
|
+
function findFile(baseDir, baseName) {
|
|
1702
|
+
const localBin = path.resolve(baseDir, baseName);
|
|
1703
|
+
if (fs.existsSync(localBin))
|
|
1704
|
+
return localBin;
|
|
1705
|
+
if (sourceExt.includes(path.extname(baseName)))
|
|
1706
|
+
return;
|
|
1707
|
+
const foundExt = sourceExt.find((ext) => fs.existsSync(`${localBin}${ext}`));
|
|
1708
|
+
if (foundExt)
|
|
1709
|
+
return `${localBin}${foundExt}`;
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
this._checkForMissingMandatoryOptions();
|
|
1713
|
+
this._checkForConflictingOptions();
|
|
1714
|
+
let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
|
|
1715
|
+
let executableDir = this._executableDir || "";
|
|
1716
|
+
if (this._scriptPath) {
|
|
1717
|
+
let resolvedScriptPath;
|
|
1718
|
+
try {
|
|
1719
|
+
resolvedScriptPath = fs.realpathSync(this._scriptPath);
|
|
1720
|
+
} catch {
|
|
1721
|
+
resolvedScriptPath = this._scriptPath;
|
|
1722
|
+
}
|
|
1723
|
+
executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
|
|
1724
|
+
}
|
|
1725
|
+
if (executableDir) {
|
|
1726
|
+
let localFile = findFile(executableDir, executableFile);
|
|
1727
|
+
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1728
|
+
const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
|
|
1729
|
+
if (legacyName !== this._name) {
|
|
1730
|
+
localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
executableFile = localFile || executableFile;
|
|
1734
|
+
}
|
|
1735
|
+
launchWithNode = sourceExt.includes(path.extname(executableFile));
|
|
1736
|
+
let proc;
|
|
1737
|
+
if (process2.platform !== "win32") {
|
|
1738
|
+
if (launchWithNode) {
|
|
1739
|
+
args.unshift(executableFile);
|
|
1740
|
+
args = incrementNodeInspectorPort(process2.execArgv).concat(args);
|
|
1741
|
+
proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" });
|
|
1742
|
+
} else {
|
|
1743
|
+
proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
|
|
1744
|
+
}
|
|
1745
|
+
} else {
|
|
1746
|
+
this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
|
|
1747
|
+
args.unshift(executableFile);
|
|
1748
|
+
args = incrementNodeInspectorPort(process2.execArgv).concat(args);
|
|
1749
|
+
proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
|
|
1750
|
+
}
|
|
1751
|
+
if (!proc.killed) {
|
|
1752
|
+
const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
|
|
1753
|
+
signals.forEach((signal) => {
|
|
1754
|
+
process2.on(signal, () => {
|
|
1755
|
+
if (proc.killed === false && proc.exitCode === null) {
|
|
1756
|
+
proc.kill(signal);
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
const exitCallback = this._exitCallback;
|
|
1762
|
+
proc.on("close", (code) => {
|
|
1763
|
+
code = code ?? 1;
|
|
1764
|
+
if (!exitCallback) {
|
|
1765
|
+
process2.exit(code);
|
|
1766
|
+
} else {
|
|
1767
|
+
exitCallback(new CommanderError(code, "commander.executeSubCommandAsync", "(close)"));
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
proc.on("error", (err) => {
|
|
1771
|
+
if (err.code === "ENOENT") {
|
|
1772
|
+
this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
|
|
1773
|
+
} else if (err.code === "EACCES") {
|
|
1774
|
+
throw new Error(`'${executableFile}' not executable`);
|
|
1775
|
+
}
|
|
1776
|
+
if (!exitCallback) {
|
|
1777
|
+
process2.exit(1);
|
|
1778
|
+
} else {
|
|
1779
|
+
const wrappedError = new CommanderError(1, "commander.executeSubCommandAsync", "(error)");
|
|
1780
|
+
wrappedError.nestedError = err;
|
|
1781
|
+
exitCallback(wrappedError);
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
this.runningCommand = proc;
|
|
1785
|
+
}
|
|
1786
|
+
_dispatchSubcommand(commandName, operands, unknown) {
|
|
1787
|
+
const subCommand = this._findCommand(commandName);
|
|
1788
|
+
if (!subCommand)
|
|
1789
|
+
this.help({ error: true });
|
|
1790
|
+
subCommand._prepareForParse();
|
|
1791
|
+
let promiseChain;
|
|
1792
|
+
promiseChain = this._chainOrCallSubCommandHook(promiseChain, subCommand, "preSubcommand");
|
|
1793
|
+
promiseChain = this._chainOrCall(promiseChain, () => {
|
|
1794
|
+
if (subCommand._executableHandler) {
|
|
1795
|
+
this._executeSubCommand(subCommand, operands.concat(unknown));
|
|
1796
|
+
} else {
|
|
1797
|
+
return subCommand._parseCommand(operands, unknown);
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
return promiseChain;
|
|
1801
|
+
}
|
|
1802
|
+
_dispatchHelpCommand(subcommandName) {
|
|
1803
|
+
if (!subcommandName) {
|
|
1804
|
+
this.help();
|
|
1805
|
+
}
|
|
1806
|
+
const subCommand = this._findCommand(subcommandName);
|
|
1807
|
+
if (subCommand && !subCommand._executableHandler) {
|
|
1808
|
+
subCommand.help();
|
|
1809
|
+
}
|
|
1810
|
+
return this._dispatchSubcommand(subcommandName, [], [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? "--help"]);
|
|
1811
|
+
}
|
|
1812
|
+
_checkNumberOfArguments() {
|
|
1813
|
+
this.registeredArguments.forEach((arg, i) => {
|
|
1814
|
+
if (arg.required && this.args[i] == null) {
|
|
1815
|
+
this.missingArgument(arg.name());
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (this.args.length > this.registeredArguments.length) {
|
|
1822
|
+
this._excessArguments(this.args);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
_processArguments() {
|
|
1826
|
+
const myParseArg = (argument, value, previous) => {
|
|
1827
|
+
let parsedValue = value;
|
|
1828
|
+
if (value !== null && argument.parseArg) {
|
|
1829
|
+
const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
|
|
1830
|
+
parsedValue = this._callParseArg(argument, value, previous, invalidValueMessage);
|
|
1831
|
+
}
|
|
1832
|
+
return parsedValue;
|
|
1833
|
+
};
|
|
1834
|
+
this._checkNumberOfArguments();
|
|
1835
|
+
const processedArgs = [];
|
|
1836
|
+
this.registeredArguments.forEach((declaredArg, index) => {
|
|
1837
|
+
let value = declaredArg.defaultValue;
|
|
1838
|
+
if (declaredArg.variadic) {
|
|
1839
|
+
if (index < this.args.length) {
|
|
1840
|
+
value = this.args.slice(index);
|
|
1841
|
+
if (declaredArg.parseArg) {
|
|
1842
|
+
value = value.reduce((processed, v) => {
|
|
1843
|
+
return myParseArg(declaredArg, v, processed);
|
|
1844
|
+
}, declaredArg.defaultValue);
|
|
1845
|
+
}
|
|
1846
|
+
} else if (value === undefined) {
|
|
1847
|
+
value = [];
|
|
1848
|
+
}
|
|
1849
|
+
} else if (index < this.args.length) {
|
|
1850
|
+
value = this.args[index];
|
|
1851
|
+
if (declaredArg.parseArg) {
|
|
1852
|
+
value = myParseArg(declaredArg, value, declaredArg.defaultValue);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
processedArgs[index] = value;
|
|
1856
|
+
});
|
|
1857
|
+
this.processedArgs = processedArgs;
|
|
1858
|
+
}
|
|
1859
|
+
_chainOrCall(promise, fn) {
|
|
1860
|
+
if (promise && promise.then && typeof promise.then === "function") {
|
|
1861
|
+
return promise.then(() => fn());
|
|
1862
|
+
}
|
|
1863
|
+
return fn();
|
|
1864
|
+
}
|
|
1865
|
+
_chainOrCallHooks(promise, event) {
|
|
1866
|
+
let result = promise;
|
|
1867
|
+
const hooks = [];
|
|
1868
|
+
this._getCommandAndAncestors().reverse().filter((cmd) => cmd._lifeCycleHooks[event] !== undefined).forEach((hookedCommand) => {
|
|
1869
|
+
hookedCommand._lifeCycleHooks[event].forEach((callback) => {
|
|
1870
|
+
hooks.push({ hookedCommand, callback });
|
|
1871
|
+
});
|
|
1872
|
+
});
|
|
1873
|
+
if (event === "postAction") {
|
|
1874
|
+
hooks.reverse();
|
|
1875
|
+
}
|
|
1876
|
+
hooks.forEach((hookDetail) => {
|
|
1877
|
+
result = this._chainOrCall(result, () => {
|
|
1878
|
+
return hookDetail.callback(hookDetail.hookedCommand, this);
|
|
1879
|
+
});
|
|
1880
|
+
});
|
|
1881
|
+
return result;
|
|
1882
|
+
}
|
|
1883
|
+
_chainOrCallSubCommandHook(promise, subCommand, event) {
|
|
1884
|
+
let result = promise;
|
|
1885
|
+
if (this._lifeCycleHooks[event] !== undefined) {
|
|
1886
|
+
this._lifeCycleHooks[event].forEach((hook) => {
|
|
1887
|
+
result = this._chainOrCall(result, () => {
|
|
1888
|
+
return hook(this, subCommand);
|
|
1889
|
+
});
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
return result;
|
|
1893
|
+
}
|
|
1894
|
+
_parseCommand(operands, unknown) {
|
|
1895
|
+
const parsed = this.parseOptions(unknown);
|
|
1896
|
+
this._parseOptionsEnv();
|
|
1897
|
+
this._parseOptionsImplied();
|
|
1898
|
+
operands = operands.concat(parsed.operands);
|
|
1899
|
+
unknown = parsed.unknown;
|
|
1900
|
+
this.args = operands.concat(unknown);
|
|
1901
|
+
if (operands && this._findCommand(operands[0])) {
|
|
1902
|
+
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
|
|
1903
|
+
}
|
|
1904
|
+
if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
|
|
1905
|
+
return this._dispatchHelpCommand(operands[1]);
|
|
1906
|
+
}
|
|
1907
|
+
if (this._defaultCommandName) {
|
|
1908
|
+
this._outputHelpIfRequested(unknown);
|
|
1909
|
+
return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
|
|
1910
|
+
}
|
|
1911
|
+
if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
|
|
1912
|
+
this.help({ error: true });
|
|
1913
|
+
}
|
|
1914
|
+
this._outputHelpIfRequested(parsed.unknown);
|
|
1915
|
+
this._checkForMissingMandatoryOptions();
|
|
1916
|
+
this._checkForConflictingOptions();
|
|
1917
|
+
const checkForUnknownOptions = () => {
|
|
1918
|
+
if (parsed.unknown.length > 0) {
|
|
1919
|
+
this.unknownOption(parsed.unknown[0]);
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
const commandEvent = `command:${this.name()}`;
|
|
1923
|
+
if (this._actionHandler) {
|
|
1924
|
+
checkForUnknownOptions();
|
|
1925
|
+
this._processArguments();
|
|
1926
|
+
let promiseChain;
|
|
1927
|
+
promiseChain = this._chainOrCallHooks(promiseChain, "preAction");
|
|
1928
|
+
promiseChain = this._chainOrCall(promiseChain, () => this._actionHandler(this.processedArgs));
|
|
1929
|
+
if (this.parent) {
|
|
1930
|
+
promiseChain = this._chainOrCall(promiseChain, () => {
|
|
1931
|
+
this.parent.emit(commandEvent, operands, unknown);
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
promiseChain = this._chainOrCallHooks(promiseChain, "postAction");
|
|
1935
|
+
return promiseChain;
|
|
1936
|
+
}
|
|
1937
|
+
if (this.parent && this.parent.listenerCount(commandEvent)) {
|
|
1938
|
+
checkForUnknownOptions();
|
|
1939
|
+
this._processArguments();
|
|
1940
|
+
this.parent.emit(commandEvent, operands, unknown);
|
|
1941
|
+
} else if (operands.length) {
|
|
1942
|
+
if (this._findCommand("*")) {
|
|
1943
|
+
return this._dispatchSubcommand("*", operands, unknown);
|
|
1944
|
+
}
|
|
1945
|
+
if (this.listenerCount("command:*")) {
|
|
1946
|
+
this.emit("command:*", operands, unknown);
|
|
1947
|
+
} else if (this.commands.length) {
|
|
1948
|
+
this.unknownCommand();
|
|
1949
|
+
} else {
|
|
1950
|
+
checkForUnknownOptions();
|
|
1951
|
+
this._processArguments();
|
|
1952
|
+
}
|
|
1953
|
+
} else if (this.commands.length) {
|
|
1954
|
+
checkForUnknownOptions();
|
|
1955
|
+
this.help({ error: true });
|
|
1956
|
+
} else {
|
|
1957
|
+
checkForUnknownOptions();
|
|
1958
|
+
this._processArguments();
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
_findCommand(name) {
|
|
1962
|
+
if (!name)
|
|
1963
|
+
return;
|
|
1964
|
+
return this.commands.find((cmd) => cmd._name === name || cmd._aliases.includes(name));
|
|
1965
|
+
}
|
|
1966
|
+
_findOption(arg) {
|
|
1967
|
+
return this.options.find((option) => option.is(arg));
|
|
1968
|
+
}
|
|
1969
|
+
_checkForMissingMandatoryOptions() {
|
|
1970
|
+
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1971
|
+
cmd.options.forEach((anOption) => {
|
|
1972
|
+
if (anOption.mandatory && cmd.getOptionValue(anOption.attributeName()) === undefined) {
|
|
1973
|
+
cmd.missingMandatoryOptionValue(anOption);
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
_checkForConflictingLocalOptions() {
|
|
1979
|
+
const definedNonDefaultOptions = this.options.filter((option) => {
|
|
1980
|
+
const optionKey = option.attributeName();
|
|
1981
|
+
if (this.getOptionValue(optionKey) === undefined) {
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
return this.getOptionValueSource(optionKey) !== "default";
|
|
1985
|
+
});
|
|
1986
|
+
const optionsWithConflicting = definedNonDefaultOptions.filter((option) => option.conflictsWith.length > 0);
|
|
1987
|
+
optionsWithConflicting.forEach((option) => {
|
|
1988
|
+
const conflictingAndDefined = definedNonDefaultOptions.find((defined) => option.conflictsWith.includes(defined.attributeName()));
|
|
1989
|
+
if (conflictingAndDefined) {
|
|
1990
|
+
this._conflictingOption(option, conflictingAndDefined);
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
_checkForConflictingOptions() {
|
|
1995
|
+
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1996
|
+
cmd._checkForConflictingLocalOptions();
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
parseOptions(argv) {
|
|
2000
|
+
const operands = [];
|
|
2001
|
+
const unknown = [];
|
|
2002
|
+
let dest = operands;
|
|
2003
|
+
const args = argv.slice();
|
|
2004
|
+
function maybeOption(arg) {
|
|
2005
|
+
return arg.length > 1 && arg[0] === "-";
|
|
2006
|
+
}
|
|
2007
|
+
let activeVariadicOption = null;
|
|
2008
|
+
while (args.length) {
|
|
2009
|
+
const arg = args.shift();
|
|
2010
|
+
if (arg === "--") {
|
|
2011
|
+
if (dest === unknown)
|
|
2012
|
+
dest.push(arg);
|
|
2013
|
+
dest.push(...args);
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
if (activeVariadicOption && !maybeOption(arg)) {
|
|
2017
|
+
this.emit(`option:${activeVariadicOption.name()}`, arg);
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
activeVariadicOption = null;
|
|
2021
|
+
if (maybeOption(arg)) {
|
|
2022
|
+
const option = this._findOption(arg);
|
|
2023
|
+
if (option) {
|
|
2024
|
+
if (option.required) {
|
|
2025
|
+
const value = args.shift();
|
|
2026
|
+
if (value === undefined)
|
|
2027
|
+
this.optionMissingArgument(option);
|
|
2028
|
+
this.emit(`option:${option.name()}`, value);
|
|
2029
|
+
} else if (option.optional) {
|
|
2030
|
+
let value = null;
|
|
2031
|
+
if (args.length > 0 && !maybeOption(args[0])) {
|
|
2032
|
+
value = args.shift();
|
|
2033
|
+
}
|
|
2034
|
+
this.emit(`option:${option.name()}`, value);
|
|
2035
|
+
} else {
|
|
2036
|
+
this.emit(`option:${option.name()}`);
|
|
2037
|
+
}
|
|
2038
|
+
activeVariadicOption = option.variadic ? option : null;
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") {
|
|
2043
|
+
const option = this._findOption(`-${arg[1]}`);
|
|
2044
|
+
if (option) {
|
|
2045
|
+
if (option.required || option.optional && this._combineFlagAndOptionalValue) {
|
|
2046
|
+
this.emit(`option:${option.name()}`, arg.slice(2));
|
|
2047
|
+
} else {
|
|
2048
|
+
this.emit(`option:${option.name()}`);
|
|
2049
|
+
args.unshift(`-${arg.slice(2)}`);
|
|
2050
|
+
}
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
if (/^--[^=]+=/.test(arg)) {
|
|
2055
|
+
const index = arg.indexOf("=");
|
|
2056
|
+
const option = this._findOption(arg.slice(0, index));
|
|
2057
|
+
if (option && (option.required || option.optional)) {
|
|
2058
|
+
this.emit(`option:${option.name()}`, arg.slice(index + 1));
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
if (maybeOption(arg)) {
|
|
2063
|
+
dest = unknown;
|
|
2064
|
+
}
|
|
2065
|
+
if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
|
|
2066
|
+
if (this._findCommand(arg)) {
|
|
2067
|
+
operands.push(arg);
|
|
2068
|
+
if (args.length > 0)
|
|
2069
|
+
unknown.push(...args);
|
|
2070
|
+
break;
|
|
2071
|
+
} else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
|
|
2072
|
+
operands.push(arg);
|
|
2073
|
+
if (args.length > 0)
|
|
2074
|
+
operands.push(...args);
|
|
2075
|
+
break;
|
|
2076
|
+
} else if (this._defaultCommandName) {
|
|
2077
|
+
unknown.push(arg);
|
|
2078
|
+
if (args.length > 0)
|
|
2079
|
+
unknown.push(...args);
|
|
2080
|
+
break;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (this._passThroughOptions) {
|
|
2084
|
+
dest.push(arg);
|
|
2085
|
+
if (args.length > 0)
|
|
2086
|
+
dest.push(...args);
|
|
2087
|
+
break;
|
|
2088
|
+
}
|
|
2089
|
+
dest.push(arg);
|
|
2090
|
+
}
|
|
2091
|
+
return { operands, unknown };
|
|
2092
|
+
}
|
|
2093
|
+
opts() {
|
|
2094
|
+
if (this._storeOptionsAsProperties) {
|
|
2095
|
+
const result = {};
|
|
2096
|
+
const len = this.options.length;
|
|
2097
|
+
for (let i = 0;i < len; i++) {
|
|
2098
|
+
const key = this.options[i].attributeName();
|
|
2099
|
+
result[key] = key === this._versionOptionName ? this._version : this[key];
|
|
2100
|
+
}
|
|
2101
|
+
return result;
|
|
2102
|
+
}
|
|
2103
|
+
return this._optionValues;
|
|
2104
|
+
}
|
|
2105
|
+
optsWithGlobals() {
|
|
2106
|
+
return this._getCommandAndAncestors().reduce((combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), {});
|
|
2107
|
+
}
|
|
2108
|
+
error(message, errorOptions) {
|
|
2109
|
+
this._outputConfiguration.outputError(`${message}
|
|
2110
|
+
`, this._outputConfiguration.writeErr);
|
|
2111
|
+
if (typeof this._showHelpAfterError === "string") {
|
|
2112
|
+
this._outputConfiguration.writeErr(`${this._showHelpAfterError}
|
|
2113
|
+
`);
|
|
2114
|
+
} else if (this._showHelpAfterError) {
|
|
2115
|
+
this._outputConfiguration.writeErr(`
|
|
2116
|
+
`);
|
|
2117
|
+
this.outputHelp({ error: true });
|
|
2118
|
+
}
|
|
2119
|
+
const config = errorOptions || {};
|
|
2120
|
+
const exitCode = config.exitCode || 1;
|
|
2121
|
+
const code = config.code || "commander.error";
|
|
2122
|
+
this._exit(exitCode, code, message);
|
|
2123
|
+
}
|
|
2124
|
+
_parseOptionsEnv() {
|
|
2125
|
+
this.options.forEach((option) => {
|
|
2126
|
+
if (option.envVar && option.envVar in process2.env) {
|
|
2127
|
+
const optionKey = option.attributeName();
|
|
2128
|
+
if (this.getOptionValue(optionKey) === undefined || ["default", "config", "env"].includes(this.getOptionValueSource(optionKey))) {
|
|
2129
|
+
if (option.required || option.optional) {
|
|
2130
|
+
this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
|
|
2131
|
+
} else {
|
|
2132
|
+
this.emit(`optionEnv:${option.name()}`);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
_parseOptionsImplied() {
|
|
2139
|
+
const dualHelper = new DualOptions(this.options);
|
|
2140
|
+
const hasCustomOptionValue = (optionKey) => {
|
|
2141
|
+
return this.getOptionValue(optionKey) !== undefined && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
|
|
2142
|
+
};
|
|
2143
|
+
this.options.filter((option) => option.implied !== undefined && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(this.getOptionValue(option.attributeName()), option)).forEach((option) => {
|
|
2144
|
+
Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
|
|
2145
|
+
this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], "implied");
|
|
2146
|
+
});
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
missingArgument(name) {
|
|
2150
|
+
const message = `error: missing required argument '${name}'`;
|
|
2151
|
+
this.error(message, { code: "commander.missingArgument" });
|
|
2152
|
+
}
|
|
2153
|
+
optionMissingArgument(option) {
|
|
2154
|
+
const message = `error: option '${option.flags}' argument missing`;
|
|
2155
|
+
this.error(message, { code: "commander.optionMissingArgument" });
|
|
2156
|
+
}
|
|
2157
|
+
missingMandatoryOptionValue(option) {
|
|
2158
|
+
const message = `error: required option '${option.flags}' not specified`;
|
|
2159
|
+
this.error(message, { code: "commander.missingMandatoryOptionValue" });
|
|
2160
|
+
}
|
|
2161
|
+
_conflictingOption(option, conflictingOption) {
|
|
2162
|
+
const findBestOptionFromValue = (option2) => {
|
|
2163
|
+
const optionKey = option2.attributeName();
|
|
2164
|
+
const optionValue = this.getOptionValue(optionKey);
|
|
2165
|
+
const negativeOption = this.options.find((target) => target.negate && optionKey === target.attributeName());
|
|
2166
|
+
const positiveOption = this.options.find((target) => !target.negate && optionKey === target.attributeName());
|
|
2167
|
+
if (negativeOption && (negativeOption.presetArg === undefined && optionValue === false || negativeOption.presetArg !== undefined && optionValue === negativeOption.presetArg)) {
|
|
2168
|
+
return negativeOption;
|
|
2169
|
+
}
|
|
2170
|
+
return positiveOption || option2;
|
|
2171
|
+
};
|
|
2172
|
+
const getErrorMessage = (option2) => {
|
|
2173
|
+
const bestOption = findBestOptionFromValue(option2);
|
|
2174
|
+
const optionKey = bestOption.attributeName();
|
|
2175
|
+
const source = this.getOptionValueSource(optionKey);
|
|
2176
|
+
if (source === "env") {
|
|
2177
|
+
return `environment variable '${bestOption.envVar}'`;
|
|
2178
|
+
}
|
|
2179
|
+
return `option '${bestOption.flags}'`;
|
|
2180
|
+
};
|
|
2181
|
+
const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
|
|
2182
|
+
this.error(message, { code: "commander.conflictingOption" });
|
|
2183
|
+
}
|
|
2184
|
+
unknownOption(flag) {
|
|
2185
|
+
if (this._allowUnknownOption)
|
|
2186
|
+
return;
|
|
2187
|
+
let suggestion = "";
|
|
2188
|
+
if (flag.startsWith("--") && this._showSuggestionAfterError) {
|
|
2189
|
+
let candidateFlags = [];
|
|
2190
|
+
let command = this;
|
|
2191
|
+
do {
|
|
2192
|
+
const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
|
|
2193
|
+
candidateFlags = candidateFlags.concat(moreFlags);
|
|
2194
|
+
command = command.parent;
|
|
2195
|
+
} while (command && !command._enablePositionalOptions);
|
|
2196
|
+
suggestion = suggestSimilar(flag, candidateFlags);
|
|
2197
|
+
}
|
|
2198
|
+
const message = `error: unknown option '${flag}'${suggestion}`;
|
|
2199
|
+
this.error(message, { code: "commander.unknownOption" });
|
|
2200
|
+
}
|
|
2201
|
+
_excessArguments(receivedArgs) {
|
|
2202
|
+
if (this._allowExcessArguments)
|
|
2203
|
+
return;
|
|
2204
|
+
const expected = this.registeredArguments.length;
|
|
2205
|
+
const s = expected === 1 ? "" : "s";
|
|
2206
|
+
const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
|
|
2207
|
+
const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
|
|
2208
|
+
this.error(message, { code: "commander.excessArguments" });
|
|
2209
|
+
}
|
|
2210
|
+
unknownCommand() {
|
|
2211
|
+
const unknownName = this.args[0];
|
|
2212
|
+
let suggestion = "";
|
|
2213
|
+
if (this._showSuggestionAfterError) {
|
|
2214
|
+
const candidateNames = [];
|
|
2215
|
+
this.createHelp().visibleCommands(this).forEach((command) => {
|
|
2216
|
+
candidateNames.push(command.name());
|
|
2217
|
+
if (command.alias())
|
|
2218
|
+
candidateNames.push(command.alias());
|
|
2219
|
+
});
|
|
2220
|
+
suggestion = suggestSimilar(unknownName, candidateNames);
|
|
2221
|
+
}
|
|
2222
|
+
const message = `error: unknown command '${unknownName}'${suggestion}`;
|
|
2223
|
+
this.error(message, { code: "commander.unknownCommand" });
|
|
2224
|
+
}
|
|
2225
|
+
version(str, flags, description) {
|
|
2226
|
+
if (str === undefined)
|
|
2227
|
+
return this._version;
|
|
2228
|
+
this._version = str;
|
|
2229
|
+
flags = flags || "-V, --version";
|
|
2230
|
+
description = description || "output the version number";
|
|
2231
|
+
const versionOption = this.createOption(flags, description);
|
|
2232
|
+
this._versionOptionName = versionOption.attributeName();
|
|
2233
|
+
this._registerOption(versionOption);
|
|
2234
|
+
this.on("option:" + versionOption.name(), () => {
|
|
2235
|
+
this._outputConfiguration.writeOut(`${str}
|
|
2236
|
+
`);
|
|
2237
|
+
this._exit(0, "commander.version", str);
|
|
2238
|
+
});
|
|
2239
|
+
return this;
|
|
2240
|
+
}
|
|
2241
|
+
description(str, argsDescription) {
|
|
2242
|
+
if (str === undefined && argsDescription === undefined)
|
|
2243
|
+
return this._description;
|
|
2244
|
+
this._description = str;
|
|
2245
|
+
if (argsDescription) {
|
|
2246
|
+
this._argsDescription = argsDescription;
|
|
2247
|
+
}
|
|
2248
|
+
return this;
|
|
2249
|
+
}
|
|
2250
|
+
summary(str) {
|
|
2251
|
+
if (str === undefined)
|
|
2252
|
+
return this._summary;
|
|
2253
|
+
this._summary = str;
|
|
2254
|
+
return this;
|
|
2255
|
+
}
|
|
2256
|
+
alias(alias) {
|
|
2257
|
+
if (alias === undefined)
|
|
2258
|
+
return this._aliases[0];
|
|
2259
|
+
let command = this;
|
|
2260
|
+
if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
|
|
2261
|
+
command = this.commands[this.commands.length - 1];
|
|
2262
|
+
}
|
|
2263
|
+
if (alias === command._name)
|
|
2264
|
+
throw new Error("Command alias can't be the same as its name");
|
|
2265
|
+
const matchingCommand = this.parent?._findCommand(alias);
|
|
2266
|
+
if (matchingCommand) {
|
|
2267
|
+
const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
|
|
2268
|
+
throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
|
|
2269
|
+
}
|
|
2270
|
+
command._aliases.push(alias);
|
|
2271
|
+
return this;
|
|
2272
|
+
}
|
|
2273
|
+
aliases(aliases) {
|
|
2274
|
+
if (aliases === undefined)
|
|
2275
|
+
return this._aliases;
|
|
2276
|
+
aliases.forEach((alias) => this.alias(alias));
|
|
2277
|
+
return this;
|
|
2278
|
+
}
|
|
2279
|
+
usage(str) {
|
|
2280
|
+
if (str === undefined) {
|
|
2281
|
+
if (this._usage)
|
|
2282
|
+
return this._usage;
|
|
2283
|
+
const args = this.registeredArguments.map((arg) => {
|
|
2284
|
+
return humanReadableArgName(arg);
|
|
2285
|
+
});
|
|
2286
|
+
return [].concat(this.options.length || this._helpOption !== null ? "[options]" : [], this.commands.length ? "[command]" : [], this.registeredArguments.length ? args : []).join(" ");
|
|
2287
|
+
}
|
|
2288
|
+
this._usage = str;
|
|
2289
|
+
return this;
|
|
2290
|
+
}
|
|
2291
|
+
name(str) {
|
|
2292
|
+
if (str === undefined)
|
|
2293
|
+
return this._name;
|
|
2294
|
+
this._name = str;
|
|
2295
|
+
return this;
|
|
2296
|
+
}
|
|
2297
|
+
nameFromFilename(filename) {
|
|
2298
|
+
this._name = path.basename(filename, path.extname(filename));
|
|
2299
|
+
return this;
|
|
2300
|
+
}
|
|
2301
|
+
executableDir(path2) {
|
|
2302
|
+
if (path2 === undefined)
|
|
2303
|
+
return this._executableDir;
|
|
2304
|
+
this._executableDir = path2;
|
|
2305
|
+
return this;
|
|
2306
|
+
}
|
|
2307
|
+
helpInformation(contextOptions) {
|
|
2308
|
+
const helper = this.createHelp();
|
|
2309
|
+
const context = this._getOutputContext(contextOptions);
|
|
2310
|
+
helper.prepareContext({
|
|
2311
|
+
error: context.error,
|
|
2312
|
+
helpWidth: context.helpWidth,
|
|
2313
|
+
outputHasColors: context.hasColors
|
|
2314
|
+
});
|
|
2315
|
+
const text = helper.formatHelp(this, helper);
|
|
2316
|
+
if (context.hasColors)
|
|
2317
|
+
return text;
|
|
2318
|
+
return this._outputConfiguration.stripColor(text);
|
|
2319
|
+
}
|
|
2320
|
+
_getOutputContext(contextOptions) {
|
|
2321
|
+
contextOptions = contextOptions || {};
|
|
2322
|
+
const error = !!contextOptions.error;
|
|
2323
|
+
let baseWrite;
|
|
2324
|
+
let hasColors;
|
|
2325
|
+
let helpWidth;
|
|
2326
|
+
if (error) {
|
|
2327
|
+
baseWrite = (str) => this._outputConfiguration.writeErr(str);
|
|
2328
|
+
hasColors = this._outputConfiguration.getErrHasColors();
|
|
2329
|
+
helpWidth = this._outputConfiguration.getErrHelpWidth();
|
|
2330
|
+
} else {
|
|
2331
|
+
baseWrite = (str) => this._outputConfiguration.writeOut(str);
|
|
2332
|
+
hasColors = this._outputConfiguration.getOutHasColors();
|
|
2333
|
+
helpWidth = this._outputConfiguration.getOutHelpWidth();
|
|
2334
|
+
}
|
|
2335
|
+
const write = (str) => {
|
|
2336
|
+
if (!hasColors)
|
|
2337
|
+
str = this._outputConfiguration.stripColor(str);
|
|
2338
|
+
return baseWrite(str);
|
|
2339
|
+
};
|
|
2340
|
+
return { error, write, hasColors, helpWidth };
|
|
2341
|
+
}
|
|
2342
|
+
outputHelp(contextOptions) {
|
|
2343
|
+
let deprecatedCallback;
|
|
2344
|
+
if (typeof contextOptions === "function") {
|
|
2345
|
+
deprecatedCallback = contextOptions;
|
|
2346
|
+
contextOptions = undefined;
|
|
2347
|
+
}
|
|
2348
|
+
const outputContext = this._getOutputContext(contextOptions);
|
|
2349
|
+
const eventContext = {
|
|
2350
|
+
error: outputContext.error,
|
|
2351
|
+
write: outputContext.write,
|
|
2352
|
+
command: this
|
|
2353
|
+
};
|
|
2354
|
+
this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
|
|
2355
|
+
this.emit("beforeHelp", eventContext);
|
|
2356
|
+
let helpInformation = this.helpInformation({ error: outputContext.error });
|
|
2357
|
+
if (deprecatedCallback) {
|
|
2358
|
+
helpInformation = deprecatedCallback(helpInformation);
|
|
2359
|
+
if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
|
|
2360
|
+
throw new Error("outputHelp callback must return a string or a Buffer");
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
outputContext.write(helpInformation);
|
|
2364
|
+
if (this._getHelpOption()?.long) {
|
|
2365
|
+
this.emit(this._getHelpOption().long);
|
|
2366
|
+
}
|
|
2367
|
+
this.emit("afterHelp", eventContext);
|
|
2368
|
+
this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp", eventContext));
|
|
2369
|
+
}
|
|
2370
|
+
helpOption(flags, description) {
|
|
2371
|
+
if (typeof flags === "boolean") {
|
|
2372
|
+
if (flags) {
|
|
2373
|
+
this._helpOption = this._helpOption ?? undefined;
|
|
2374
|
+
} else {
|
|
2375
|
+
this._helpOption = null;
|
|
2376
|
+
}
|
|
2377
|
+
return this;
|
|
2378
|
+
}
|
|
2379
|
+
flags = flags ?? "-h, --help";
|
|
2380
|
+
description = description ?? "display help for command";
|
|
2381
|
+
this._helpOption = this.createOption(flags, description);
|
|
2382
|
+
return this;
|
|
2383
|
+
}
|
|
2384
|
+
_getHelpOption() {
|
|
2385
|
+
if (this._helpOption === undefined) {
|
|
2386
|
+
this.helpOption(undefined, undefined);
|
|
2387
|
+
}
|
|
2388
|
+
return this._helpOption;
|
|
2389
|
+
}
|
|
2390
|
+
addHelpOption(option) {
|
|
2391
|
+
this._helpOption = option;
|
|
2392
|
+
return this;
|
|
2393
|
+
}
|
|
2394
|
+
help(contextOptions) {
|
|
2395
|
+
this.outputHelp(contextOptions);
|
|
2396
|
+
let exitCode = Number(process2.exitCode ?? 0);
|
|
2397
|
+
if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
|
|
2398
|
+
exitCode = 1;
|
|
2399
|
+
}
|
|
2400
|
+
this._exit(exitCode, "commander.help", "(outputHelp)");
|
|
2401
|
+
}
|
|
2402
|
+
addHelpText(position, text) {
|
|
2403
|
+
const allowedValues = ["beforeAll", "before", "after", "afterAll"];
|
|
2404
|
+
if (!allowedValues.includes(position)) {
|
|
2405
|
+
throw new Error(`Unexpected value for position to addHelpText.
|
|
2406
|
+
Expecting one of '${allowedValues.join("', '")}'`);
|
|
2407
|
+
}
|
|
2408
|
+
const helpEvent = `${position}Help`;
|
|
2409
|
+
this.on(helpEvent, (context) => {
|
|
2410
|
+
let helpStr;
|
|
2411
|
+
if (typeof text === "function") {
|
|
2412
|
+
helpStr = text({ error: context.error, command: context.command });
|
|
2413
|
+
} else {
|
|
2414
|
+
helpStr = text;
|
|
2415
|
+
}
|
|
2416
|
+
if (helpStr) {
|
|
2417
|
+
context.write(`${helpStr}
|
|
2418
|
+
`);
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
return this;
|
|
2422
|
+
}
|
|
2423
|
+
_outputHelpIfRequested(args) {
|
|
2424
|
+
const helpOption = this._getHelpOption();
|
|
2425
|
+
const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
|
|
2426
|
+
if (helpRequested) {
|
|
2427
|
+
this.outputHelp();
|
|
2428
|
+
this._exit(0, "commander.helpDisplayed", "(outputHelp)");
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
function incrementNodeInspectorPort(args) {
|
|
2433
|
+
return args.map((arg) => {
|
|
2434
|
+
if (!arg.startsWith("--inspect")) {
|
|
2435
|
+
return arg;
|
|
2436
|
+
}
|
|
2437
|
+
let debugOption;
|
|
2438
|
+
let debugHost = "127.0.0.1";
|
|
2439
|
+
let debugPort = "9229";
|
|
2440
|
+
let match;
|
|
2441
|
+
if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
|
|
2442
|
+
debugOption = match[1];
|
|
2443
|
+
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
|
|
2444
|
+
debugOption = match[1];
|
|
2445
|
+
if (/^\d+$/.test(match[3])) {
|
|
2446
|
+
debugPort = match[3];
|
|
2447
|
+
} else {
|
|
2448
|
+
debugHost = match[3];
|
|
2449
|
+
}
|
|
2450
|
+
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
|
|
2451
|
+
debugOption = match[1];
|
|
2452
|
+
debugHost = match[3];
|
|
2453
|
+
debugPort = match[4];
|
|
2454
|
+
}
|
|
2455
|
+
if (debugOption && debugPort !== "0") {
|
|
2456
|
+
return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
|
|
2457
|
+
}
|
|
2458
|
+
return arg;
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
function useColor() {
|
|
2462
|
+
if (process2.env.NO_COLOR || process2.env.FORCE_COLOR === "0" || process2.env.FORCE_COLOR === "false")
|
|
2463
|
+
return false;
|
|
2464
|
+
if (process2.env.FORCE_COLOR || process2.env.CLICOLOR_FORCE !== undefined)
|
|
2465
|
+
return true;
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
exports.Command = Command;
|
|
2469
|
+
exports.useColor = useColor;
|
|
2470
|
+
});
|
|
2471
|
+
|
|
2472
|
+
// ../../node_modules/commander/index.js
|
|
2473
|
+
var require_commander = __commonJS((exports) => {
|
|
2474
|
+
var { Argument } = require_argument();
|
|
2475
|
+
var { Command } = require_command();
|
|
2476
|
+
var { CommanderError, InvalidArgumentError } = require_error();
|
|
2477
|
+
var { Help } = require_help();
|
|
2478
|
+
var { Option } = require_option();
|
|
2479
|
+
exports.program = new Command;
|
|
2480
|
+
exports.createCommand = (name) => new Command(name);
|
|
2481
|
+
exports.createOption = (flags, description) => new Option(flags, description);
|
|
2482
|
+
exports.createArgument = (name, description) => new Argument(name, description);
|
|
2483
|
+
exports.Command = Command;
|
|
2484
|
+
exports.Option = Option;
|
|
2485
|
+
exports.Argument = Argument;
|
|
2486
|
+
exports.Help = Help;
|
|
2487
|
+
exports.CommanderError = CommanderError;
|
|
2488
|
+
exports.InvalidArgumentError = InvalidArgumentError;
|
|
2489
|
+
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
// ../../node_modules/picocolors/picocolors.js
|
|
2493
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
2494
|
+
var p = process || {};
|
|
2495
|
+
var argv = p.argv || [];
|
|
2496
|
+
var env = p.env || {};
|
|
2497
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
2498
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
2499
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
2500
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
2501
|
+
};
|
|
2502
|
+
var replaceClose = (string, close, replace, index) => {
|
|
2503
|
+
let result = "", cursor = 0;
|
|
2504
|
+
do {
|
|
2505
|
+
result += string.substring(cursor, index) + replace;
|
|
2506
|
+
cursor = index + close.length;
|
|
2507
|
+
index = string.indexOf(close, cursor);
|
|
2508
|
+
} while (~index);
|
|
2509
|
+
return result + string.substring(cursor);
|
|
2510
|
+
};
|
|
2511
|
+
var createColors = (enabled = isColorSupported) => {
|
|
2512
|
+
let f = enabled ? formatter : () => String;
|
|
2513
|
+
return {
|
|
2514
|
+
isColorSupported: enabled,
|
|
2515
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
2516
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
2517
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
2518
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
2519
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
2520
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
2521
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
2522
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
2523
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
2524
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
2525
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
2526
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
2527
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
2528
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
2529
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
2530
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
2531
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
2532
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
2533
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
2534
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
2535
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
2536
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
2537
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
2538
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
2539
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
2540
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
2541
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
2542
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
2543
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
2544
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
2545
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
2546
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
2547
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
2548
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
2549
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
2550
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
2551
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
2552
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
2553
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
2554
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
2555
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
2556
|
+
};
|
|
2557
|
+
};
|
|
2558
|
+
module.exports = createColors();
|
|
2559
|
+
module.exports.createColors = createColors;
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
// src/cli.ts
|
|
2563
|
+
var import_config = __toESM(require_config(), 1);
|
|
2564
|
+
|
|
2565
|
+
// ../../node_modules/commander/esm.mjs
|
|
2566
|
+
var import__ = __toESM(require_commander(), 1);
|
|
2567
|
+
var {
|
|
2568
|
+
program,
|
|
2569
|
+
createCommand,
|
|
2570
|
+
createArgument,
|
|
2571
|
+
createOption,
|
|
2572
|
+
CommanderError,
|
|
2573
|
+
InvalidArgumentError,
|
|
2574
|
+
InvalidOptionArgumentError,
|
|
2575
|
+
Command,
|
|
2576
|
+
Argument,
|
|
2577
|
+
Option,
|
|
2578
|
+
Help
|
|
2579
|
+
} = import__.default;
|
|
2580
|
+
|
|
2581
|
+
// src/utils/logger.ts
|
|
2582
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
2583
|
+
var verboseEnabled = false;
|
|
2584
|
+
function setVerbose(enabled) {
|
|
2585
|
+
verboseEnabled = enabled;
|
|
2586
|
+
}
|
|
2587
|
+
function info(msg) {
|
|
2588
|
+
console.log(import_picocolors.default.blue("i") + " " + msg);
|
|
2589
|
+
}
|
|
2590
|
+
function success(msg) {
|
|
2591
|
+
console.log(import_picocolors.default.green("✓") + " " + msg);
|
|
2592
|
+
}
|
|
2593
|
+
function warn(msg) {
|
|
2594
|
+
console.log(import_picocolors.default.yellow("!") + " " + msg);
|
|
2595
|
+
}
|
|
2596
|
+
function error(msg) {
|
|
2597
|
+
console.error(import_picocolors.default.red("✗") + " " + msg);
|
|
2598
|
+
}
|
|
2599
|
+
function step(current, total, name, description) {
|
|
2600
|
+
console.log(import_picocolors.default.cyan(`[${current}/${total}]`) + " " + import_picocolors.default.bold(name) + ": " + description);
|
|
2601
|
+
}
|
|
2602
|
+
function verbose(msg) {
|
|
2603
|
+
if (verboseEnabled) {
|
|
2604
|
+
console.log(import_picocolors.default.gray(" " + msg));
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
function header(title) {
|
|
2608
|
+
console.log(`
|
|
2609
|
+
` + import_picocolors.default.bold(import_picocolors.default.magenta(title)));
|
|
2610
|
+
console.log(import_picocolors.default.gray("─".repeat(title.length + 4)));
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// src/commands/record.ts
|
|
2614
|
+
import { join as join2 } from "node:path";
|
|
2615
|
+
|
|
2616
|
+
// src/utils/process.ts
|
|
2617
|
+
import { spawn } from "node:child_process";
|
|
2618
|
+
function spawnInteractive(command, args, options) {
|
|
2619
|
+
return spawn(command, args, {
|
|
2620
|
+
stdio: "inherit",
|
|
2621
|
+
...options
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
function spawnAndCapture(command, args, options) {
|
|
2625
|
+
return new Promise((resolve, reject) => {
|
|
2626
|
+
const child = spawn(command, args, {
|
|
2627
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
2628
|
+
...options
|
|
2629
|
+
});
|
|
2630
|
+
let stdout = "";
|
|
2631
|
+
let stderr = "";
|
|
2632
|
+
child.stdout?.on("data", (data) => {
|
|
2633
|
+
stdout += data.toString();
|
|
2634
|
+
process.stdout.write(data);
|
|
2635
|
+
});
|
|
2636
|
+
child.stderr?.on("data", (data) => {
|
|
2637
|
+
stderr += data.toString();
|
|
2638
|
+
process.stderr.write(data);
|
|
2639
|
+
});
|
|
2640
|
+
child.on("error", reject);
|
|
2641
|
+
child.on("close", (code) => {
|
|
2642
|
+
resolve({
|
|
2643
|
+
exitCode: code ?? 1,
|
|
2644
|
+
stdout,
|
|
2645
|
+
stderr
|
|
2646
|
+
});
|
|
2647
|
+
});
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
function waitForProcess(child) {
|
|
2651
|
+
return new Promise((resolve, reject) => {
|
|
2652
|
+
child.on("error", reject);
|
|
2653
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/config/paths.ts
|
|
2658
|
+
import { join } from "node:path";
|
|
2659
|
+
function resolvePaths(config, key) {
|
|
2660
|
+
const root = getProjectRoot();
|
|
2661
|
+
const workingDir = join(root, config.paths.workingDir);
|
|
2662
|
+
const testsDir = join(root, config.paths.tests);
|
|
2663
|
+
const recordingsDir = join(root, config.paths.recordings);
|
|
2664
|
+
const transcriptsDir = join(root, config.paths.transcripts);
|
|
2665
|
+
const tracesDir = join(root, config.paths.traces);
|
|
2666
|
+
const qaDir = join(root, config.paths.qaOutput);
|
|
2667
|
+
const keyDir = key ? join(workingDir, key) : null;
|
|
2668
|
+
const testDir = key ? join(testsDir, key) : null;
|
|
2669
|
+
const testFile = key ? join(testsDir, key, `${key}.test.ts`) : null;
|
|
2670
|
+
const scenarioDir = key ? join(testsDir, key) : null;
|
|
2671
|
+
const scenarioFile = key ? join(testsDir, key, `${key}.yaml`) : null;
|
|
2672
|
+
const qaFile = key ? join(qaDir, `${key}.md`) : null;
|
|
2673
|
+
const needsZephyr = config.outputTarget === "zephyr" || config.outputTarget === "both";
|
|
2674
|
+
const zephyrJsonFile = needsZephyr && key ? join(workingDir, key, `${key}-zephyr-test-case.json`) : null;
|
|
2675
|
+
const zephyrXmlFile = needsZephyr && key ? join(testsDir, key, `${key}-zephyr-import.xml`) : null;
|
|
2676
|
+
return {
|
|
2677
|
+
projectRoot: root,
|
|
2678
|
+
workingDir,
|
|
2679
|
+
keyDir,
|
|
2680
|
+
testsDir,
|
|
2681
|
+
testDir,
|
|
2682
|
+
testFile,
|
|
2683
|
+
scenarioDir,
|
|
2684
|
+
scenarioFile,
|
|
2685
|
+
recordingsDir,
|
|
2686
|
+
transcriptsDir,
|
|
2687
|
+
tracesDir,
|
|
2688
|
+
qaDir,
|
|
2689
|
+
qaFile,
|
|
2690
|
+
zephyrJsonFile,
|
|
2691
|
+
zephyrXmlFile
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// src/commands/_shared.ts
|
|
2696
|
+
async function resolveCommandContext(program2) {
|
|
2697
|
+
const opts = program2.opts();
|
|
2698
|
+
const config = await loadConfig();
|
|
2699
|
+
const key = opts.key;
|
|
2700
|
+
const paths = resolvePaths(config, key);
|
|
2701
|
+
const provider = opts.provider ?? process.env.AI_PROVIDER ?? config.llm.provider;
|
|
2702
|
+
const model = opts.model ?? process.env.AI_MODEL ?? config.llm.model ?? undefined;
|
|
2703
|
+
return {
|
|
2704
|
+
key,
|
|
2705
|
+
provider,
|
|
2706
|
+
model,
|
|
2707
|
+
verbose: opts.verbose ?? false,
|
|
2708
|
+
voice: opts.voice !== false && config.voice.enabled,
|
|
2709
|
+
trace: opts.trace !== false,
|
|
2710
|
+
config,
|
|
2711
|
+
paths
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
// src/commands/record.ts
|
|
2716
|
+
function registerRecord(program2) {
|
|
2717
|
+
program2.command("record [session]").description("Launch Playwright codegen with audio recording").action(async (session) => {
|
|
2718
|
+
const ctx = await resolveCommandContext(program2);
|
|
2719
|
+
const scriptPath = join2(getPackageRoot(), "scripts", "codegen-env.mjs");
|
|
2720
|
+
const args = [];
|
|
2721
|
+
if (ctx.key) {
|
|
2722
|
+
args.push(ctx.key);
|
|
2723
|
+
} else if (session) {
|
|
2724
|
+
args.push(session);
|
|
2725
|
+
}
|
|
2726
|
+
if (!ctx.voice)
|
|
2727
|
+
args.push("--no-voice");
|
|
2728
|
+
if (!ctx.trace)
|
|
2729
|
+
args.push("--no-trace");
|
|
2730
|
+
info(`Launching codegen recording...`);
|
|
2731
|
+
if (ctx.key)
|
|
2732
|
+
info(`Issue key: ${ctx.key}`);
|
|
2733
|
+
verbose(`Script: ${scriptPath}`);
|
|
2734
|
+
verbose(`Args: ${args.join(" ")}`);
|
|
2735
|
+
const child = spawnInteractive("node", [scriptPath, ...args], {
|
|
2736
|
+
cwd: ctx.paths.projectRoot,
|
|
2737
|
+
env: {
|
|
2738
|
+
...process.env,
|
|
2739
|
+
E2E_AI_PROJECT_ROOT: ctx.paths.projectRoot,
|
|
2740
|
+
E2E_AI_WORKING_DIR: ctx.config.paths.workingDir,
|
|
2741
|
+
E2E_AI_KEY: ctx.key ?? ""
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
const exitCode = await waitForProcess(child);
|
|
2745
|
+
if (exitCode === 0) {
|
|
2746
|
+
success("Recording session completed");
|
|
2747
|
+
} else {
|
|
2748
|
+
error(`Recording exited with code ${exitCode}`);
|
|
2749
|
+
process.exit(exitCode);
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/commands/transcribe.ts
|
|
2755
|
+
import { join as join4, basename } from "node:path";
|
|
2756
|
+
|
|
2757
|
+
// src/utils/fs.ts
|
|
2758
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2759
|
+
import { dirname, join as join3 } from "node:path";
|
|
2760
|
+
function ensureDir(dir) {
|
|
2761
|
+
if (!existsSync(dir)) {
|
|
2762
|
+
mkdirSync(dir, { recursive: true });
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
function readFile(filePath) {
|
|
2766
|
+
return readFileSync(filePath, "utf-8");
|
|
2767
|
+
}
|
|
2768
|
+
function writeFile(filePath, content) {
|
|
2769
|
+
ensureDir(dirname(filePath));
|
|
2770
|
+
writeFileSync(filePath, content, "utf-8");
|
|
2771
|
+
}
|
|
2772
|
+
function fileExists(filePath) {
|
|
2773
|
+
return existsSync(filePath);
|
|
2774
|
+
}
|
|
2775
|
+
function findFileWithPattern(dir, pattern) {
|
|
2776
|
+
if (!existsSync(dir))
|
|
2777
|
+
return;
|
|
2778
|
+
const files = readdirSync(dir).filter((f) => pattern.test(f)).sort();
|
|
2779
|
+
const match = files.length > 0 ? files[files.length - 1] : undefined;
|
|
2780
|
+
return match ? join3(dir, match) : undefined;
|
|
2781
|
+
}
|
|
2782
|
+
function timestampSuffix() {
|
|
2783
|
+
const now = new Date;
|
|
2784
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2785
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
// src/commands/transcribe.ts
|
|
2789
|
+
function registerTranscribe(program2) {
|
|
2790
|
+
program2.command("transcribe [session]").description("Transcribe .wav recording via OpenAI Whisper").action(async (session) => {
|
|
2791
|
+
const ctx = await resolveCommandContext(program2);
|
|
2792
|
+
const root = ctx.paths.projectRoot;
|
|
2793
|
+
const pkgRoot = getPackageRoot();
|
|
2794
|
+
let wavPath;
|
|
2795
|
+
if (session && session.endsWith(".wav")) {
|
|
2796
|
+
wavPath = join4(root, session);
|
|
2797
|
+
} else if (ctx.key) {
|
|
2798
|
+
const keyDir = join4(ctx.paths.workingDir, ctx.key, "recordings");
|
|
2799
|
+
wavPath = findFileWithPattern(keyDir, /\.wav$/);
|
|
2800
|
+
if (!wavPath) {
|
|
2801
|
+
wavPath = findFileWithPattern(join4(ctx.paths.workingDir, ctx.key), /voice-.*\.wav$/);
|
|
2802
|
+
}
|
|
2803
|
+
} else if (session) {
|
|
2804
|
+
wavPath = join4(ctx.paths.recordingsDir, `${session}.wav`);
|
|
2805
|
+
}
|
|
2806
|
+
if (!wavPath || !fileExists(wavPath)) {
|
|
2807
|
+
error(`No .wav file found${wavPath ? ` at ${wavPath}` : ""}`);
|
|
2808
|
+
process.exit(1);
|
|
2809
|
+
}
|
|
2810
|
+
info(`Transcribing: ${wavPath}`);
|
|
2811
|
+
const transcriber = await import(join4(pkgRoot, "scripts", "voice", "transcriber.mjs"));
|
|
2812
|
+
const segments = await transcriber.transcribe(wavPath);
|
|
2813
|
+
if (!segments || segments.length === 0) {
|
|
2814
|
+
warn("No speech segments detected");
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
success(`Transcribed ${segments.length} segments`);
|
|
2818
|
+
const sessionName = basename(wavPath, ".wav");
|
|
2819
|
+
const outputDir = ctx.key ? join4(ctx.paths.workingDir, ctx.key) : ctx.paths.transcriptsDir;
|
|
2820
|
+
const jsonPath = join4(outputDir, `${sessionName}-transcript.json`);
|
|
2821
|
+
const mdPath = join4(outputDir, `${sessionName}-transcript.md`);
|
|
2822
|
+
writeFile(jsonPath, JSON.stringify(segments, null, 2));
|
|
2823
|
+
success(`Transcript JSON: ${jsonPath}`);
|
|
2824
|
+
const md = generateTranscriptMd(sessionName, segments);
|
|
2825
|
+
writeFile(mdPath, md);
|
|
2826
|
+
success(`Transcript MD: ${mdPath}`);
|
|
2827
|
+
if (ctx.key) {
|
|
2828
|
+
const codegenDir = join4(ctx.paths.workingDir, ctx.key);
|
|
2829
|
+
const codegenFile = findFileWithPattern(codegenDir, /codegen-.*\.ts$/);
|
|
2830
|
+
if (codegenFile) {
|
|
2831
|
+
info("Merging voice comments into codegen...");
|
|
2832
|
+
const merger = await import(join4(pkgRoot, "scripts", "voice", "merger.mjs"));
|
|
2833
|
+
const codegenContent = readFile(codegenFile);
|
|
2834
|
+
const lastSegment = segments[segments.length - 1];
|
|
2835
|
+
const duration = lastSegment.end;
|
|
2836
|
+
const merged = merger.merge(codegenContent, segments, duration);
|
|
2837
|
+
writeFile(codegenFile, merged);
|
|
2838
|
+
success(`Voice comments merged into: ${codegenFile}`);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
function generateTranscriptMd(sessionName, segments) {
|
|
2844
|
+
const lines = [
|
|
2845
|
+
`# Transcript: ${sessionName}`,
|
|
2846
|
+
"",
|
|
2847
|
+
`**Segments**: ${segments.length}`,
|
|
2848
|
+
`**Duration**: ${formatTime(segments[segments.length - 1]?.end ?? 0)}`,
|
|
2849
|
+
"",
|
|
2850
|
+
"| Time | Text |",
|
|
2851
|
+
"|------|------|"
|
|
2852
|
+
];
|
|
2853
|
+
for (const seg of segments) {
|
|
2854
|
+
lines.push(`| ${formatTime(seg.start)} - ${formatTime(seg.end)} | ${seg.text} |`);
|
|
2855
|
+
}
|
|
2856
|
+
return lines.join(`
|
|
2857
|
+
`) + `
|
|
2858
|
+
`;
|
|
2859
|
+
}
|
|
2860
|
+
function formatTime(sec) {
|
|
2861
|
+
const m = Math.floor(sec / 60);
|
|
2862
|
+
const s = Math.floor(sec % 60);
|
|
2863
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
// src/commands/scenario.ts
|
|
2867
|
+
import { join as join7 } from "node:path";
|
|
2868
|
+
|
|
2869
|
+
// src/agents/loadAgent.ts
|
|
2870
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
2871
|
+
import { join as join5 } from "node:path";
|
|
2872
|
+
function loadAgent(agentName, config) {
|
|
2873
|
+
const agentDir = join5(getPackageRoot(), "agents");
|
|
2874
|
+
const filePath = join5(agentDir, `${agentName}.md`);
|
|
2875
|
+
let content;
|
|
2876
|
+
try {
|
|
2877
|
+
content = readFileSync2(filePath, "utf-8");
|
|
2878
|
+
} catch {
|
|
2879
|
+
throw new Error(`Agent file not found: ${filePath}`);
|
|
2880
|
+
}
|
|
2881
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
2882
|
+
const agentConfig = extractConfig(frontmatter);
|
|
2883
|
+
let systemPrompt = body;
|
|
2884
|
+
if (config) {
|
|
2885
|
+
const contextPath = join5(getProjectRoot(), config.contextFile);
|
|
2886
|
+
if (existsSync2(contextPath)) {
|
|
2887
|
+
const projectContext = readFileSync2(contextPath, "utf-8").trim();
|
|
2888
|
+
if (projectContext) {
|
|
2889
|
+
systemPrompt = `${body}
|
|
2890
|
+
|
|
2891
|
+
## Project Context
|
|
2892
|
+
|
|
2893
|
+
${projectContext}`;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
if (config.llm.agentModels[agentName]) {
|
|
2897
|
+
agentConfig.model = config.llm.agentModels[agentName];
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
const sections = parseSections(body);
|
|
2901
|
+
return {
|
|
2902
|
+
name: frontmatter.agent ?? agentName,
|
|
2903
|
+
systemPrompt,
|
|
2904
|
+
inputSchema: sections["Input Schema"],
|
|
2905
|
+
outputSchema: sections["Output Schema"],
|
|
2906
|
+
rules: sections["Rules"],
|
|
2907
|
+
example: sections["Example"],
|
|
2908
|
+
config: agentConfig
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
function parseFrontmatter(content) {
|
|
2912
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2913
|
+
if (!match)
|
|
2914
|
+
return { frontmatter: {}, body: content };
|
|
2915
|
+
const frontmatter = {};
|
|
2916
|
+
for (const line of match[1].split(`
|
|
2917
|
+
`)) {
|
|
2918
|
+
const colonIdx = line.indexOf(":");
|
|
2919
|
+
if (colonIdx === -1)
|
|
2920
|
+
continue;
|
|
2921
|
+
const key = line.slice(0, colonIdx).trim();
|
|
2922
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
2923
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
2924
|
+
value = value.slice(1, -1);
|
|
2925
|
+
if (value === "true")
|
|
2926
|
+
value = true;
|
|
2927
|
+
if (value === "false")
|
|
2928
|
+
value = false;
|
|
2929
|
+
if (!isNaN(Number(value)) && value !== "")
|
|
2930
|
+
value = Number(value);
|
|
2931
|
+
frontmatter[key] = value;
|
|
2932
|
+
}
|
|
2933
|
+
return { frontmatter, body: match[2] };
|
|
2934
|
+
}
|
|
2935
|
+
function extractConfig(frontmatter) {
|
|
2936
|
+
return {
|
|
2937
|
+
model: frontmatter.model,
|
|
2938
|
+
maxTokens: frontmatter.max_tokens ?? 4096,
|
|
2939
|
+
temperature: frontmatter.temperature ?? 0.2
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function parseSections(body) {
|
|
2943
|
+
const sections = {};
|
|
2944
|
+
const headingRegex = /^##\s+(.+)$/gm;
|
|
2945
|
+
const headings = [];
|
|
2946
|
+
let match;
|
|
2947
|
+
while ((match = headingRegex.exec(body)) !== null) {
|
|
2948
|
+
headings.push({ title: match[1].trim(), index: match.index });
|
|
2949
|
+
}
|
|
2950
|
+
const systemMatch = body.match(/^#\s+System Prompt\n([\s\S]*?)(?=\n##\s|$)/m);
|
|
2951
|
+
if (systemMatch) {
|
|
2952
|
+
sections["System Prompt"] = systemMatch[1].trim();
|
|
2953
|
+
}
|
|
2954
|
+
for (let i = 0;i < headings.length; i++) {
|
|
2955
|
+
const start = headings[i].index + body.slice(headings[i].index).indexOf(`
|
|
2956
|
+
`) + 1;
|
|
2957
|
+
const end = i + 1 < headings.length ? headings[i + 1].index : body.length;
|
|
2958
|
+
sections[headings[i].title] = body.slice(start, end).trim();
|
|
2959
|
+
}
|
|
2960
|
+
return sections;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// src/agents/callLLM.ts
|
|
2964
|
+
var DEFAULT_MODELS = {
|
|
2965
|
+
openai: "gpt-4o",
|
|
2966
|
+
anthropic: "claude-sonnet-4-20250514"
|
|
2967
|
+
};
|
|
2968
|
+
var MAX_RETRIES = 2;
|
|
2969
|
+
async function callLLM(request) {
|
|
2970
|
+
const model = request.model ?? DEFAULT_MODELS[request.provider];
|
|
2971
|
+
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
2972
|
+
try {
|
|
2973
|
+
if (request.provider === "openai") {
|
|
2974
|
+
return await callOpenAI({ ...request, model });
|
|
2975
|
+
}
|
|
2976
|
+
return await callAnthropic({ ...request, model });
|
|
2977
|
+
} catch (err) {
|
|
2978
|
+
const isRateLimit = err?.status === 429;
|
|
2979
|
+
if (isRateLimit && attempt < MAX_RETRIES) {
|
|
2980
|
+
const delay = Math.pow(2, attempt + 1) * 1000;
|
|
2981
|
+
warn(`Rate limited, retrying in ${delay / 1000}s...`);
|
|
2982
|
+
await sleep(delay);
|
|
2983
|
+
continue;
|
|
2984
|
+
}
|
|
2985
|
+
throw err;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
throw new Error("Unreachable");
|
|
2989
|
+
}
|
|
2990
|
+
async function callOpenAI(request) {
|
|
2991
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
2992
|
+
if (!apiKey)
|
|
2993
|
+
throw new Error("OPENAI_API_KEY not set");
|
|
2994
|
+
verbose(`Calling OpenAI ${request.model}...`);
|
|
2995
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
2996
|
+
method: "POST",
|
|
2997
|
+
headers: {
|
|
2998
|
+
"Content-Type": "application/json",
|
|
2999
|
+
Authorization: `Bearer ${apiKey}`
|
|
3000
|
+
},
|
|
3001
|
+
body: JSON.stringify({
|
|
3002
|
+
model: request.model,
|
|
3003
|
+
messages: [
|
|
3004
|
+
{ role: "system", content: request.systemPrompt },
|
|
3005
|
+
{ role: "user", content: request.userMessage }
|
|
3006
|
+
],
|
|
3007
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
3008
|
+
temperature: request.temperature ?? 0.2,
|
|
3009
|
+
...request.jsonMode ? { response_format: { type: "json_object" } } : {}
|
|
3010
|
+
})
|
|
3011
|
+
});
|
|
3012
|
+
if (!response.ok) {
|
|
3013
|
+
const body = await response.text();
|
|
3014
|
+
const err = new Error(`OpenAI API error ${response.status}: ${body}`);
|
|
3015
|
+
err.status = response.status;
|
|
3016
|
+
throw err;
|
|
3017
|
+
}
|
|
3018
|
+
const data = await response.json();
|
|
3019
|
+
const choice = data.choices?.[0];
|
|
3020
|
+
return {
|
|
3021
|
+
content: choice?.message?.content ?? "",
|
|
3022
|
+
model: data.model,
|
|
3023
|
+
usage: data.usage ? { inputTokens: data.usage.prompt_tokens, outputTokens: data.usage.completion_tokens } : undefined
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
async function callAnthropic(request) {
|
|
3027
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
3028
|
+
if (!apiKey)
|
|
3029
|
+
throw new Error("ANTHROPIC_API_KEY not set");
|
|
3030
|
+
verbose(`Calling Anthropic ${request.model}...`);
|
|
3031
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
3032
|
+
method: "POST",
|
|
3033
|
+
headers: {
|
|
3034
|
+
"Content-Type": "application/json",
|
|
3035
|
+
"x-api-key": apiKey,
|
|
3036
|
+
"anthropic-version": "2023-06-01"
|
|
3037
|
+
},
|
|
3038
|
+
body: JSON.stringify({
|
|
3039
|
+
model: request.model,
|
|
3040
|
+
system: request.systemPrompt,
|
|
3041
|
+
messages: [{ role: "user", content: request.userMessage }],
|
|
3042
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
3043
|
+
temperature: request.temperature ?? 0.2
|
|
3044
|
+
})
|
|
3045
|
+
});
|
|
3046
|
+
if (!response.ok) {
|
|
3047
|
+
const body = await response.text();
|
|
3048
|
+
const err = new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
3049
|
+
err.status = response.status;
|
|
3050
|
+
throw err;
|
|
3051
|
+
}
|
|
3052
|
+
const data = await response.json();
|
|
3053
|
+
const textBlock = data.content?.find((b) => b.type === "text");
|
|
3054
|
+
return {
|
|
3055
|
+
content: textBlock?.text ?? "",
|
|
3056
|
+
model: data.model,
|
|
3057
|
+
usage: data.usage ? { inputTokens: data.usage.input_tokens, outputTokens: data.usage.output_tokens } : undefined
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
function sleep(ms) {
|
|
3061
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// src/agents/parseResponse.ts
|
|
3065
|
+
function extractJSON(content) {
|
|
3066
|
+
const trimmed = content.trim();
|
|
3067
|
+
try {
|
|
3068
|
+
JSON.parse(trimmed);
|
|
3069
|
+
return trimmed;
|
|
3070
|
+
} catch {}
|
|
3071
|
+
const fenceMatch = trimmed.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
|
|
3072
|
+
if (fenceMatch) {
|
|
3073
|
+
try {
|
|
3074
|
+
JSON.parse(fenceMatch[1]);
|
|
3075
|
+
return fenceMatch[1];
|
|
3076
|
+
} catch {}
|
|
3077
|
+
}
|
|
3078
|
+
const jsonMatch = trimmed.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
|
|
3079
|
+
if (jsonMatch) {
|
|
3080
|
+
try {
|
|
3081
|
+
JSON.parse(jsonMatch[1]);
|
|
3082
|
+
return jsonMatch[1];
|
|
3083
|
+
} catch {}
|
|
3084
|
+
}
|
|
3085
|
+
throw new Error(`Could not extract JSON from LLM response. First 200 chars: ${trimmed.slice(0, 200)}`);
|
|
3086
|
+
}
|
|
3087
|
+
function extractYAML(content) {
|
|
3088
|
+
const trimmed = content.trim();
|
|
3089
|
+
const fenceMatch = trimmed.match(/```(?:ya?ml)?\s*\n([\s\S]*?)\n```/);
|
|
3090
|
+
if (fenceMatch) {
|
|
3091
|
+
return fenceMatch[1];
|
|
3092
|
+
}
|
|
3093
|
+
if (trimmed.match(/^\w+:/m)) {
|
|
3094
|
+
return trimmed;
|
|
3095
|
+
}
|
|
3096
|
+
const yamlStart = trimmed.search(/^name:/m);
|
|
3097
|
+
if (yamlStart !== -1) {
|
|
3098
|
+
return trimmed.slice(yamlStart);
|
|
3099
|
+
}
|
|
3100
|
+
return trimmed;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
// src/integrations/index.ts
|
|
3104
|
+
import { dirname as dirname3 } from "node:path";
|
|
3105
|
+
|
|
3106
|
+
// src/integrations/jira.ts
|
|
3107
|
+
import { join as join6 } from "node:path";
|
|
3108
|
+
async function fetchJiraContext(key, config, workingDir) {
|
|
3109
|
+
const jsonPath = join6(workingDir, key, `${key}-zephyr-test-case.json`);
|
|
3110
|
+
if (!fileExists(jsonPath))
|
|
3111
|
+
return null;
|
|
3112
|
+
const data = JSON.parse(readFile(jsonPath));
|
|
3113
|
+
return data.jiraContext ?? data.issueContext ?? null;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/integrations/linear.ts
|
|
3117
|
+
async function fetchLinearContext(key, config) {
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// src/integrations/zephyr.ts
|
|
3122
|
+
import { dirname as dirname2 } from "node:path";
|
|
3123
|
+
|
|
3124
|
+
// scripts/exporters/zephyr-json-to-import-xml.ts
|
|
3125
|
+
function cdata(value) {
|
|
3126
|
+
const s = String(value ?? "");
|
|
3127
|
+
return s.replace(/\]\]>/g, "]]]]><![CDATA[>");
|
|
3128
|
+
}
|
|
3129
|
+
function escapeXml(value) {
|
|
3130
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3131
|
+
}
|
|
3132
|
+
function getFeature(data, titlePrefix) {
|
|
3133
|
+
const parent = data.issueContext?.parent;
|
|
3134
|
+
if (parent && typeof parent === "string") {
|
|
3135
|
+
const withoutKey = parent.replace(/^[A-Z][A-Z0-9]*-[0-9]+\s*/i, "").trim();
|
|
3136
|
+
if (withoutKey)
|
|
3137
|
+
return withoutKey;
|
|
3138
|
+
}
|
|
3139
|
+
const labels = data.issueContext?.labels;
|
|
3140
|
+
if (Array.isArray(labels) && labels.length > 0 && typeof labels[0] === "string") {
|
|
3141
|
+
return labels[0];
|
|
3142
|
+
}
|
|
3143
|
+
return "General";
|
|
3144
|
+
}
|
|
3145
|
+
function formatExportTitle(data, titlePrefix) {
|
|
3146
|
+
const prefix = titlePrefix ?? "UI Automation";
|
|
3147
|
+
const feature = getFeature(data, titlePrefix);
|
|
3148
|
+
const testName = (data.title ?? "").trim() || "Untitled";
|
|
3149
|
+
return `${prefix} - ${feature} - ${testName}`;
|
|
3150
|
+
}
|
|
3151
|
+
function jsonToImportXml(data, titlePrefix) {
|
|
3152
|
+
const projectKey = data.issueContext?.project || data.issueKey?.split("-")[0] || "PROJECT";
|
|
3153
|
+
const exportDate = new Date().toISOString().replace("T", " ").replace(/\.[0-9]{3}Z$/, " UTC");
|
|
3154
|
+
const name = formatExportTitle(data, titlePrefix);
|
|
3155
|
+
const precondition = data.precondition ?? "";
|
|
3156
|
+
const steps = (data.steps ?? []).slice().sort((a, b) => a.stepNumber - b.stepNumber);
|
|
3157
|
+
const stepLines = steps.map((step2, i) => ` <step index="${i}">
|
|
3158
|
+
<customFields/>
|
|
3159
|
+
<description><![CDATA[${cdata(step2.description ?? "")}]]></description>
|
|
3160
|
+
<expectedResult><![CDATA[${cdata(step2.expectedResult ?? "")}]]></expectedResult>
|
|
3161
|
+
</step>`);
|
|
3162
|
+
const testCaseAttrs = data.issueKey ? ` key="${escapeXml(data.issueKey)}"` : "";
|
|
3163
|
+
const summary = data.issueContext?.summary || data.title || "";
|
|
3164
|
+
const issuesBlock = data.issueKey ? `<issues>
|
|
3165
|
+
<issue>
|
|
3166
|
+
<key>${escapeXml(data.issueKey)}</key>
|
|
3167
|
+
<summary><![CDATA[${cdata(summary)}]]></summary>
|
|
3168
|
+
</issue>
|
|
3169
|
+
</issues>` : "<issues/>";
|
|
3170
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
3171
|
+
<project>
|
|
3172
|
+
<projectId>0</projectId>
|
|
3173
|
+
<projectKey>${escapeXml(projectKey)}</projectKey>
|
|
3174
|
+
<exportDate>${exportDate}</exportDate>
|
|
3175
|
+
<testCases>
|
|
3176
|
+
<testCase${testCaseAttrs}>
|
|
3177
|
+
<attachments/>
|
|
3178
|
+
<confluencePageLinks/>
|
|
3179
|
+
<createdBy/>
|
|
3180
|
+
<createdOn>${exportDate}</createdOn>
|
|
3181
|
+
<customFields/>
|
|
3182
|
+
<folder><![CDATA[]]></folder>
|
|
3183
|
+
${issuesBlock}
|
|
3184
|
+
<labels/>
|
|
3185
|
+
<name><![CDATA[${cdata(name)}]]></name>
|
|
3186
|
+
<owner/>
|
|
3187
|
+
<precondition><![CDATA[${cdata(precondition)}]]></precondition>
|
|
3188
|
+
<priority><![CDATA[Normal]]></priority>
|
|
3189
|
+
<status><![CDATA[Draft]]></status>
|
|
3190
|
+
<parameters/>
|
|
3191
|
+
<testDataWrapper/>
|
|
3192
|
+
<testScript type="steps">
|
|
3193
|
+
<steps>
|
|
3194
|
+
${stepLines.join(`
|
|
3195
|
+
`)}
|
|
3196
|
+
</steps>
|
|
3197
|
+
</testScript>
|
|
3198
|
+
</testCase>
|
|
3199
|
+
</testCases>
|
|
3200
|
+
</project>
|
|
3201
|
+
`;
|
|
3202
|
+
}
|
|
3203
|
+
var isDirectRun = process.argv[1]?.includes("zephyr-json-to-import-xml");
|
|
3204
|
+
if (isDirectRun) {
|
|
3205
|
+
const fs = await import("node:fs");
|
|
3206
|
+
const path = await import("node:path");
|
|
3207
|
+
const args = process.argv.slice(2).filter((a) => !a.startsWith("--"));
|
|
3208
|
+
const outIdx = process.argv.indexOf("-o");
|
|
3209
|
+
const outArg = outIdx >= 0 ? process.argv[outIdx + 1] : null;
|
|
3210
|
+
const filePath = args[0];
|
|
3211
|
+
if (!filePath) {
|
|
3212
|
+
console.error("Usage: bun run zephyr-json-to-import-xml <test-case.json> [-o output.xml]");
|
|
3213
|
+
process.exit(1);
|
|
3214
|
+
}
|
|
3215
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
3216
|
+
if (!fs.existsSync(absPath)) {
|
|
3217
|
+
console.error(`File not found: ${absPath}`);
|
|
3218
|
+
process.exit(1);
|
|
3219
|
+
}
|
|
3220
|
+
const raw = JSON.parse(fs.readFileSync(absPath, "utf-8"));
|
|
3221
|
+
const xml = jsonToImportXml(raw);
|
|
3222
|
+
const outPath = outArg ? path.isAbsolute(outArg) ? outArg : path.resolve(outArg) : absPath.replace(/\.json$/i, "-import.xml");
|
|
3223
|
+
fs.writeFileSync(outPath, xml, "utf-8");
|
|
3224
|
+
console.log(`Wrote ${outPath}`);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/integrations/zephyr.ts
|
|
3228
|
+
async function generateZephyrXml(testCase, outputPath, titlePrefix) {
|
|
3229
|
+
const xml = jsonToImportXml(testCase, titlePrefix);
|
|
3230
|
+
ensureDir(dirname2(outputPath));
|
|
3231
|
+
writeFile(outputPath, xml);
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
// src/integrations/index.ts
|
|
3235
|
+
async function fetchIssueContext(key, config, paths) {
|
|
3236
|
+
switch (config.inputSource) {
|
|
3237
|
+
case "jira":
|
|
3238
|
+
return fetchJiraContext(key, config, paths.workingDir);
|
|
3239
|
+
case "linear":
|
|
3240
|
+
return fetchLinearContext(key, config);
|
|
3241
|
+
case "none":
|
|
3242
|
+
return null;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
async function generateQaExport(data, key, config, paths) {
|
|
3246
|
+
const needsZephyr = config.outputTarget === "zephyr" || config.outputTarget === "both";
|
|
3247
|
+
const needsMarkdown = config.outputTarget === "markdown" || config.outputTarget === "both";
|
|
3248
|
+
if (needsZephyr && data.testCase && paths.zephyrXmlFile) {
|
|
3249
|
+
const titlePrefix = config.integrations.zephyr?.titlePrefix;
|
|
3250
|
+
await generateZephyrXml(data.testCase, paths.zephyrXmlFile, titlePrefix);
|
|
3251
|
+
if (paths.zephyrJsonFile) {
|
|
3252
|
+
ensureDir(dirname3(paths.zephyrJsonFile));
|
|
3253
|
+
writeFile(paths.zephyrJsonFile, JSON.stringify(data.testCase, null, 2));
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
if (needsMarkdown && data.markdown && paths.qaFile) {
|
|
3257
|
+
ensureDir(dirname3(paths.qaFile));
|
|
3258
|
+
writeFile(paths.qaFile, data.markdown);
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
// src/commands/scenario.ts
|
|
3263
|
+
function registerScenario(program2) {
|
|
3264
|
+
program2.command("scenario [session]").description("Generate YAML scenario from codegen + transcript").action(async (session) => {
|
|
3265
|
+
const ctx = await resolveCommandContext(program2);
|
|
3266
|
+
const root = ctx.paths.projectRoot;
|
|
3267
|
+
let codegenContent;
|
|
3268
|
+
let transcriptContent;
|
|
3269
|
+
if (ctx.key) {
|
|
3270
|
+
const keyDir = join7(ctx.paths.workingDir, ctx.key);
|
|
3271
|
+
const recordingsDir = join7(keyDir, "recordings");
|
|
3272
|
+
const codegenFile = findFileWithPattern(keyDir, /codegen-.*\.ts$/);
|
|
3273
|
+
const transcriptFile = findFileWithPattern(keyDir, /transcript\.json$/) ?? findFileWithPattern(recordingsDir, /voice-.*\.json$/) ?? findFileWithPattern(keyDir, /voice-.*\.json$/);
|
|
3274
|
+
if (codegenFile)
|
|
3275
|
+
codegenContent = readFile(codegenFile);
|
|
3276
|
+
if (transcriptFile)
|
|
3277
|
+
transcriptContent = readFile(transcriptFile);
|
|
3278
|
+
} else if (session) {
|
|
3279
|
+
const codegenPath = join7(ctx.paths.recordingsDir, `${session}.ts`);
|
|
3280
|
+
const transcriptPath = join7(ctx.paths.transcriptsDir, `${session}-transcript.json`);
|
|
3281
|
+
if (fileExists(codegenPath))
|
|
3282
|
+
codegenContent = readFile(codegenPath);
|
|
3283
|
+
if (fileExists(transcriptPath))
|
|
3284
|
+
transcriptContent = readFile(transcriptPath);
|
|
3285
|
+
}
|
|
3286
|
+
if (!codegenContent) {
|
|
3287
|
+
error("No codegen file found. Run `e2e-ai record` first.");
|
|
3288
|
+
process.exit(1);
|
|
3289
|
+
}
|
|
3290
|
+
let issueContext;
|
|
3291
|
+
if (ctx.key) {
|
|
3292
|
+
issueContext = await fetchIssueContext(ctx.key, ctx.config, ctx.paths);
|
|
3293
|
+
}
|
|
3294
|
+
let narrative;
|
|
3295
|
+
if (transcriptContent) {
|
|
3296
|
+
info("Analyzing transcript with transcript-agent...");
|
|
3297
|
+
const transcriptAgent = loadAgent("transcript-agent", ctx.config);
|
|
3298
|
+
const transcriptResponse = await callLLM({
|
|
3299
|
+
provider: ctx.provider,
|
|
3300
|
+
model: ctx.model ?? transcriptAgent.config.model,
|
|
3301
|
+
systemPrompt: transcriptAgent.systemPrompt,
|
|
3302
|
+
userMessage: JSON.stringify({
|
|
3303
|
+
codegen: codegenContent,
|
|
3304
|
+
transcript: JSON.parse(transcriptContent)
|
|
3305
|
+
}),
|
|
3306
|
+
maxTokens: transcriptAgent.config.maxTokens,
|
|
3307
|
+
temperature: transcriptAgent.config.temperature,
|
|
3308
|
+
jsonMode: true
|
|
3309
|
+
});
|
|
3310
|
+
narrative = JSON.parse(extractJSON(transcriptResponse.content));
|
|
3311
|
+
success(`Narrative generated: ${narrative.sessionSummary}`);
|
|
3312
|
+
} else {
|
|
3313
|
+
warn("No transcript found, generating scenario from codegen only");
|
|
3314
|
+
narrative = {
|
|
3315
|
+
sessionSummary: "Recording session (no voice)",
|
|
3316
|
+
actionIntents: codegenContent.split(`
|
|
3317
|
+
`).filter((l) => l.trim().startsWith("await ")).map((l, i) => ({
|
|
3318
|
+
codegenLine: l.trim(),
|
|
3319
|
+
lineNumber: i + 1,
|
|
3320
|
+
intent: "Recorded action",
|
|
3321
|
+
voiceContext: null
|
|
3322
|
+
}))
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
info("Generating scenario with scenario-agent...");
|
|
3326
|
+
const scenarioAgent = loadAgent("scenario-agent", ctx.config);
|
|
3327
|
+
const scenarioResponse = await callLLM({
|
|
3328
|
+
provider: ctx.provider,
|
|
3329
|
+
model: ctx.model ?? scenarioAgent.config.model,
|
|
3330
|
+
systemPrompt: scenarioAgent.systemPrompt,
|
|
3331
|
+
userMessage: JSON.stringify({
|
|
3332
|
+
narrative,
|
|
3333
|
+
key: ctx.key,
|
|
3334
|
+
issueContext
|
|
3335
|
+
}),
|
|
3336
|
+
maxTokens: scenarioAgent.config.maxTokens,
|
|
3337
|
+
temperature: scenarioAgent.config.temperature
|
|
3338
|
+
});
|
|
3339
|
+
const scenarioYaml = extractYAML(scenarioResponse.content);
|
|
3340
|
+
const scenarioName = ctx.key ?? session ?? "scenario";
|
|
3341
|
+
const scenarioDir = join7(ctx.paths.testsDir, scenarioName);
|
|
3342
|
+
const scenarioPath = join7(scenarioDir, `${scenarioName}.yaml`);
|
|
3343
|
+
ensureDir(scenarioDir);
|
|
3344
|
+
writeFile(scenarioPath, scenarioYaml);
|
|
3345
|
+
success(`Scenario saved: ${scenarioPath}`);
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
// src/commands/generate.ts
|
|
3350
|
+
import { join as join8, basename as basename2 } from "node:path";
|
|
3351
|
+
function registerGenerate(program2) {
|
|
3352
|
+
program2.command("generate [scenario]").description("Generate Playwright test from YAML scenario").action(async (scenarioArg) => {
|
|
3353
|
+
const ctx = await resolveCommandContext(program2);
|
|
3354
|
+
const root = ctx.paths.projectRoot;
|
|
3355
|
+
let scenarioPath;
|
|
3356
|
+
if (scenarioArg && fileExists(join8(root, scenarioArg))) {
|
|
3357
|
+
scenarioPath = join8(root, scenarioArg);
|
|
3358
|
+
} else if (ctx.key) {
|
|
3359
|
+
scenarioPath = join8(ctx.paths.testsDir, ctx.key, `${ctx.key}.yaml`);
|
|
3360
|
+
} else if (scenarioArg) {
|
|
3361
|
+
scenarioPath = join8(ctx.paths.testsDir, scenarioArg, `${scenarioArg}.yaml`);
|
|
3362
|
+
} else {
|
|
3363
|
+
error("Provide a scenario file path or --key");
|
|
3364
|
+
process.exit(1);
|
|
3365
|
+
}
|
|
3366
|
+
if (!fileExists(scenarioPath)) {
|
|
3367
|
+
error(`Scenario not found: ${scenarioPath}`);
|
|
3368
|
+
process.exit(1);
|
|
3369
|
+
}
|
|
3370
|
+
info(`Reading scenario: ${scenarioPath}`);
|
|
3371
|
+
const yaml = await import("./index-4fsmqzap.js");
|
|
3372
|
+
const scenario = yaml.parse(readFile(scenarioPath));
|
|
3373
|
+
info("Generating test with playwright-generator-agent...");
|
|
3374
|
+
const agent = loadAgent("playwright-generator-agent", ctx.config);
|
|
3375
|
+
const response = await callLLM({
|
|
3376
|
+
provider: ctx.provider,
|
|
3377
|
+
model: ctx.model ?? agent.config.model,
|
|
3378
|
+
systemPrompt: agent.systemPrompt,
|
|
3379
|
+
userMessage: JSON.stringify({
|
|
3380
|
+
scenario,
|
|
3381
|
+
projectContext: scenario.projectContext ?? undefined
|
|
3382
|
+
}),
|
|
3383
|
+
maxTokens: agent.config.maxTokens,
|
|
3384
|
+
temperature: agent.config.temperature
|
|
3385
|
+
});
|
|
3386
|
+
let testContent = response.content.trim();
|
|
3387
|
+
if (testContent.startsWith("```")) {
|
|
3388
|
+
testContent = testContent.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3389
|
+
}
|
|
3390
|
+
const testName = scenario.issueKey ?? ctx.key ?? basename2(scenarioPath, ".yaml");
|
|
3391
|
+
const testDir = join8(ctx.paths.testsDir, testName);
|
|
3392
|
+
const testPath = join8(testDir, `${testName}.test.ts`);
|
|
3393
|
+
ensureDir(testDir);
|
|
3394
|
+
writeFile(testPath, testContent);
|
|
3395
|
+
success(`Test generated: ${testPath}`);
|
|
3396
|
+
const needsZephyr = ctx.config.outputTarget === "zephyr" || ctx.config.outputTarget === "both";
|
|
3397
|
+
if (needsZephyr && (scenario.issueKey || ctx.key)) {
|
|
3398
|
+
const issueKey = scenario.issueKey ?? ctx.key;
|
|
3399
|
+
const testCase = {
|
|
3400
|
+
issueKey,
|
|
3401
|
+
issueContext: scenario.issueContext ?? {},
|
|
3402
|
+
title: scenario.name,
|
|
3403
|
+
precondition: scenario.precondition,
|
|
3404
|
+
steps: (scenario.steps ?? []).map((s) => ({
|
|
3405
|
+
stepNumber: s.number,
|
|
3406
|
+
description: s.action,
|
|
3407
|
+
expectedResult: s.expectedResult
|
|
3408
|
+
}))
|
|
3409
|
+
};
|
|
3410
|
+
await generateQaExport({ testCase }, issueKey, ctx.config, ctx.paths);
|
|
3411
|
+
success(`Zephyr export generated`);
|
|
3412
|
+
}
|
|
3413
|
+
});
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
// src/commands/refine.ts
|
|
3417
|
+
import { join as join9 } from "node:path";
|
|
3418
|
+
function registerRefine(program2) {
|
|
3419
|
+
program2.command("refine [test]").description("Refactor/improve test with AI").action(async (testArg) => {
|
|
3420
|
+
const ctx = await resolveCommandContext(program2);
|
|
3421
|
+
const root = ctx.paths.projectRoot;
|
|
3422
|
+
let testPath;
|
|
3423
|
+
if (testArg && fileExists(join9(root, testArg))) {
|
|
3424
|
+
testPath = join9(root, testArg);
|
|
3425
|
+
} else if (ctx.key) {
|
|
3426
|
+
testPath = join9(ctx.paths.testsDir, ctx.key, `${ctx.key}.test.ts`);
|
|
3427
|
+
} else {
|
|
3428
|
+
error("Provide a test file path or --key");
|
|
3429
|
+
process.exit(1);
|
|
3430
|
+
}
|
|
3431
|
+
if (!fileExists(testPath)) {
|
|
3432
|
+
error(`Test file not found: ${testPath}`);
|
|
3433
|
+
process.exit(1);
|
|
3434
|
+
}
|
|
3435
|
+
info(`Refining test: ${testPath}`);
|
|
3436
|
+
const testContent = readFile(testPath);
|
|
3437
|
+
const agent = loadAgent("refactor-agent", ctx.config);
|
|
3438
|
+
const response = await callLLM({
|
|
3439
|
+
provider: ctx.provider,
|
|
3440
|
+
model: ctx.model ?? agent.config.model,
|
|
3441
|
+
systemPrompt: agent.systemPrompt,
|
|
3442
|
+
userMessage: JSON.stringify({
|
|
3443
|
+
testContent,
|
|
3444
|
+
featureMethods: "",
|
|
3445
|
+
utilityPatterns: ""
|
|
3446
|
+
}),
|
|
3447
|
+
maxTokens: agent.config.maxTokens,
|
|
3448
|
+
temperature: agent.config.temperature
|
|
3449
|
+
});
|
|
3450
|
+
let refined = response.content.trim();
|
|
3451
|
+
if (refined.startsWith("```")) {
|
|
3452
|
+
refined = refined.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3453
|
+
}
|
|
3454
|
+
writeFile(testPath, refined);
|
|
3455
|
+
success(`Test refined in-place: ${testPath}`);
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// src/commands/test.ts
|
|
3460
|
+
import { join as join10 } from "node:path";
|
|
3461
|
+
function registerTest(program2) {
|
|
3462
|
+
program2.command("test [test]").description("Run Playwright test with traces").action(async (testArg) => {
|
|
3463
|
+
const ctx = await resolveCommandContext(program2);
|
|
3464
|
+
const root = ctx.paths.projectRoot;
|
|
3465
|
+
let testPath;
|
|
3466
|
+
if (testArg && fileExists(join10(root, testArg))) {
|
|
3467
|
+
testPath = join10(root, testArg);
|
|
3468
|
+
} else if (ctx.key) {
|
|
3469
|
+
testPath = join10(ctx.paths.testsDir, ctx.key, `${ctx.key}.test.ts`);
|
|
3470
|
+
} else {
|
|
3471
|
+
error("Provide a test file path or --key");
|
|
3472
|
+
process.exit(1);
|
|
3473
|
+
}
|
|
3474
|
+
if (!fileExists(testPath)) {
|
|
3475
|
+
error(`Test file not found: ${testPath}`);
|
|
3476
|
+
process.exit(1);
|
|
3477
|
+
}
|
|
3478
|
+
const result = await runPlaywrightTest(testPath, root);
|
|
3479
|
+
if (result.passed) {
|
|
3480
|
+
success(`Test PASSED (exit code ${result.exitCode})`);
|
|
3481
|
+
} else {
|
|
3482
|
+
error(`Test FAILED (exit code ${result.exitCode})`);
|
|
3483
|
+
if (result.errorMessage) {
|
|
3484
|
+
error(result.errorMessage);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
async function runPlaywrightTest(testPath, root) {
|
|
3490
|
+
const projectRoot = root ?? getProjectRoot();
|
|
3491
|
+
const config = await loadConfig();
|
|
3492
|
+
const paths = resolvePaths(config);
|
|
3493
|
+
ensureDir(paths.tracesDir);
|
|
3494
|
+
const args = [
|
|
3495
|
+
"playwright",
|
|
3496
|
+
"test",
|
|
3497
|
+
testPath,
|
|
3498
|
+
"--project",
|
|
3499
|
+
config.playwright.browser
|
|
3500
|
+
];
|
|
3501
|
+
const pkgConfigPath = join10(projectRoot, "packages", "e2e-ai", "playwright.e2e-ai.config.ts");
|
|
3502
|
+
if (fileExists(pkgConfigPath)) {
|
|
3503
|
+
args.push("--config", pkgConfigPath);
|
|
3504
|
+
}
|
|
3505
|
+
info(`Running: npx ${args.join(" ")}`);
|
|
3506
|
+
const result = await spawnAndCapture("npx", args, {
|
|
3507
|
+
cwd: projectRoot,
|
|
3508
|
+
env: { ...process.env }
|
|
3509
|
+
});
|
|
3510
|
+
let errorMessage;
|
|
3511
|
+
const combined = result.stdout + result.stderr;
|
|
3512
|
+
const errorMatch = combined.match(/Error:.*$/m);
|
|
3513
|
+
if (errorMatch) {
|
|
3514
|
+
errorMessage = errorMatch[0];
|
|
3515
|
+
}
|
|
3516
|
+
const traceMatch = combined.match(/trace:\s*(.*\.zip)/);
|
|
3517
|
+
const tracePath = traceMatch?.[1];
|
|
3518
|
+
return {
|
|
3519
|
+
passed: result.exitCode === 0,
|
|
3520
|
+
exitCode: result.exitCode,
|
|
3521
|
+
output: combined,
|
|
3522
|
+
errorMessage,
|
|
3523
|
+
tracePath
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
// src/commands/heal.ts
|
|
3528
|
+
import { join as join11 } from "node:path";
|
|
3529
|
+
var MAX_HEAL_RETRIES = 3;
|
|
3530
|
+
function registerHeal(program2) {
|
|
3531
|
+
program2.command("heal [test]").description("Self-heal a failing test").action(async (testArg) => {
|
|
3532
|
+
const ctx = await resolveCommandContext(program2);
|
|
3533
|
+
const root = ctx.paths.projectRoot;
|
|
3534
|
+
let testPath;
|
|
3535
|
+
if (testArg && fileExists(join11(root, testArg))) {
|
|
3536
|
+
testPath = join11(root, testArg);
|
|
3537
|
+
} else if (ctx.key) {
|
|
3538
|
+
testPath = join11(ctx.paths.testsDir, ctx.key, `${ctx.key}.test.ts`);
|
|
3539
|
+
} else {
|
|
3540
|
+
error("Provide a test file path or --key");
|
|
3541
|
+
process.exit(1);
|
|
3542
|
+
}
|
|
3543
|
+
if (!fileExists(testPath)) {
|
|
3544
|
+
error(`Test file not found: ${testPath}`);
|
|
3545
|
+
process.exit(1);
|
|
3546
|
+
}
|
|
3547
|
+
info("Running test to confirm failure...");
|
|
3548
|
+
let testResult = await runPlaywrightTest(testPath, root);
|
|
3549
|
+
if (testResult.passed) {
|
|
3550
|
+
success("Test is already passing, no healing needed");
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
const agent = loadAgent("self-healing-agent", ctx.config);
|
|
3554
|
+
let previousDiagnosis;
|
|
3555
|
+
for (let attempt = 1;attempt <= MAX_HEAL_RETRIES; attempt++) {
|
|
3556
|
+
info(`Healing attempt ${attempt}/${MAX_HEAL_RETRIES}...`);
|
|
3557
|
+
const testContent = readFile(testPath);
|
|
3558
|
+
const response = await callLLM({
|
|
3559
|
+
provider: ctx.provider,
|
|
3560
|
+
model: ctx.model ?? agent.config.model,
|
|
3561
|
+
systemPrompt: agent.systemPrompt,
|
|
3562
|
+
userMessage: JSON.stringify({
|
|
3563
|
+
testContent,
|
|
3564
|
+
errorOutput: testResult.output,
|
|
3565
|
+
traceData: testResult.tracePath ? `Trace available at: ${testResult.tracePath}` : undefined,
|
|
3566
|
+
attempt,
|
|
3567
|
+
previousDiagnosis
|
|
3568
|
+
}),
|
|
3569
|
+
maxTokens: agent.config.maxTokens,
|
|
3570
|
+
temperature: agent.config.temperature
|
|
3571
|
+
});
|
|
3572
|
+
let healResult;
|
|
3573
|
+
try {
|
|
3574
|
+
let content = response.content.trim();
|
|
3575
|
+
if (content.startsWith("```")) {
|
|
3576
|
+
content = content.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3577
|
+
}
|
|
3578
|
+
healResult = JSON.parse(content);
|
|
3579
|
+
} catch {
|
|
3580
|
+
error("Failed to parse healing response");
|
|
3581
|
+
continue;
|
|
3582
|
+
}
|
|
3583
|
+
const { diagnosis, patchedTest, changes } = healResult;
|
|
3584
|
+
previousDiagnosis = `${diagnosis.failureType}: ${diagnosis.rootCause}`;
|
|
3585
|
+
info(`Diagnosis: ${diagnosis.failureType} - ${diagnosis.rootCause} (${diagnosis.confidence} confidence)`);
|
|
3586
|
+
for (const change of changes ?? []) {
|
|
3587
|
+
verbose(` Line ${change.line}: ${change.reason}`);
|
|
3588
|
+
}
|
|
3589
|
+
writeFile(testPath, patchedTest);
|
|
3590
|
+
info("Patched test written, re-running...");
|
|
3591
|
+
testResult = await runPlaywrightTest(testPath, root);
|
|
3592
|
+
if (testResult.passed) {
|
|
3593
|
+
success(`Test healed after ${attempt} attempt(s)!`);
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3596
|
+
warn(`Attempt ${attempt} did not fix the test`);
|
|
3597
|
+
}
|
|
3598
|
+
error(`Failed to heal test after ${MAX_HEAL_RETRIES} attempts`);
|
|
3599
|
+
process.exit(1);
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
// src/commands/qa.ts
|
|
3604
|
+
import { join as join12 } from "node:path";
|
|
3605
|
+
function registerQa(program2) {
|
|
3606
|
+
program2.command("qa [test]").description("Generate QA documentation").action(async (testArg) => {
|
|
3607
|
+
const ctx = await resolveCommandContext(program2);
|
|
3608
|
+
const root = ctx.paths.projectRoot;
|
|
3609
|
+
let testPath;
|
|
3610
|
+
if (testArg && fileExists(join12(root, testArg))) {
|
|
3611
|
+
testPath = join12(root, testArg);
|
|
3612
|
+
} else if (ctx.key) {
|
|
3613
|
+
testPath = join12(ctx.paths.testsDir, ctx.key, `${ctx.key}.test.ts`);
|
|
3614
|
+
} else {
|
|
3615
|
+
error("Provide a test file path or --key");
|
|
3616
|
+
process.exit(1);
|
|
3617
|
+
}
|
|
3618
|
+
if (!fileExists(testPath)) {
|
|
3619
|
+
error(`Test file not found: ${testPath}`);
|
|
3620
|
+
process.exit(1);
|
|
3621
|
+
}
|
|
3622
|
+
const testContent = readFile(testPath);
|
|
3623
|
+
let scenario;
|
|
3624
|
+
if (ctx.key) {
|
|
3625
|
+
const scenarioPath = join12(ctx.paths.testsDir, ctx.key, `${ctx.key}.yaml`);
|
|
3626
|
+
if (fileExists(scenarioPath)) {
|
|
3627
|
+
const yaml = await import("./index-4fsmqzap.js");
|
|
3628
|
+
scenario = yaml.parse(readFile(scenarioPath));
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
let issueContext;
|
|
3632
|
+
let existingTestCase;
|
|
3633
|
+
if (ctx.key) {
|
|
3634
|
+
issueContext = await fetchIssueContext(ctx.key, ctx.config, ctx.paths);
|
|
3635
|
+
const existingJsonPath = join12(ctx.paths.workingDir, ctx.key, `${ctx.key}-zephyr-test-case.json`);
|
|
3636
|
+
if (fileExists(existingJsonPath)) {
|
|
3637
|
+
existingTestCase = JSON.parse(readFile(existingJsonPath));
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
info("Generating QA documentation...");
|
|
3641
|
+
const agent = loadAgent("qa-testcase-agent", ctx.config);
|
|
3642
|
+
const response = await callLLM({
|
|
3643
|
+
provider: ctx.provider,
|
|
3644
|
+
model: ctx.model ?? agent.config.model,
|
|
3645
|
+
systemPrompt: agent.systemPrompt,
|
|
3646
|
+
userMessage: JSON.stringify({
|
|
3647
|
+
testContent,
|
|
3648
|
+
scenario,
|
|
3649
|
+
existingTestCase,
|
|
3650
|
+
key: ctx.key,
|
|
3651
|
+
issueContext
|
|
3652
|
+
}),
|
|
3653
|
+
maxTokens: agent.config.maxTokens,
|
|
3654
|
+
temperature: agent.config.temperature
|
|
3655
|
+
});
|
|
3656
|
+
let qaResult;
|
|
3657
|
+
try {
|
|
3658
|
+
let content = response.content.trim();
|
|
3659
|
+
if (content.startsWith("```")) {
|
|
3660
|
+
content = content.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3661
|
+
}
|
|
3662
|
+
qaResult = JSON.parse(content);
|
|
3663
|
+
} catch {
|
|
3664
|
+
error("Failed to parse QA agent response");
|
|
3665
|
+
process.exit(1);
|
|
3666
|
+
}
|
|
3667
|
+
await generateQaExport({ markdown: qaResult.markdown, testCase: qaResult.testCase ?? qaResult.zephyrTestCase }, ctx.key ?? "test", ctx.config, ctx.paths);
|
|
3668
|
+
if (qaResult.markdown && ctx.config.outputTarget !== "zephyr") {
|
|
3669
|
+
const qaDir = ctx.paths.qaDir;
|
|
3670
|
+
const testId = ctx.key ?? "test";
|
|
3671
|
+
const qaMdPath = join12(qaDir, `${testId}.md`);
|
|
3672
|
+
ensureDir(qaDir);
|
|
3673
|
+
writeFile(qaMdPath, qaResult.markdown);
|
|
3674
|
+
success(`QA document: ${qaMdPath}`);
|
|
3675
|
+
}
|
|
3676
|
+
success("QA documentation generated");
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
// src/commands/run.ts
|
|
3681
|
+
import { join as join13 } from "node:path";
|
|
3682
|
+
|
|
3683
|
+
// src/pipeline/runPipeline.ts
|
|
3684
|
+
async function runPipeline(steps, ctx, options) {
|
|
3685
|
+
const totalStart = Date.now();
|
|
3686
|
+
const results = [];
|
|
3687
|
+
const skipSet = new Set(options?.skip ?? []);
|
|
3688
|
+
let started = !options?.from;
|
|
3689
|
+
header(`Pipeline: ${ctx.sessionName}`);
|
|
3690
|
+
for (let i = 0;i < steps.length; i++) {
|
|
3691
|
+
const s = steps[i];
|
|
3692
|
+
if (!started) {
|
|
3693
|
+
if (s.name === options?.from) {
|
|
3694
|
+
started = true;
|
|
3695
|
+
} else {
|
|
3696
|
+
verbose(`Skipping ${s.name} (before --from)`);
|
|
3697
|
+
continue;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
if (skipSet.has(s.name)) {
|
|
3701
|
+
verbose(`Skipping ${s.name} (--skip)`);
|
|
3702
|
+
continue;
|
|
3703
|
+
}
|
|
3704
|
+
if (s.canSkip?.(ctx)) {
|
|
3705
|
+
verbose(`Skipping ${s.name} (canSkip returned true)`);
|
|
3706
|
+
continue;
|
|
3707
|
+
}
|
|
3708
|
+
step(i + 1, steps.length, s.name, s.description);
|
|
3709
|
+
const stepStart = Date.now();
|
|
3710
|
+
let result;
|
|
3711
|
+
try {
|
|
3712
|
+
result = await s.execute(ctx);
|
|
3713
|
+
} catch (err) {
|
|
3714
|
+
result = { success: false, output: null, error: err instanceof Error ? err : new Error(String(err)) };
|
|
3715
|
+
}
|
|
3716
|
+
const durationMs = Date.now() - stepStart;
|
|
3717
|
+
results.push({ name: s.name, result, durationMs });
|
|
3718
|
+
if (result.success) {
|
|
3719
|
+
success(`${s.name} completed (${(durationMs / 1000).toFixed(1)}s)`);
|
|
3720
|
+
} else if (result.nonBlocking) {
|
|
3721
|
+
warn(`${s.name} failed (non-blocking): ${result.error?.message ?? "unknown error"}`);
|
|
3722
|
+
} else {
|
|
3723
|
+
error(`${s.name} failed: ${result.error?.message ?? "unknown error"}`);
|
|
3724
|
+
return {
|
|
3725
|
+
success: false,
|
|
3726
|
+
steps: results,
|
|
3727
|
+
totalDurationMs: Date.now() - totalStart
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
ctx.outputs[s.name] = result.output;
|
|
3731
|
+
}
|
|
3732
|
+
const totalDurationMs = Date.now() - totalStart;
|
|
3733
|
+
success(`Pipeline completed in ${(totalDurationMs / 1000).toFixed(1)}s`);
|
|
3734
|
+
return { success: true, steps: results, totalDurationMs };
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
// src/commands/run.ts
|
|
3738
|
+
function registerRun(program2) {
|
|
3739
|
+
program2.command("run [session]").description("Run full pipeline: record -> transcribe -> scenario -> generate -> refine -> test -> heal -> qa").option("--from <step>", "Start from specific step").option("--skip <steps>", "Comma-separated steps to skip").action(async (session, cmdOpts) => {
|
|
3740
|
+
const opts = program2.opts();
|
|
3741
|
+
const config = await loadConfig();
|
|
3742
|
+
const key = opts.key;
|
|
3743
|
+
const paths = resolvePaths(config, key);
|
|
3744
|
+
const sessionName = key ?? session ?? `session-${timestampSuffix()}`;
|
|
3745
|
+
const ctx = {
|
|
3746
|
+
sessionName,
|
|
3747
|
+
key,
|
|
3748
|
+
projectRoot: paths.projectRoot,
|
|
3749
|
+
appConfig: config,
|
|
3750
|
+
paths,
|
|
3751
|
+
config: {
|
|
3752
|
+
provider: opts.provider ?? process.env.AI_PROVIDER ?? config.llm.provider,
|
|
3753
|
+
model: opts.model ?? process.env.AI_MODEL ?? config.llm.model ?? undefined,
|
|
3754
|
+
verbose: opts.verbose ?? false,
|
|
3755
|
+
noVoice: opts.voice === false || !config.voice.enabled,
|
|
3756
|
+
noTrace: opts.trace === false,
|
|
3757
|
+
maxHealRetries: 3
|
|
3758
|
+
},
|
|
3759
|
+
outputs: {},
|
|
3760
|
+
healAttempts: 0
|
|
3761
|
+
};
|
|
3762
|
+
const steps = buildSteps();
|
|
3763
|
+
const skip = cmdOpts?.skip?.split(",") ?? [];
|
|
3764
|
+
const result = await runPipeline(steps, ctx, {
|
|
3765
|
+
from: cmdOpts?.from,
|
|
3766
|
+
skip
|
|
3767
|
+
});
|
|
3768
|
+
if (result.success) {
|
|
3769
|
+
header("Pipeline Summary");
|
|
3770
|
+
for (const s of result.steps) {
|
|
3771
|
+
const icon = s.result.success ? " ✓" : " ✗";
|
|
3772
|
+
info(`${icon} ${s.name} (${(s.durationMs / 1000).toFixed(1)}s)`);
|
|
3773
|
+
}
|
|
3774
|
+
} else {
|
|
3775
|
+
process.exit(1);
|
|
3776
|
+
}
|
|
3777
|
+
});
|
|
3778
|
+
}
|
|
3779
|
+
function buildSteps() {
|
|
3780
|
+
return [
|
|
3781
|
+
{
|
|
3782
|
+
name: "record",
|
|
3783
|
+
description: "Launch codegen + audio recording",
|
|
3784
|
+
async execute(ctx) {
|
|
3785
|
+
const pkgRoot = getPackageRoot();
|
|
3786
|
+
const args = [];
|
|
3787
|
+
if (ctx.key)
|
|
3788
|
+
args.push(ctx.key);
|
|
3789
|
+
if (ctx.config.noVoice)
|
|
3790
|
+
args.push("--no-voice");
|
|
3791
|
+
if (ctx.config.noTrace)
|
|
3792
|
+
args.push("--no-trace");
|
|
3793
|
+
const scriptPath = join13(pkgRoot, "scripts", "codegen-env.mjs");
|
|
3794
|
+
const child = spawnInteractive("node", [scriptPath, ...args], {
|
|
3795
|
+
cwd: ctx.projectRoot,
|
|
3796
|
+
env: {
|
|
3797
|
+
...process.env,
|
|
3798
|
+
E2E_AI_PROJECT_ROOT: ctx.projectRoot,
|
|
3799
|
+
E2E_AI_WORKING_DIR: ctx.appConfig.paths.workingDir,
|
|
3800
|
+
E2E_AI_KEY: ctx.key ?? ""
|
|
3801
|
+
}
|
|
3802
|
+
});
|
|
3803
|
+
const exitCode = await waitForProcess(child);
|
|
3804
|
+
if (exitCode !== 0)
|
|
3805
|
+
return { success: false, output: null, error: new Error(`Recording exited with code ${exitCode}`) };
|
|
3806
|
+
if (ctx.key) {
|
|
3807
|
+
ctx.codegenPath = findFileWithPattern(join13(ctx.paths.workingDir, ctx.key), /codegen-.*\.ts$/);
|
|
3808
|
+
ctx.audioPath = findFileWithPattern(join13(ctx.paths.workingDir, ctx.key, "recordings"), /\.wav$/);
|
|
3809
|
+
}
|
|
3810
|
+
return { success: true, output: { codegenPath: ctx.codegenPath, audioPath: ctx.audioPath } };
|
|
3811
|
+
}
|
|
3812
|
+
},
|
|
3813
|
+
{
|
|
3814
|
+
name: "transcribe",
|
|
3815
|
+
description: "Transcribe audio via Whisper",
|
|
3816
|
+
canSkip(ctx) {
|
|
3817
|
+
return ctx.config.noVoice || !ctx.audioPath;
|
|
3818
|
+
},
|
|
3819
|
+
async execute(ctx) {
|
|
3820
|
+
if (!ctx.audioPath)
|
|
3821
|
+
return { success: true, output: null, nonBlocking: true };
|
|
3822
|
+
const pkgRoot = getPackageRoot();
|
|
3823
|
+
const transcriber = await import(join13(pkgRoot, "scripts", "voice", "transcriber.mjs"));
|
|
3824
|
+
const segments = await transcriber.transcribe(ctx.audioPath);
|
|
3825
|
+
if (!segments?.length)
|
|
3826
|
+
return { success: true, output: { segments: [] }, nonBlocking: true };
|
|
3827
|
+
const outputDir = ctx.key ? join13(ctx.paths.workingDir, ctx.key) : ctx.paths.transcriptsDir;
|
|
3828
|
+
const jsonPath = join13(outputDir, `${ctx.sessionName}-transcript.json`);
|
|
3829
|
+
writeFile(jsonPath, JSON.stringify(segments, null, 2));
|
|
3830
|
+
ctx.transcriptPath = jsonPath;
|
|
3831
|
+
if (ctx.codegenPath) {
|
|
3832
|
+
const merger = await import(join13(pkgRoot, "scripts", "voice", "merger.mjs"));
|
|
3833
|
+
const content = readFile(ctx.codegenPath);
|
|
3834
|
+
const merged = merger.merge(content, segments, segments[segments.length - 1].end);
|
|
3835
|
+
writeFile(ctx.codegenPath, merged);
|
|
3836
|
+
}
|
|
3837
|
+
return { success: true, output: { segments, transcriptPath: ctx.transcriptPath } };
|
|
3838
|
+
}
|
|
3839
|
+
},
|
|
3840
|
+
{
|
|
3841
|
+
name: "scenario",
|
|
3842
|
+
description: "Generate YAML scenario from codegen + transcript",
|
|
3843
|
+
async execute(ctx) {
|
|
3844
|
+
if (!ctx.codegenPath)
|
|
3845
|
+
return { success: false, output: null, error: new Error("No codegen file") };
|
|
3846
|
+
const codegenContent = readFile(ctx.codegenPath);
|
|
3847
|
+
let transcriptContent;
|
|
3848
|
+
if (ctx.transcriptPath && fileExists(ctx.transcriptPath)) {
|
|
3849
|
+
transcriptContent = JSON.parse(readFile(ctx.transcriptPath));
|
|
3850
|
+
}
|
|
3851
|
+
let narrative;
|
|
3852
|
+
if (transcriptContent) {
|
|
3853
|
+
const agent2 = loadAgent("transcript-agent", ctx.appConfig);
|
|
3854
|
+
const resp2 = await callLLM({
|
|
3855
|
+
provider: ctx.config.provider,
|
|
3856
|
+
model: ctx.config.model ?? agent2.config.model,
|
|
3857
|
+
systemPrompt: agent2.systemPrompt,
|
|
3858
|
+
userMessage: JSON.stringify({ codegen: codegenContent, transcript: transcriptContent }),
|
|
3859
|
+
maxTokens: agent2.config.maxTokens,
|
|
3860
|
+
temperature: agent2.config.temperature
|
|
3861
|
+
});
|
|
3862
|
+
narrative = JSON.parse(resp2.content);
|
|
3863
|
+
} else {
|
|
3864
|
+
narrative = {
|
|
3865
|
+
sessionSummary: "Recording session (no voice)",
|
|
3866
|
+
actionIntents: codegenContent.split(`
|
|
3867
|
+
`).filter((l) => l.trim().startsWith("await ")).map((l, i) => ({ codegenLine: l.trim(), lineNumber: i + 1, intent: "Recorded action", voiceContext: null }))
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
let issueContext;
|
|
3871
|
+
if (ctx.key) {
|
|
3872
|
+
issueContext = await fetchIssueContext(ctx.key, ctx.appConfig, ctx.paths);
|
|
3873
|
+
}
|
|
3874
|
+
const agent = loadAgent("scenario-agent", ctx.appConfig);
|
|
3875
|
+
const resp = await callLLM({
|
|
3876
|
+
provider: ctx.config.provider,
|
|
3877
|
+
model: ctx.config.model ?? agent.config.model,
|
|
3878
|
+
systemPrompt: agent.systemPrompt,
|
|
3879
|
+
userMessage: JSON.stringify({ narrative, key: ctx.key, issueContext }),
|
|
3880
|
+
maxTokens: agent.config.maxTokens,
|
|
3881
|
+
temperature: agent.config.temperature
|
|
3882
|
+
});
|
|
3883
|
+
const scenarioDir = join13(ctx.paths.testsDir, ctx.sessionName);
|
|
3884
|
+
ensureDir(scenarioDir);
|
|
3885
|
+
const scenarioPath = join13(scenarioDir, `${ctx.sessionName}.yaml`);
|
|
3886
|
+
writeFile(scenarioPath, resp.content.trim());
|
|
3887
|
+
ctx.scenarioPath = scenarioPath;
|
|
3888
|
+
return { success: true, output: { scenarioPath } };
|
|
3889
|
+
}
|
|
3890
|
+
},
|
|
3891
|
+
{
|
|
3892
|
+
name: "generate",
|
|
3893
|
+
description: "Generate Playwright test from scenario",
|
|
3894
|
+
async execute(ctx) {
|
|
3895
|
+
if (!ctx.scenarioPath)
|
|
3896
|
+
return { success: false, output: null, error: new Error("No scenario file") };
|
|
3897
|
+
const yaml = await import("./index-4fsmqzap.js");
|
|
3898
|
+
const scenario = yaml.parse(readFile(ctx.scenarioPath));
|
|
3899
|
+
const agent = loadAgent("playwright-generator-agent", ctx.appConfig);
|
|
3900
|
+
const resp = await callLLM({
|
|
3901
|
+
provider: ctx.config.provider,
|
|
3902
|
+
model: ctx.config.model ?? agent.config.model,
|
|
3903
|
+
systemPrompt: agent.systemPrompt,
|
|
3904
|
+
userMessage: JSON.stringify({ scenario }),
|
|
3905
|
+
maxTokens: agent.config.maxTokens,
|
|
3906
|
+
temperature: agent.config.temperature
|
|
3907
|
+
});
|
|
3908
|
+
let testContent = resp.content.trim();
|
|
3909
|
+
if (testContent.startsWith("```"))
|
|
3910
|
+
testContent = testContent.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3911
|
+
const testName = ctx.key ?? ctx.sessionName;
|
|
3912
|
+
const testDir = join13(ctx.paths.testsDir, testName);
|
|
3913
|
+
ctx.testPath = join13(testDir, `${testName}.test.ts`);
|
|
3914
|
+
ensureDir(testDir);
|
|
3915
|
+
writeFile(ctx.testPath, testContent);
|
|
3916
|
+
return { success: true, output: { testPath: ctx.testPath } };
|
|
3917
|
+
}
|
|
3918
|
+
},
|
|
3919
|
+
{
|
|
3920
|
+
name: "refine",
|
|
3921
|
+
description: "Refactor test with AI",
|
|
3922
|
+
async execute(ctx) {
|
|
3923
|
+
if (!ctx.testPath)
|
|
3924
|
+
return { success: false, output: null, error: new Error("No test file") };
|
|
3925
|
+
const testContent = readFile(ctx.testPath);
|
|
3926
|
+
const agent = loadAgent("refactor-agent", ctx.appConfig);
|
|
3927
|
+
const resp = await callLLM({
|
|
3928
|
+
provider: ctx.config.provider,
|
|
3929
|
+
model: ctx.config.model ?? agent.config.model,
|
|
3930
|
+
systemPrompt: agent.systemPrompt,
|
|
3931
|
+
userMessage: JSON.stringify({
|
|
3932
|
+
testContent,
|
|
3933
|
+
featureMethods: "",
|
|
3934
|
+
utilityPatterns: ""
|
|
3935
|
+
}),
|
|
3936
|
+
maxTokens: agent.config.maxTokens,
|
|
3937
|
+
temperature: agent.config.temperature
|
|
3938
|
+
});
|
|
3939
|
+
let refined = resp.content.trim();
|
|
3940
|
+
if (refined.startsWith("```"))
|
|
3941
|
+
refined = refined.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3942
|
+
writeFile(ctx.testPath, refined);
|
|
3943
|
+
return { success: true, output: { testPath: ctx.testPath } };
|
|
3944
|
+
}
|
|
3945
|
+
},
|
|
3946
|
+
{
|
|
3947
|
+
name: "test",
|
|
3948
|
+
description: "Run Playwright test",
|
|
3949
|
+
async execute(ctx) {
|
|
3950
|
+
if (!ctx.testPath)
|
|
3951
|
+
return { success: false, output: null, error: new Error("No test file") };
|
|
3952
|
+
const result = await runPlaywrightTest(ctx.testPath, ctx.projectRoot);
|
|
3953
|
+
ctx.testResult = result;
|
|
3954
|
+
ctx.tracePath = result.tracePath;
|
|
3955
|
+
return { success: true, output: result };
|
|
3956
|
+
}
|
|
3957
|
+
},
|
|
3958
|
+
{
|
|
3959
|
+
name: "heal",
|
|
3960
|
+
description: "Self-heal failing test",
|
|
3961
|
+
canSkip(ctx) {
|
|
3962
|
+
return ctx.testResult?.passed === true;
|
|
3963
|
+
},
|
|
3964
|
+
async execute(ctx) {
|
|
3965
|
+
if (!ctx.testPath || !ctx.testResult)
|
|
3966
|
+
return { success: true, output: null };
|
|
3967
|
+
const agent = loadAgent("self-healing-agent", ctx.appConfig);
|
|
3968
|
+
let previousDiagnosis;
|
|
3969
|
+
for (let attempt = 1;attempt <= ctx.config.maxHealRetries; attempt++) {
|
|
3970
|
+
ctx.healAttempts = attempt;
|
|
3971
|
+
info(` Heal attempt ${attempt}/${ctx.config.maxHealRetries}`);
|
|
3972
|
+
const testContent = readFile(ctx.testPath);
|
|
3973
|
+
const resp = await callLLM({
|
|
3974
|
+
provider: ctx.config.provider,
|
|
3975
|
+
model: ctx.config.model ?? agent.config.model,
|
|
3976
|
+
systemPrompt: agent.systemPrompt,
|
|
3977
|
+
userMessage: JSON.stringify({
|
|
3978
|
+
testContent,
|
|
3979
|
+
errorOutput: ctx.testResult.output,
|
|
3980
|
+
attempt,
|
|
3981
|
+
previousDiagnosis
|
|
3982
|
+
}),
|
|
3983
|
+
maxTokens: agent.config.maxTokens,
|
|
3984
|
+
temperature: agent.config.temperature
|
|
3985
|
+
});
|
|
3986
|
+
let content = resp.content.trim();
|
|
3987
|
+
if (content.startsWith("```"))
|
|
3988
|
+
content = content.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
3989
|
+
const healResult = JSON.parse(content);
|
|
3990
|
+
previousDiagnosis = `${healResult.diagnosis.failureType}: ${healResult.diagnosis.rootCause}`;
|
|
3991
|
+
writeFile(ctx.testPath, healResult.patchedTest);
|
|
3992
|
+
const testResult = await runPlaywrightTest(ctx.testPath, ctx.projectRoot);
|
|
3993
|
+
ctx.testResult = testResult;
|
|
3994
|
+
if (testResult.passed) {
|
|
3995
|
+
return { success: true, output: { healed: true, attempts: attempt } };
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
return { success: false, output: null, error: new Error(`Failed to heal after ${ctx.config.maxHealRetries} attempts`) };
|
|
3999
|
+
}
|
|
4000
|
+
},
|
|
4001
|
+
{
|
|
4002
|
+
name: "qa",
|
|
4003
|
+
description: "Generate QA documentation",
|
|
4004
|
+
async execute(ctx) {
|
|
4005
|
+
if (!ctx.testPath)
|
|
4006
|
+
return { success: false, output: null, error: new Error("No test file") };
|
|
4007
|
+
const testContent = readFile(ctx.testPath);
|
|
4008
|
+
let scenario;
|
|
4009
|
+
if (ctx.scenarioPath && fileExists(ctx.scenarioPath)) {
|
|
4010
|
+
const yaml = await import("./index-4fsmqzap.js");
|
|
4011
|
+
scenario = yaml.parse(readFile(ctx.scenarioPath));
|
|
4012
|
+
}
|
|
4013
|
+
let issueContext;
|
|
4014
|
+
if (ctx.key) {
|
|
4015
|
+
issueContext = await fetchIssueContext(ctx.key, ctx.appConfig, ctx.paths);
|
|
4016
|
+
}
|
|
4017
|
+
const agent = loadAgent("qa-testcase-agent", ctx.appConfig);
|
|
4018
|
+
const resp = await callLLM({
|
|
4019
|
+
provider: ctx.config.provider,
|
|
4020
|
+
model: ctx.config.model ?? agent.config.model,
|
|
4021
|
+
systemPrompt: agent.systemPrompt,
|
|
4022
|
+
userMessage: JSON.stringify({
|
|
4023
|
+
testContent,
|
|
4024
|
+
scenario,
|
|
4025
|
+
key: ctx.key,
|
|
4026
|
+
issueContext
|
|
4027
|
+
}),
|
|
4028
|
+
maxTokens: agent.config.maxTokens,
|
|
4029
|
+
temperature: agent.config.temperature
|
|
4030
|
+
});
|
|
4031
|
+
let content = resp.content.trim();
|
|
4032
|
+
if (content.startsWith("```"))
|
|
4033
|
+
content = content.replace(/^```\w*\n/, "").replace(/\n```$/, "");
|
|
4034
|
+
const qaResult = JSON.parse(content);
|
|
4035
|
+
await generateQaExport({ markdown: qaResult.markdown, testCase: qaResult.testCase ?? qaResult.zephyrTestCase }, ctx.key ?? ctx.sessionName, ctx.appConfig, ctx.paths);
|
|
4036
|
+
return { success: true, output: qaResult, nonBlocking: true };
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
];
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
// src/commands/init.ts
|
|
4043
|
+
import { join as join14 } from "node:path";
|
|
4044
|
+
import { createInterface } from "node:readline";
|
|
4045
|
+
function registerInit(program2) {
|
|
4046
|
+
program2.command("init").description("Initialize e2e-ai configuration for your project").option("--non-interactive", "Skip interactive prompts, use defaults").action(async (cmdOpts) => {
|
|
4047
|
+
const projectRoot = getProjectRoot();
|
|
4048
|
+
header("e2e-ai init");
|
|
4049
|
+
const answers = cmdOpts?.nonInteractive ? getDefaultAnswers() : await askConfigQuestions();
|
|
4050
|
+
const config = buildConfigFromAnswers(answers);
|
|
4051
|
+
const configPath = join14(projectRoot, "e2e-ai.config.ts");
|
|
4052
|
+
if (fileExists(configPath)) {
|
|
4053
|
+
warn(`Config already exists: ${configPath}`);
|
|
4054
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4055
|
+
const overwrite = await new Promise((resolve) => {
|
|
4056
|
+
rl.question("Overwrite? (y/N) ", resolve);
|
|
4057
|
+
});
|
|
4058
|
+
rl.close();
|
|
4059
|
+
if (overwrite.toLowerCase() !== "y") {
|
|
4060
|
+
info("Skipping config generation");
|
|
4061
|
+
} else {
|
|
4062
|
+
writeFile(configPath, generateConfigFile(config));
|
|
4063
|
+
success(`Config written: ${configPath}`);
|
|
4064
|
+
}
|
|
4065
|
+
} else {
|
|
4066
|
+
writeFile(configPath, generateConfigFile(config));
|
|
4067
|
+
success(`Config written: ${configPath}`);
|
|
4068
|
+
}
|
|
4069
|
+
if (!cmdOpts?.nonInteractive) {
|
|
4070
|
+
const opts = program2.opts();
|
|
4071
|
+
const provider = opts.provider ?? process.env.AI_PROVIDER ?? answers.provider ?? "openai";
|
|
4072
|
+
const model = opts.model ?? process.env.AI_MODEL;
|
|
4073
|
+
info(`
|
|
4074
|
+
Scanning codebase for test patterns...`);
|
|
4075
|
+
const scan = await scanCodebase(projectRoot);
|
|
4076
|
+
if (scan.testFiles.length === 0 && scan.configFiles.length === 0) {
|
|
4077
|
+
warn("No test files found. Skipping context generation.");
|
|
4078
|
+
info("You can create e2e-ai.context.md manually later.");
|
|
4079
|
+
} else {
|
|
4080
|
+
info(`Found ${scan.testFiles.length} test files, ${scan.configFiles.length} config files`);
|
|
4081
|
+
const contextContent = await runInitConversation(scan, provider, model);
|
|
4082
|
+
if (contextContent) {
|
|
4083
|
+
const contextPath = join14(projectRoot, config.contextFile ?? "e2e-ai.context.md");
|
|
4084
|
+
writeFile(contextPath, contextContent);
|
|
4085
|
+
success(`Context file written: ${contextPath}`);
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
success(`
|
|
4090
|
+
Initialization complete!`);
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
function getDefaultAnswers() {
|
|
4094
|
+
return {
|
|
4095
|
+
inputSource: "none",
|
|
4096
|
+
outputTarget: "markdown",
|
|
4097
|
+
voiceEnabled: true,
|
|
4098
|
+
provider: "openai",
|
|
4099
|
+
baseUrl: process.env.BASE_URL ?? ""
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
async function askConfigQuestions() {
|
|
4103
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4104
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
4105
|
+
info(`Configure your e2e-ai setup:
|
|
4106
|
+
`);
|
|
4107
|
+
const inputSource = await ask("Issue tracker? (jira/linear/none) [none]: ") || "none";
|
|
4108
|
+
const outputTarget = await ask("QA documentation format? (zephyr/markdown/both) [markdown]: ") || "markdown";
|
|
4109
|
+
const voiceInput = await ask("Enable voice recording? (yes/no) [yes]: ") || "yes";
|
|
4110
|
+
const provider = await ask("LLM provider? (openai/anthropic) [openai]: ") || "openai";
|
|
4111
|
+
const baseUrl = await ask(`Base URL? [${process.env.BASE_URL ?? ""}]: `) || (process.env.BASE_URL ?? "");
|
|
4112
|
+
rl.close();
|
|
4113
|
+
return {
|
|
4114
|
+
inputSource,
|
|
4115
|
+
outputTarget,
|
|
4116
|
+
voiceEnabled: voiceInput.toLowerCase() !== "no",
|
|
4117
|
+
provider,
|
|
4118
|
+
baseUrl
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
function buildConfigFromAnswers(answers) {
|
|
4122
|
+
const config = {
|
|
4123
|
+
inputSource: answers.inputSource,
|
|
4124
|
+
outputTarget: answers.outputTarget,
|
|
4125
|
+
voice: { enabled: answers.voiceEnabled },
|
|
4126
|
+
llm: { provider: answers.provider },
|
|
4127
|
+
contextFile: "e2e-ai.context.md"
|
|
4128
|
+
};
|
|
4129
|
+
if (answers.baseUrl) {
|
|
4130
|
+
config.baseUrl = answers.baseUrl;
|
|
4131
|
+
}
|
|
4132
|
+
if (answers.outputTarget === "zephyr" || answers.outputTarget === "both") {
|
|
4133
|
+
config.integrations = {
|
|
4134
|
+
zephyr: { titlePrefix: "UI Automation" }
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
return config;
|
|
4138
|
+
}
|
|
4139
|
+
function generateConfigFile(config) {
|
|
4140
|
+
const lines = [
|
|
4141
|
+
`import { defineConfig } from 'e2e-ai/config';`,
|
|
4142
|
+
"",
|
|
4143
|
+
"export default defineConfig({"
|
|
4144
|
+
];
|
|
4145
|
+
for (const [key, value] of Object.entries(config)) {
|
|
4146
|
+
if (typeof value === "object" && value !== null) {
|
|
4147
|
+
lines.push(` ${key}: ${JSON.stringify(value)},`);
|
|
4148
|
+
} else if (typeof value === "string") {
|
|
4149
|
+
lines.push(` ${key}: '${value}',`);
|
|
4150
|
+
} else {
|
|
4151
|
+
lines.push(` ${key}: ${value},`);
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
lines.push("});");
|
|
4155
|
+
lines.push("");
|
|
4156
|
+
return lines.join(`
|
|
4157
|
+
`);
|
|
4158
|
+
}
|
|
4159
|
+
async function scanCodebase(root) {
|
|
4160
|
+
const { readdirSync: readdirSync2, existsSync: existsSync3, readFileSync: readFileSync3, statSync } = await import("node:fs");
|
|
4161
|
+
const { join: join15, relative } = await import("node:path");
|
|
4162
|
+
const scan = {
|
|
4163
|
+
testFiles: [],
|
|
4164
|
+
configFiles: [],
|
|
4165
|
+
fixtureFiles: [],
|
|
4166
|
+
featureFiles: [],
|
|
4167
|
+
tsconfigPaths: {},
|
|
4168
|
+
playwrightConfig: null,
|
|
4169
|
+
sampleTestContent: null
|
|
4170
|
+
};
|
|
4171
|
+
function walk(dir, depth = 0) {
|
|
4172
|
+
if (depth > 5)
|
|
4173
|
+
return [];
|
|
4174
|
+
const files = [];
|
|
4175
|
+
try {
|
|
4176
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
4177
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
|
|
4178
|
+
continue;
|
|
4179
|
+
const full = join15(dir, entry.name);
|
|
4180
|
+
if (entry.isDirectory()) {
|
|
4181
|
+
files.push(...walk(full, depth + 1));
|
|
4182
|
+
} else {
|
|
4183
|
+
files.push(full);
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
} catch {}
|
|
4187
|
+
return files;
|
|
4188
|
+
}
|
|
4189
|
+
const allFiles = walk(root);
|
|
4190
|
+
for (const file of allFiles) {
|
|
4191
|
+
const rel = relative(root, file);
|
|
4192
|
+
if (rel.endsWith(".test.ts") || rel.endsWith(".spec.ts")) {
|
|
4193
|
+
scan.testFiles.push(rel);
|
|
4194
|
+
if (!scan.sampleTestContent && scan.testFiles.length <= 3) {
|
|
4195
|
+
try {
|
|
4196
|
+
scan.sampleTestContent = readFileSync3(file, "utf-8").slice(0, 3000);
|
|
4197
|
+
} catch {}
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
if (rel.endsWith(".feature.ts"))
|
|
4201
|
+
scan.featureFiles.push(rel);
|
|
4202
|
+
if (rel.includes("fixture") && rel.endsWith(".ts"))
|
|
4203
|
+
scan.fixtureFiles.push(rel);
|
|
4204
|
+
if (rel === "playwright.config.ts" || rel === "playwright.config.js")
|
|
4205
|
+
scan.playwrightConfig = rel;
|
|
4206
|
+
if (rel === "tsconfig.json" || rel.endsWith("/tsconfig.json")) {
|
|
4207
|
+
try {
|
|
4208
|
+
const tsconfig = JSON.parse(readFileSync3(file, "utf-8"));
|
|
4209
|
+
if (tsconfig.compilerOptions?.paths) {
|
|
4210
|
+
scan.tsconfigPaths = { ...scan.tsconfigPaths, ...tsconfig.compilerOptions.paths };
|
|
4211
|
+
}
|
|
4212
|
+
} catch {}
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
for (const name of ["playwright.config.ts", "vitest.config.ts", "jest.config.ts", "tsconfig.json", "package.json"]) {
|
|
4216
|
+
if (existsSync3(join15(root, name)))
|
|
4217
|
+
scan.configFiles.push(name);
|
|
4218
|
+
}
|
|
4219
|
+
return scan;
|
|
4220
|
+
}
|
|
4221
|
+
async function runInitConversation(scan, provider, model) {
|
|
4222
|
+
const agent = loadAgent("init-agent");
|
|
4223
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4224
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
4225
|
+
const messages = [];
|
|
4226
|
+
const scanMessage = JSON.stringify({
|
|
4227
|
+
testFiles: scan.testFiles.slice(0, 20),
|
|
4228
|
+
configFiles: scan.configFiles,
|
|
4229
|
+
fixtureFiles: scan.fixtureFiles.slice(0, 10),
|
|
4230
|
+
featureFiles: scan.featureFiles.slice(0, 20),
|
|
4231
|
+
tsconfigPaths: scan.tsconfigPaths,
|
|
4232
|
+
playwrightConfig: scan.playwrightConfig,
|
|
4233
|
+
sampleTestContent: scan.sampleTestContent
|
|
4234
|
+
}, null, 2);
|
|
4235
|
+
messages.push({ role: "user", content: `Here are the scan results from the project:
|
|
4236
|
+
|
|
4237
|
+
${scanMessage}` });
|
|
4238
|
+
const MAX_TURNS = 5;
|
|
4239
|
+
for (let turn = 0;turn < MAX_TURNS; turn++) {
|
|
4240
|
+
const userContent = messages.filter((m) => m.role === "user").map((m) => m.content).join(`
|
|
4241
|
+
|
|
4242
|
+
---
|
|
4243
|
+
|
|
4244
|
+
`);
|
|
4245
|
+
const resp = await callLLM({
|
|
4246
|
+
provider,
|
|
4247
|
+
model: model ?? agent.config.model,
|
|
4248
|
+
systemPrompt: agent.systemPrompt,
|
|
4249
|
+
userMessage: userContent,
|
|
4250
|
+
maxTokens: agent.config.maxTokens,
|
|
4251
|
+
temperature: agent.config.temperature
|
|
4252
|
+
});
|
|
4253
|
+
const assistantContent = resp.content.trim();
|
|
4254
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
4255
|
+
const contextMatch = assistantContent.match(/<context>([\s\S]*?)<\/context>/);
|
|
4256
|
+
if (contextMatch) {
|
|
4257
|
+
rl.close();
|
|
4258
|
+
return contextMatch[1].trim();
|
|
4259
|
+
}
|
|
4260
|
+
console.log(`
|
|
4261
|
+
` + assistantContent + `
|
|
4262
|
+
`);
|
|
4263
|
+
const answer = await ask('Your answer (or "done" to let the agent finalize): ');
|
|
4264
|
+
if (answer.toLowerCase() === "done") {
|
|
4265
|
+
messages.push({ role: "user", content: "Please produce the final context document now based on what you know. Wrap it in <context> tags." });
|
|
4266
|
+
} else {
|
|
4267
|
+
messages.push({ role: "user", content: answer });
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
rl.close();
|
|
4271
|
+
warn("Max conversation turns reached. Context may be incomplete.");
|
|
4272
|
+
return null;
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
// src/cli.ts
|
|
4276
|
+
var program2 = new Command;
|
|
4277
|
+
program2.name("e2e-ai").description("AI-powered E2E test automation pipeline").version("1.0.0").option("-k, --key <KEY>", "Issue key (e.g., PROJ-101, LIN-42)").option("--provider <provider>", "LLM provider (openai|anthropic)", process.env.AI_PROVIDER).option("--model <model>", "LLM model override", process.env.AI_MODEL).option("-v, --verbose", "Verbose output").option("--no-voice", "Disable voice recording").option("--no-trace", "Disable trace replay").hook("preAction", () => {
|
|
4278
|
+
if (program2.opts().verbose) {
|
|
4279
|
+
setVerbose(true);
|
|
4280
|
+
}
|
|
4281
|
+
});
|
|
4282
|
+
registerInit(program2);
|
|
4283
|
+
registerRecord(program2);
|
|
4284
|
+
registerTranscribe(program2);
|
|
4285
|
+
registerScenario(program2);
|
|
4286
|
+
registerGenerate(program2);
|
|
4287
|
+
registerRefine(program2);
|
|
4288
|
+
registerTest(program2);
|
|
4289
|
+
registerHeal(program2);
|
|
4290
|
+
registerQa(program2);
|
|
4291
|
+
registerRun(program2);
|
|
4292
|
+
program2.parse();
|