@automatify-au/cli 0.1.5 → 0.1.6
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 +25 -1
- package/dist/automatify.cjs +190 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,16 @@ Minimum:
|
|
|
76
76
|
- `TESTOPS_FORGE_ENDPOINT` for commands that call Forge contracts (`doctor`, `ingest`, `run`, `sync`)
|
|
77
77
|
- `TESTOPS_FORGE_AUTH_TOKEN` from the one-time token shown in Forge `Operations -> CLI access`
|
|
78
78
|
|
|
79
|
+
Local config helper:
|
|
80
|
+
```bash
|
|
81
|
+
automatify testops config set baseUrl https://automatify-com-au.atlassian.net
|
|
82
|
+
automatify testops config set projectKey DEV
|
|
83
|
+
automatify testops config set forgeEndpoint "<webtrigger-url>"
|
|
84
|
+
printf "%s" "$TESTOPS_FORGE_AUTH_TOKEN" | automatify testops config set forgeAuthToken --stdin
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`forgeAuthToken` local storage currently uses macOS Keychain. On Windows, Linux, and CI, keep the token in `TESTOPS_FORGE_AUTH_TOKEN` instead. `.testops-cli.json` should store non-secret values only.
|
|
88
|
+
|
|
79
89
|
Optional:
|
|
80
90
|
- `TESTOPS_FORGE_TIMEOUT_MS`
|
|
81
91
|
- `TESTOPS_FORGE_MAX_RETRIES`
|
|
@@ -91,6 +101,7 @@ All TestOps commands are invoked as `automatify testops <command>`:
|
|
|
91
101
|
- `bdd`
|
|
92
102
|
- `automatify testops bdd scenarios show --id <SCENARIO_ID>`
|
|
93
103
|
- `automatify testops bdd scenarios show --id <SCENARIO_ID> --format feature`
|
|
104
|
+
- `automatify testops bdd scenarios show --id <SCENARIO_ID|SC-4> --feature`
|
|
94
105
|
- `automatify testops bdd scenarios export --id <SCENARIO_ID|SC-4> --output-dir ./scenario-export`
|
|
95
106
|
- `automatify testops bdd scenarios export --all --output-dir ./scenario-export`
|
|
96
107
|
- `automatify testops bdd scenarios import --file ./scenario-export/SC-4.feature`
|
|
@@ -171,6 +182,19 @@ export JIRA_BASE_URL="https://automatify-com-au.atlassian.net"
|
|
|
171
182
|
export JIRA_PROJECT_KEY="DEV"
|
|
172
183
|
```
|
|
173
184
|
|
|
185
|
+
Or store the non-secret values in `.testops-cli.json` and, on macOS, the token in Keychain:
|
|
186
|
+
```bash
|
|
187
|
+
automatify testops config set baseUrl https://automatify-com-au.atlassian.net
|
|
188
|
+
automatify testops config set projectKey DEV
|
|
189
|
+
automatify testops config set forgeEndpoint "<webtrigger-url>"
|
|
190
|
+
printf "%s" "$TESTOPS_FORGE_AUTH_TOKEN" | automatify testops config set forgeAuthToken --stdin
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Windows, Linux, and CI should keep the token in environment variables:
|
|
194
|
+
```bash
|
|
195
|
+
export TESTOPS_FORGE_AUTH_TOKEN="<one-time-token-from-forge-ui>"
|
|
196
|
+
```
|
|
197
|
+
|
|
174
198
|
Transport notes:
|
|
175
199
|
- Bearer auth is required for operator/CI use. Forge stores only the token hash plus metadata.
|
|
176
200
|
- Tokens are project-scoped, shown once at creation time, and can be revoked from the Forge UI.
|
|
@@ -317,7 +341,7 @@ automatify testops ingest feature --project-key DEV --file ./artifacts/features-
|
|
|
317
341
|
```
|
|
318
342
|
|
|
319
343
|
Feature export notes:
|
|
320
|
-
- `bdd scenarios show --format feature`
|
|
344
|
+
- `bdd scenarios show --format feature` and `bdd scenarios show --feature` render one scenario in feature-style text.
|
|
321
345
|
- `bdd scenarios export` writes a single-scenario `SC-*.feature` file with a reserved `@testops-scenario-SC-*` tag.
|
|
322
346
|
- Jira issue links are exported as normal Gherkin tags like `@DEV-2`; on import they are written back to `linkedIssueKeys`.
|
|
323
347
|
- current model has `linked issues`, not a separate persisted `primary issue`, so all exported issue-key tags are treated as equal links.
|
package/dist/automatify.cjs
CHANGED
|
@@ -1030,8 +1030,10 @@ var Priority;
|
|
|
1030
1030
|
|
|
1031
1031
|
// src/config.ts
|
|
1032
1032
|
var import_node_fs = require("node:fs");
|
|
1033
|
+
var import_node_child_process = require("node:child_process");
|
|
1033
1034
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
1034
1035
|
var DEFAULT_CONFIG_FILENAME = ".testops-cli.json";
|
|
1036
|
+
var KEYCHAIN_SERVICE = "automatify-testops-cli";
|
|
1035
1037
|
function parseFlags(args) {
|
|
1036
1038
|
const flags = {};
|
|
1037
1039
|
const unknownFlags = [];
|
|
@@ -1077,6 +1079,11 @@ function readConfigFile(configPath) {
|
|
|
1077
1079
|
return {};
|
|
1078
1080
|
}
|
|
1079
1081
|
}
|
|
1082
|
+
function writeConfigFile(configPath, file) {
|
|
1083
|
+
(0, import_node_fs.writeFileSync)(configPath, `${JSON.stringify(file, null, 2)}
|
|
1084
|
+
`, "utf8");
|
|
1085
|
+
(0, import_node_fs.chmodSync)(configPath, 384);
|
|
1086
|
+
}
|
|
1080
1087
|
function normalizeBaseUrl(value) {
|
|
1081
1088
|
if (value.endsWith("/")) {
|
|
1082
1089
|
return value.slice(0, -1);
|
|
@@ -1086,6 +1093,30 @@ function normalizeBaseUrl(value) {
|
|
|
1086
1093
|
function readAuthMode(input) {
|
|
1087
1094
|
return input === "api-token" ? "api-token" : "none";
|
|
1088
1095
|
}
|
|
1096
|
+
function defaultKeychainAccount(configPath) {
|
|
1097
|
+
return import_node_path.default.resolve(configPath);
|
|
1098
|
+
}
|
|
1099
|
+
function readKeychainSecret(account) {
|
|
1100
|
+
if (!account || process.platform !== "darwin") {
|
|
1101
|
+
return "";
|
|
1102
|
+
}
|
|
1103
|
+
try {
|
|
1104
|
+
return (0, import_node_child_process.execFileSync)("security", ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account, "-w"], {
|
|
1105
|
+
encoding: "utf8",
|
|
1106
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1107
|
+
}).trim();
|
|
1108
|
+
} catch {
|
|
1109
|
+
return "";
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function setKeychainSecret(account, value) {
|
|
1113
|
+
if (process.platform !== "darwin") {
|
|
1114
|
+
throw new Error("Keychain-backed secrets are only supported on macOS.");
|
|
1115
|
+
}
|
|
1116
|
+
(0, import_node_child_process.execFileSync)("security", ["add-generic-password", "-U", "-s", KEYCHAIN_SERVICE, "-a", account, "-w", value], {
|
|
1117
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1089
1120
|
function valueFromPrecedence(key, flagValue, envValue, fileValue, fallback = "") {
|
|
1090
1121
|
if (flagValue) {
|
|
1091
1122
|
return { value: flagValue, source: "flag" };
|
|
@@ -1145,6 +1176,22 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
|
1145
1176
|
toStringValue(env.JIRA_API_TOKEN),
|
|
1146
1177
|
toStringValue(file.jiraApiToken ?? file.JIRA_API_TOKEN)
|
|
1147
1178
|
);
|
|
1179
|
+
const forgeEndpointResolved = valueFromPrecedence(
|
|
1180
|
+
"forgeEndpoint",
|
|
1181
|
+
"",
|
|
1182
|
+
toStringValue(env.TESTOPS_FORGE_ENDPOINT),
|
|
1183
|
+
toStringValue(file.forgeEndpoint ?? file.TESTOPS_FORGE_ENDPOINT)
|
|
1184
|
+
);
|
|
1185
|
+
const forgeAuthTokenKeychainAccountResolved = valueFromPrecedence(
|
|
1186
|
+
"forgeAuthTokenKeychainAccount",
|
|
1187
|
+
"",
|
|
1188
|
+
"",
|
|
1189
|
+
toStringValue(file.forgeAuthTokenKeychainAccount)
|
|
1190
|
+
);
|
|
1191
|
+
const forgeAuthTokenFromEnv = toStringValue(env.TESTOPS_FORGE_AUTH_TOKEN);
|
|
1192
|
+
const forgeAuthTokenFromFile = toStringValue(file.forgeAuthToken ?? file.TESTOPS_FORGE_AUTH_TOKEN);
|
|
1193
|
+
const forgeAuthTokenFromKeychain = forgeAuthTokenFromEnv || forgeAuthTokenFromFile || !forgeAuthTokenKeychainAccountResolved.value ? "" : readKeychainSecret(forgeAuthTokenKeychainAccountResolved.value);
|
|
1194
|
+
const forgeAuthTokenResolved = forgeAuthTokenFromEnv ? { value: forgeAuthTokenFromEnv, source: "env" } : forgeAuthTokenFromFile ? { value: forgeAuthTokenFromFile, source: "file" } : forgeAuthTokenFromKeychain ? { value: forgeAuthTokenFromKeychain, source: "keychain" } : { value: "", source: "default" };
|
|
1148
1195
|
const config = {
|
|
1149
1196
|
baseUrl: normalizeBaseUrl(baseUrlResolved.value),
|
|
1150
1197
|
projectKey: projectKeyResolved.value,
|
|
@@ -1152,6 +1199,9 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
|
1152
1199
|
authMode: readAuthMode(authModeResolved.value),
|
|
1153
1200
|
jiraEmail: jiraEmailResolved.value,
|
|
1154
1201
|
jiraApiToken: jiraApiTokenResolved.value,
|
|
1202
|
+
forgeEndpoint: normalizeBaseUrl(forgeEndpointResolved.value),
|
|
1203
|
+
forgeAuthToken: forgeAuthTokenResolved.value,
|
|
1204
|
+
forgeAuthTokenKeychainAccount: forgeAuthTokenKeychainAccountResolved.value,
|
|
1155
1205
|
configPath
|
|
1156
1206
|
};
|
|
1157
1207
|
const sources = {
|
|
@@ -1161,6 +1211,9 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
|
1161
1211
|
authMode: authModeResolved.source,
|
|
1162
1212
|
jiraEmail: jiraEmailResolved.source,
|
|
1163
1213
|
jiraApiToken: jiraApiTokenResolved.source,
|
|
1214
|
+
forgeEndpoint: forgeEndpointResolved.source,
|
|
1215
|
+
forgeAuthToken: forgeAuthTokenResolved.source,
|
|
1216
|
+
forgeAuthTokenKeychainAccount: forgeAuthTokenKeychainAccountResolved.source,
|
|
1164
1217
|
configPath: configPathSource
|
|
1165
1218
|
};
|
|
1166
1219
|
const warnings = [];
|
|
@@ -1218,9 +1271,78 @@ function toDisplayLines(resolution) {
|
|
|
1218
1271
|
` authMode: ${values.authMode} (${sources.authMode})`,
|
|
1219
1272
|
` jiraEmail: ${values.jiraEmail || "<unset>"} (${sources.jiraEmail})`,
|
|
1220
1273
|
` jiraApiToken: ${values.jiraApiToken ? maskSecret(values.jiraApiToken) : "<unset>"} (${sources.jiraApiToken})`,
|
|
1274
|
+
` forgeEndpoint: ${values.forgeEndpoint || "<unset>"} (${sources.forgeEndpoint})`,
|
|
1275
|
+
` forgeAuthToken: ${values.forgeAuthToken ? maskSecret(values.forgeAuthToken) : "<unset>"} (${sources.forgeAuthToken})`,
|
|
1276
|
+
` forgeAuthTokenKeychainAccount: ${values.forgeAuthTokenKeychainAccount || "<unset>"} (${sources.forgeAuthTokenKeychainAccount})`,
|
|
1221
1277
|
` configPath: ${values.configPath} (${sources.configPath})`
|
|
1222
1278
|
];
|
|
1223
1279
|
}
|
|
1280
|
+
function normalizeConfigSetKey(input) {
|
|
1281
|
+
switch (input) {
|
|
1282
|
+
case "baseUrl":
|
|
1283
|
+
case "JIRA_BASE_URL":
|
|
1284
|
+
return "baseUrl";
|
|
1285
|
+
case "projectKey":
|
|
1286
|
+
case "JIRA_PROJECT_KEY":
|
|
1287
|
+
return "projectKey";
|
|
1288
|
+
case "issueKey":
|
|
1289
|
+
case "JIRA_ISSUE_KEY":
|
|
1290
|
+
return "issueKey";
|
|
1291
|
+
case "authMode":
|
|
1292
|
+
case "TESTOPS_AUTH_MODE":
|
|
1293
|
+
return "authMode";
|
|
1294
|
+
case "jiraEmail":
|
|
1295
|
+
case "JIRA_EMAIL":
|
|
1296
|
+
return "jiraEmail";
|
|
1297
|
+
case "jiraApiToken":
|
|
1298
|
+
case "JIRA_API_TOKEN":
|
|
1299
|
+
return "jiraApiToken";
|
|
1300
|
+
case "forgeEndpoint":
|
|
1301
|
+
case "TESTOPS_FORGE_ENDPOINT":
|
|
1302
|
+
return "forgeEndpoint";
|
|
1303
|
+
case "forgeAuthToken":
|
|
1304
|
+
case "TESTOPS_FORGE_AUTH_TOKEN":
|
|
1305
|
+
return "forgeAuthToken";
|
|
1306
|
+
default:
|
|
1307
|
+
return "";
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function applyConfigSet(configPath, rawKey, value) {
|
|
1311
|
+
const key = normalizeConfigSetKey(rawKey);
|
|
1312
|
+
if (!key) {
|
|
1313
|
+
return {
|
|
1314
|
+
exitCode: ExitCode.UsageError,
|
|
1315
|
+
stderr: [
|
|
1316
|
+
"Unsupported config key. Use one of: baseUrl, projectKey, issueKey, authMode, jiraEmail, jiraApiToken, forgeEndpoint, forgeAuthToken."
|
|
1317
|
+
]
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
const file = readConfigFile(configPath);
|
|
1321
|
+
if (key === "forgeAuthToken") {
|
|
1322
|
+
const account = toStringValue(file.forgeAuthTokenKeychainAccount) || defaultKeychainAccount(configPath);
|
|
1323
|
+
setKeychainSecret(account, value);
|
|
1324
|
+
const nextFile2 = {
|
|
1325
|
+
...file,
|
|
1326
|
+
forgeAuthToken: void 0,
|
|
1327
|
+
TESTOPS_FORGE_AUTH_TOKEN: void 0,
|
|
1328
|
+
forgeAuthTokenKeychainAccount: account
|
|
1329
|
+
};
|
|
1330
|
+
writeConfigFile(configPath, nextFile2);
|
|
1331
|
+
return {
|
|
1332
|
+
exitCode: ExitCode.Success,
|
|
1333
|
+
stdout: [`Config updated: forgeAuthToken stored in macOS Keychain (${account}).`]
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
const nextFile = {
|
|
1337
|
+
...file,
|
|
1338
|
+
[key]: key === "baseUrl" || key === "forgeEndpoint" ? normalizeBaseUrl(value) : value
|
|
1339
|
+
};
|
|
1340
|
+
writeConfigFile(configPath, nextFile);
|
|
1341
|
+
return {
|
|
1342
|
+
exitCode: ExitCode.Success,
|
|
1343
|
+
stdout: [`Config updated: ${rawKey}.`]
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1224
1346
|
function createConfigHandler(env = process.env, cwd = process.cwd()) {
|
|
1225
1347
|
return (request) => {
|
|
1226
1348
|
const [subcommand, ...subArgs] = request.args;
|
|
@@ -1245,6 +1367,18 @@ function createConfigHandler(env = process.env, cwd = process.cwd()) {
|
|
|
1245
1367
|
stderr: validation.errors.map((line) => `ERROR: ${line}`)
|
|
1246
1368
|
};
|
|
1247
1369
|
}
|
|
1370
|
+
if (subcommand === "set") {
|
|
1371
|
+
const [key, ...valueParts] = subArgs;
|
|
1372
|
+
const readValueFromStdin = key === "forgeAuthToken" && valueParts.length === 1 && valueParts[0] === "--stdin";
|
|
1373
|
+
const value = readValueFromStdin ? (0, import_node_fs.readFileSync)(0, "utf8").trim() : valueParts.join(" ").trim();
|
|
1374
|
+
if (!key || !value) {
|
|
1375
|
+
return {
|
|
1376
|
+
exitCode: ExitCode.UsageError,
|
|
1377
|
+
stderr: ["Usage: automatify testops config set <key> <value>"]
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
return applyConfigSet(resolution.values.configPath, key, value);
|
|
1381
|
+
}
|
|
1248
1382
|
return {
|
|
1249
1383
|
exitCode: ExitCode.UsageError,
|
|
1250
1384
|
stderr: [`Unsupported config subcommand: ${subcommand}`]
|
|
@@ -2761,7 +2895,7 @@ function parseArgs2(args) {
|
|
|
2761
2895
|
"--file",
|
|
2762
2896
|
...CONFIG_FLAGS2
|
|
2763
2897
|
]);
|
|
2764
|
-
const supportedBoolFlags = /* @__PURE__ */ new Set(["--json", "--zip", "--all", "--dry-run"]);
|
|
2898
|
+
const supportedBoolFlags = /* @__PURE__ */ new Set(["--json", "--zip", "--all", "--dry-run", "--feature"]);
|
|
2765
2899
|
for (let index = 0; index < args.length; index += 1) {
|
|
2766
2900
|
const token = args[index];
|
|
2767
2901
|
if (!token.startsWith("--")) {
|
|
@@ -2923,6 +3057,39 @@ function scenarioToLines(scenario) {
|
|
|
2923
3057
|
}
|
|
2924
3058
|
return lines;
|
|
2925
3059
|
}
|
|
3060
|
+
async function resolveScenarioId(context, projectKey, issueKey, scenarioSelector) {
|
|
3061
|
+
const scenariosResult = await context.invokeForgeContract("listBddScenarios", {
|
|
3062
|
+
context: {
|
|
3063
|
+
projectKey,
|
|
3064
|
+
issueKey: issueKey || void 0
|
|
3065
|
+
}
|
|
3066
|
+
});
|
|
3067
|
+
if (!scenariosResult.ok) {
|
|
3068
|
+
return {
|
|
3069
|
+
ok: false,
|
|
3070
|
+
response: {
|
|
3071
|
+
exitCode: ExitCode.RemoteError,
|
|
3072
|
+
stderr: [`ERROR: ${scenariosResult.error.code}: ${scenariosResult.error.message}`]
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
const matchedScenario = scenariosResult.data.find(
|
|
3077
|
+
(item) => item.id === scenarioSelector || (item.key ?? "").toUpperCase() === scenarioSelector.toUpperCase()
|
|
3078
|
+
);
|
|
3079
|
+
if (!matchedScenario) {
|
|
3080
|
+
return {
|
|
3081
|
+
ok: false,
|
|
3082
|
+
response: {
|
|
3083
|
+
exitCode: ExitCode.RemoteError,
|
|
3084
|
+
stderr: [`ERROR: NOT_FOUND: BDD scenario ${scenarioSelector} was not found in this project.`]
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
return {
|
|
3089
|
+
ok: true,
|
|
3090
|
+
scenarioId: matchedScenario.id
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
2926
3093
|
function featureToLines(feature) {
|
|
2927
3094
|
return [
|
|
2928
3095
|
`BDD feature: ${feature.payload.name}`,
|
|
@@ -3010,7 +3177,7 @@ function createBddHandler(deps = {}) {
|
|
|
3010
3177
|
const [subcommand, ...restArgs] = request.args;
|
|
3011
3178
|
const parsed = parseArgs2(restArgs);
|
|
3012
3179
|
const requestedFormat = (parsed.flags["--format"] ?? "").trim().toLowerCase();
|
|
3013
|
-
const outputFormat = parsed.boolFlags.has("--json") ? "json" : requestedFormat || "text";
|
|
3180
|
+
const outputFormat = parsed.boolFlags.has("--json") ? "json" : parsed.boolFlags.has("--feature") ? "feature" : requestedFormat || "text";
|
|
3014
3181
|
const config = resolveCliConfig(pickConfigArgs2(parsed), env, cwd);
|
|
3015
3182
|
const projectKey = config.values.projectKey;
|
|
3016
3183
|
const issueKey = config.values.issueKey;
|
|
@@ -3152,10 +3319,10 @@ function createBddHandler(deps = {}) {
|
|
|
3152
3319
|
};
|
|
3153
3320
|
}
|
|
3154
3321
|
if (nested === "export") {
|
|
3155
|
-
const
|
|
3322
|
+
const scenarioSelector2 = parsed.flags["--id"]?.trim() ?? "";
|
|
3156
3323
|
const exportAll = parsed.boolFlags.has("--all");
|
|
3157
3324
|
const outputDirFlag = parsed.flags["--output-dir"]?.trim() ?? "";
|
|
3158
|
-
if (!
|
|
3325
|
+
if (!scenarioSelector2 && !exportAll) {
|
|
3159
3326
|
return {
|
|
3160
3327
|
exitCode: ExitCode.ValidationError,
|
|
3161
3328
|
stderr: ["ERROR: Provide --id <SCENARIO_ID|SC-4> or --all for testops bdd scenarios export."]
|
|
@@ -3183,12 +3350,12 @@ function createBddHandler(deps = {}) {
|
|
|
3183
3350
|
};
|
|
3184
3351
|
}
|
|
3185
3352
|
const selectedScenarios = exportAll ? [...scenariosResult.data].sort((left, right) => (left.key ?? left.id).localeCompare(right.key ?? right.id)) : scenariosResult.data.filter(
|
|
3186
|
-
(item) => item.id ===
|
|
3353
|
+
(item) => item.id === scenarioSelector2 || (item.key ?? "").toUpperCase() === scenarioSelector2.toUpperCase()
|
|
3187
3354
|
);
|
|
3188
3355
|
if (!exportAll && selectedScenarios.length === 0) {
|
|
3189
3356
|
return {
|
|
3190
3357
|
exitCode: ExitCode.RemoteError,
|
|
3191
|
-
stderr: [`ERROR: NOT_FOUND: BDD scenario ${
|
|
3358
|
+
stderr: [`ERROR: NOT_FOUND: BDD scenario ${scenarioSelector2} was not found in this project.`]
|
|
3192
3359
|
};
|
|
3193
3360
|
}
|
|
3194
3361
|
if (selectedScenarios.length === 0) {
|
|
@@ -3429,9 +3596,9 @@ function createBddHandler(deps = {}) {
|
|
|
3429
3596
|
};
|
|
3430
3597
|
}
|
|
3431
3598
|
}
|
|
3432
|
-
const
|
|
3599
|
+
const scenarioId = parsed.flags["--id"]?.trim() ?? "";
|
|
3433
3600
|
const testCaseKey = parsed.flags["--test-case-key"]?.trim().toUpperCase() ?? "";
|
|
3434
|
-
if (!
|
|
3601
|
+
if (!scenarioId && !testCaseKey) {
|
|
3435
3602
|
return {
|
|
3436
3603
|
exitCode: ExitCode.ValidationError,
|
|
3437
3604
|
stderr: ["ERROR: Provide either --id <SCENARIO_ID> or --test-case-key <TC-197> for testops bdd scenarios run."]
|
|
@@ -3439,8 +3606,8 @@ function createBddHandler(deps = {}) {
|
|
|
3439
3606
|
}
|
|
3440
3607
|
try {
|
|
3441
3608
|
let scenarioIds = [];
|
|
3442
|
-
let scenarioLabel =
|
|
3443
|
-
if (testCaseKey && !
|
|
3609
|
+
let scenarioLabel = scenarioId || testCaseKey;
|
|
3610
|
+
if (testCaseKey && !scenarioId) {
|
|
3444
3611
|
const testCaseResult = await context.invokeForgeContract("getTestCase", {
|
|
3445
3612
|
context: {
|
|
3446
3613
|
projectKey,
|
|
@@ -3475,8 +3642,8 @@ function createBddHandler(deps = {}) {
|
|
|
3475
3642
|
};
|
|
3476
3643
|
}
|
|
3477
3644
|
scenarioLabel = `${testCase.key} (${scenarioIds.length} scenario${scenarioIds.length === 1 ? "" : "s"})`;
|
|
3478
|
-
} else if (
|
|
3479
|
-
scenarioIds = [
|
|
3645
|
+
} else if (scenarioId) {
|
|
3646
|
+
scenarioIds = [scenarioId];
|
|
3480
3647
|
}
|
|
3481
3648
|
const runs = [];
|
|
3482
3649
|
for (const currentScenarioId of scenarioIds) {
|
|
@@ -3532,20 +3699,24 @@ function createBddHandler(deps = {}) {
|
|
|
3532
3699
|
};
|
|
3533
3700
|
}
|
|
3534
3701
|
}
|
|
3535
|
-
const
|
|
3536
|
-
if (!
|
|
3702
|
+
const scenarioSelector = parsed.flags["--id"]?.trim() ?? "";
|
|
3703
|
+
if (!scenarioSelector) {
|
|
3537
3704
|
return {
|
|
3538
3705
|
exitCode: ExitCode.ValidationError,
|
|
3539
3706
|
stderr: ["ERROR: Missing required --id for testops bdd scenarios show."]
|
|
3540
3707
|
};
|
|
3541
3708
|
}
|
|
3542
3709
|
try {
|
|
3710
|
+
const resolvedScenario = await resolveScenarioId(context, projectKey, issueKey, scenarioSelector);
|
|
3711
|
+
if (!resolvedScenario.ok) {
|
|
3712
|
+
return resolvedScenario.response;
|
|
3713
|
+
}
|
|
3543
3714
|
const result = await context.invokeForgeContract("getBddScenario", {
|
|
3544
3715
|
context: {
|
|
3545
3716
|
projectKey,
|
|
3546
3717
|
issueKey: issueKey || void 0
|
|
3547
3718
|
},
|
|
3548
|
-
scenarioId
|
|
3719
|
+
scenarioId: resolvedScenario.scenarioId
|
|
3549
3720
|
});
|
|
3550
3721
|
if (!result.ok) {
|
|
3551
3722
|
return {
|
|
@@ -5454,7 +5625,7 @@ var COMMAND_REGISTRY = [
|
|
|
5454
5625
|
{
|
|
5455
5626
|
name: "config",
|
|
5456
5627
|
description: "Configuration and auth bootstrap commands",
|
|
5457
|
-
subcommands: ["show", "validate"],
|
|
5628
|
+
subcommands: ["show", "validate", "set"],
|
|
5458
5629
|
handler: createConfigHandler()
|
|
5459
5630
|
},
|
|
5460
5631
|
{
|
|
@@ -5496,8 +5667,9 @@ var COMMAND_REGISTRY = [
|
|
|
5496
5667
|
}
|
|
5497
5668
|
];
|
|
5498
5669
|
function createThinClientContext() {
|
|
5499
|
-
const
|
|
5500
|
-
const
|
|
5670
|
+
const config = resolveCliConfig([], process.env, process.cwd()).values;
|
|
5671
|
+
const endpoint = process.env.TESTOPS_FORGE_ENDPOINT?.trim() || config.forgeEndpoint;
|
|
5672
|
+
const authToken = process.env.TESTOPS_FORGE_AUTH_TOKEN?.trim() || config.forgeAuthToken;
|
|
5501
5673
|
if (!endpoint) {
|
|
5502
5674
|
return {
|
|
5503
5675
|
invokeForgeContract: async (_contractName, _payload) => {
|