@automatify-au/cli 0.1.4 → 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 +28 -1
- package/dist/automatify.cjs +234 -20
- 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,10 +101,13 @@ 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`
|
|
108
|
+
- `automatify testops bdd scenarios import --file ./scenario-export/SC-4.feature --dry-run`
|
|
97
109
|
- `automatify testops bdd scenarios import --input-dir ./scenario-export`
|
|
110
|
+
- `automatify testops bdd scenarios import --input-dir ./scenario-export --dry-run`
|
|
98
111
|
- `automatify testops bdd features export --output-dir ./features-export`
|
|
99
112
|
- `automatify testops bdd features export --output-dir ./features-export --zip`
|
|
100
113
|
- `automatify testops bdd features export --output-dir ./features-export --feature-id <FEATURE_ID>`
|
|
@@ -169,6 +182,19 @@ export JIRA_BASE_URL="https://automatify-com-au.atlassian.net"
|
|
|
169
182
|
export JIRA_PROJECT_KEY="DEV"
|
|
170
183
|
```
|
|
171
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
|
+
|
|
172
198
|
Transport notes:
|
|
173
199
|
- Bearer auth is required for operator/CI use. Forge stores only the token hash plus metadata.
|
|
174
200
|
- Tokens are project-scoped, shown once at creation time, and can be revoked from the Forge UI.
|
|
@@ -307,6 +333,7 @@ automatify testops bdd scenarios import --project-key DEV --file ./artifacts/sce
|
|
|
307
333
|
|
|
308
334
|
# Export every visible SC-* file and import the whole folder back later
|
|
309
335
|
automatify testops bdd scenarios export --project-key DEV --all --output-dir ./artifacts/scenario-bulk
|
|
336
|
+
automatify testops bdd scenarios import --project-key DEV --input-dir ./artifacts/scenario-bulk --dry-run
|
|
310
337
|
automatify testops bdd scenarios import --project-key DEV --input-dir ./artifacts/scenario-bulk
|
|
311
338
|
|
|
312
339
|
# Re-import one exported file later
|
|
@@ -314,7 +341,7 @@ automatify testops ingest feature --project-key DEV --file ./artifacts/features-
|
|
|
314
341
|
```
|
|
315
342
|
|
|
316
343
|
Feature export notes:
|
|
317
|
-
- `bdd scenarios show --format feature`
|
|
344
|
+
- `bdd scenarios show --format feature` and `bdd scenarios show --feature` render one scenario in feature-style text.
|
|
318
345
|
- `bdd scenarios export` writes a single-scenario `SC-*.feature` file with a reserved `@testops-scenario-SC-*` tag.
|
|
319
346
|
- Jira issue links are exported as normal Gherkin tags like `@DEV-2`; on import they are written back to `linkedIssueKeys`.
|
|
320
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"]);
|
|
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) {
|
|
@@ -3278,6 +3445,7 @@ function createBddHandler(deps = {}) {
|
|
|
3278
3445
|
if (nested === "import") {
|
|
3279
3446
|
const sourceFile = parsed.flags["--file"] ? import_node_path6.default.resolve(cwd, parsed.flags["--file"]) : "";
|
|
3280
3447
|
const inputDir = parsed.flags["--input-dir"] ? import_node_path6.default.resolve(cwd, parsed.flags["--input-dir"]) : "";
|
|
3448
|
+
const dryRun = parsed.boolFlags.has("--dry-run");
|
|
3281
3449
|
if (!sourceFile && !inputDir) {
|
|
3282
3450
|
return {
|
|
3283
3451
|
exitCode: ExitCode.ValidationError,
|
|
@@ -3318,6 +3486,7 @@ function createBddHandler(deps = {}) {
|
|
|
3318
3486
|
};
|
|
3319
3487
|
}
|
|
3320
3488
|
const updatedItems = [];
|
|
3489
|
+
const plannedItems = [];
|
|
3321
3490
|
for (const entry of parsedScenarios) {
|
|
3322
3491
|
const parsedScenario = entry.parsed;
|
|
3323
3492
|
const scenario = scenariosResult.data.find(
|
|
@@ -3330,6 +3499,19 @@ function createBddHandler(deps = {}) {
|
|
|
3330
3499
|
};
|
|
3331
3500
|
}
|
|
3332
3501
|
const nextTags = parsedScenario.tags.filter((tag) => !SCENARIO_METADATA_PATTERN.test(tag));
|
|
3502
|
+
if (dryRun) {
|
|
3503
|
+
plannedItems.push({
|
|
3504
|
+
file: entry.filePath,
|
|
3505
|
+
scenarioId: scenario.id,
|
|
3506
|
+
scenarioKey: scenario.key ?? null,
|
|
3507
|
+
featureName: parsedScenario.featureName,
|
|
3508
|
+
scenarioName: parsedScenario.scenarioName,
|
|
3509
|
+
tags: [...nextTags],
|
|
3510
|
+
linkedIssueKeys: [...parsedScenario.linkedIssueKeys],
|
|
3511
|
+
steps: [...parsedScenario.steps]
|
|
3512
|
+
});
|
|
3513
|
+
continue;
|
|
3514
|
+
}
|
|
3333
3515
|
const updateResult = await context.invokeForgeContract("updateScenario", {
|
|
3334
3516
|
context: {
|
|
3335
3517
|
projectKey,
|
|
@@ -3362,16 +3544,43 @@ function createBddHandler(deps = {}) {
|
|
|
3362
3544
|
action: "bdd-scenarios-import",
|
|
3363
3545
|
projectKey,
|
|
3364
3546
|
issueKey: issueKey || null,
|
|
3365
|
-
|
|
3547
|
+
dryRun,
|
|
3548
|
+
count: dryRun ? plannedItems.length : updatedItems.length,
|
|
3366
3549
|
file: sourceFile || null,
|
|
3367
3550
|
inputDir: inputDir || null,
|
|
3368
|
-
items:
|
|
3551
|
+
items: dryRun ? plannedItems.map((entry) => ({
|
|
3552
|
+
file: entry.file,
|
|
3553
|
+
scenarioId: entry.scenarioId,
|
|
3554
|
+
scenarioKey: entry.scenarioKey,
|
|
3555
|
+
featureName: entry.featureName,
|
|
3556
|
+
scenarioName: entry.scenarioName,
|
|
3557
|
+
tags: [...entry.tags],
|
|
3558
|
+
linkedIssueKeys: [...entry.linkedIssueKeys],
|
|
3559
|
+
steps: entry.steps.map((step) => ({
|
|
3560
|
+
keyword: step.keyword,
|
|
3561
|
+
text: step.text
|
|
3562
|
+
}))
|
|
3563
|
+
})) : updatedItems.map((entry) => ({
|
|
3369
3564
|
file: entry.file,
|
|
3370
3565
|
item: toJsonScenario(entry.item)
|
|
3371
3566
|
}))
|
|
3372
3567
|
})
|
|
3373
3568
|
};
|
|
3374
3569
|
}
|
|
3570
|
+
if (dryRun) {
|
|
3571
|
+
return {
|
|
3572
|
+
exitCode: ExitCode.Success,
|
|
3573
|
+
stdout: plannedItems.flatMap((entry) => [
|
|
3574
|
+
`BDD scenario import dry-run: ${entry.scenarioKey ?? entry.scenarioId}`,
|
|
3575
|
+
`Source: ${entry.file}`,
|
|
3576
|
+
`Feature: ${entry.featureName}`,
|
|
3577
|
+
`Scenario: ${entry.scenarioName}`,
|
|
3578
|
+
`Tags: ${entry.tags.length > 0 ? entry.tags.join(", ") : "none"}`,
|
|
3579
|
+
`Linked issues: ${entry.linkedIssueKeys.length > 0 ? entry.linkedIssueKeys.join(", ") : "none"}`,
|
|
3580
|
+
...entry.steps.map((step) => ` ${step.keyword} ${step.text}`)
|
|
3581
|
+
])
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3375
3584
|
return {
|
|
3376
3585
|
exitCode: ExitCode.Success,
|
|
3377
3586
|
stdout: updatedItems.flatMap((entry) => [
|
|
@@ -3387,9 +3596,9 @@ function createBddHandler(deps = {}) {
|
|
|
3387
3596
|
};
|
|
3388
3597
|
}
|
|
3389
3598
|
}
|
|
3390
|
-
const
|
|
3599
|
+
const scenarioId = parsed.flags["--id"]?.trim() ?? "";
|
|
3391
3600
|
const testCaseKey = parsed.flags["--test-case-key"]?.trim().toUpperCase() ?? "";
|
|
3392
|
-
if (!
|
|
3601
|
+
if (!scenarioId && !testCaseKey) {
|
|
3393
3602
|
return {
|
|
3394
3603
|
exitCode: ExitCode.ValidationError,
|
|
3395
3604
|
stderr: ["ERROR: Provide either --id <SCENARIO_ID> or --test-case-key <TC-197> for testops bdd scenarios run."]
|
|
@@ -3397,8 +3606,8 @@ function createBddHandler(deps = {}) {
|
|
|
3397
3606
|
}
|
|
3398
3607
|
try {
|
|
3399
3608
|
let scenarioIds = [];
|
|
3400
|
-
let scenarioLabel =
|
|
3401
|
-
if (testCaseKey && !
|
|
3609
|
+
let scenarioLabel = scenarioId || testCaseKey;
|
|
3610
|
+
if (testCaseKey && !scenarioId) {
|
|
3402
3611
|
const testCaseResult = await context.invokeForgeContract("getTestCase", {
|
|
3403
3612
|
context: {
|
|
3404
3613
|
projectKey,
|
|
@@ -3433,8 +3642,8 @@ function createBddHandler(deps = {}) {
|
|
|
3433
3642
|
};
|
|
3434
3643
|
}
|
|
3435
3644
|
scenarioLabel = `${testCase.key} (${scenarioIds.length} scenario${scenarioIds.length === 1 ? "" : "s"})`;
|
|
3436
|
-
} else if (
|
|
3437
|
-
scenarioIds = [
|
|
3645
|
+
} else if (scenarioId) {
|
|
3646
|
+
scenarioIds = [scenarioId];
|
|
3438
3647
|
}
|
|
3439
3648
|
const runs = [];
|
|
3440
3649
|
for (const currentScenarioId of scenarioIds) {
|
|
@@ -3490,20 +3699,24 @@ function createBddHandler(deps = {}) {
|
|
|
3490
3699
|
};
|
|
3491
3700
|
}
|
|
3492
3701
|
}
|
|
3493
|
-
const
|
|
3494
|
-
if (!
|
|
3702
|
+
const scenarioSelector = parsed.flags["--id"]?.trim() ?? "";
|
|
3703
|
+
if (!scenarioSelector) {
|
|
3495
3704
|
return {
|
|
3496
3705
|
exitCode: ExitCode.ValidationError,
|
|
3497
3706
|
stderr: ["ERROR: Missing required --id for testops bdd scenarios show."]
|
|
3498
3707
|
};
|
|
3499
3708
|
}
|
|
3500
3709
|
try {
|
|
3710
|
+
const resolvedScenario = await resolveScenarioId(context, projectKey, issueKey, scenarioSelector);
|
|
3711
|
+
if (!resolvedScenario.ok) {
|
|
3712
|
+
return resolvedScenario.response;
|
|
3713
|
+
}
|
|
3501
3714
|
const result = await context.invokeForgeContract("getBddScenario", {
|
|
3502
3715
|
context: {
|
|
3503
3716
|
projectKey,
|
|
3504
3717
|
issueKey: issueKey || void 0
|
|
3505
3718
|
},
|
|
3506
|
-
scenarioId
|
|
3719
|
+
scenarioId: resolvedScenario.scenarioId
|
|
3507
3720
|
});
|
|
3508
3721
|
if (!result.ok) {
|
|
3509
3722
|
return {
|
|
@@ -5412,7 +5625,7 @@ var COMMAND_REGISTRY = [
|
|
|
5412
5625
|
{
|
|
5413
5626
|
name: "config",
|
|
5414
5627
|
description: "Configuration and auth bootstrap commands",
|
|
5415
|
-
subcommands: ["show", "validate"],
|
|
5628
|
+
subcommands: ["show", "validate", "set"],
|
|
5416
5629
|
handler: createConfigHandler()
|
|
5417
5630
|
},
|
|
5418
5631
|
{
|
|
@@ -5454,8 +5667,9 @@ var COMMAND_REGISTRY = [
|
|
|
5454
5667
|
}
|
|
5455
5668
|
];
|
|
5456
5669
|
function createThinClientContext() {
|
|
5457
|
-
const
|
|
5458
|
-
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;
|
|
5459
5673
|
if (!endpoint) {
|
|
5460
5674
|
return {
|
|
5461
5675
|
invokeForgeContract: async (_contractName, _payload) => {
|