@automatify-au/cli 0.1.16 → 0.1.18

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.
Files changed (3) hide show
  1. package/README.md +37 -21
  2. package/dist/automatify.cjs +450 -194
  3. package/package.json +1 -1
@@ -2894,8 +2894,8 @@ var require_utils = __commonJS({
2894
2894
  var result = transform[inputType][outputType](input);
2895
2895
  return result;
2896
2896
  };
2897
- exports2.resolve = function(path16) {
2898
- var parts = path16.split("/");
2897
+ exports2.resolve = function(path17) {
2898
+ var parts = path17.split("/");
2899
2899
  var result = [];
2900
2900
  for (var index = 0; index < parts.length; index++) {
2901
2901
  var part = parts[index];
@@ -8748,18 +8748,18 @@ var require_object = __commonJS({
8748
8748
  var object = new ZipObject(name, zipObjectContent, o);
8749
8749
  this.files[name] = object;
8750
8750
  };
8751
- var parentFolder = function(path16) {
8752
- if (path16.slice(-1) === "/") {
8753
- path16 = path16.substring(0, path16.length - 1);
8751
+ var parentFolder = function(path17) {
8752
+ if (path17.slice(-1) === "/") {
8753
+ path17 = path17.substring(0, path17.length - 1);
8754
8754
  }
8755
- var lastSlash = path16.lastIndexOf("/");
8756
- return lastSlash > 0 ? path16.substring(0, lastSlash) : "";
8755
+ var lastSlash = path17.lastIndexOf("/");
8756
+ return lastSlash > 0 ? path17.substring(0, lastSlash) : "";
8757
8757
  };
8758
- var forceTrailingSlash = function(path16) {
8759
- if (path16.slice(-1) !== "/") {
8760
- path16 += "/";
8758
+ var forceTrailingSlash = function(path17) {
8759
+ if (path17.slice(-1) !== "/") {
8760
+ path17 += "/";
8761
8761
  }
8762
- return path16;
8762
+ return path17;
8763
8763
  };
8764
8764
  var folderAdd = function(name, createFolders) {
8765
8765
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -10720,8 +10720,8 @@ function toJsonLine(data) {
10720
10720
  }
10721
10721
 
10722
10722
  // src/auto.ts
10723
- var import_node_fs4 = require("node:fs");
10724
- var import_node_path5 = __toESM(require("node:path"), 1);
10723
+ var import_node_fs5 = require("node:fs");
10724
+ var import_node_path6 = __toESM(require("node:path"), 1);
10725
10725
 
10726
10726
  // ../../packages/shared-types/dist/entitlements.js
10727
10727
  var UNLIMITED_ACTIVE_USERS = Number.MAX_SAFE_INTEGER;
@@ -10764,12 +10764,19 @@ var Priority;
10764
10764
  })(Priority || (Priority = {}));
10765
10765
 
10766
10766
  // src/config.ts
10767
- var import_node_fs = require("node:fs");
10768
- var import_node_path = __toESM(require("node:path"), 1);
10767
+ var import_node_fs2 = require("node:fs");
10768
+ var import_node_path2 = __toESM(require("node:path"), 1);
10769
10769
 
10770
10770
  // src/secureStore.ts
10771
10771
  var import_node_child_process = require("node:child_process");
10772
+ var import_node_crypto = require("node:crypto");
10773
+ var import_node_fs = require("node:fs");
10774
+ var import_node_path = __toESM(require("node:path"), 1);
10772
10775
  var KEYCHAIN_SERVICE = "automatify-testops-cli";
10776
+ var LINUX_SECURE_STORE_ENV = "TESTOPS_LINUX_SECURE_STORE";
10777
+ var LINUX_PROTECTED_PREFIX = "linux-aes-gcm-v1";
10778
+ var LINUX_JIRA_ACCOUNT_SUFFIX = ":jiraApiToken";
10779
+ var LINUX_KEY_FILENAME = ".testops-cli.key";
10773
10780
  function errorText(error) {
10774
10781
  return error instanceof Error && error.message ? error.message : "unknown secure-storage error";
10775
10782
  }
@@ -10809,11 +10816,85 @@ function macOSKeychainWriteInput(account, value) {
10809
10816
  quoteSecurityInteractiveArg(value)
10810
10817
  ].join(" ") + "\n";
10811
10818
  }
10819
+ function linuxSecureStoreEnabled() {
10820
+ return process.env[LINUX_SECURE_STORE_ENV]?.trim() === "1";
10821
+ }
10822
+ function isLinuxProtectedValue(value) {
10823
+ return Boolean(value?.startsWith(`${LINUX_PROTECTED_PREFIX}.`));
10824
+ }
10825
+ function linuxConfigPathForAccount(account) {
10826
+ const normalized = account.endsWith(LINUX_JIRA_ACCOUNT_SUFFIX) ? account.slice(0, -LINUX_JIRA_ACCOUNT_SUFFIX.length) : account;
10827
+ return import_node_path.default.resolve(normalized);
10828
+ }
10829
+ function linuxKeyPathForAccount(account) {
10830
+ return import_node_path.default.join(import_node_path.default.dirname(linuxConfigPathForAccount(account)), LINUX_KEY_FILENAME);
10831
+ }
10832
+ function readLinuxKey(keyPath) {
10833
+ const key = (0, import_node_fs.readFileSync)(keyPath);
10834
+ if (key.length !== 32) {
10835
+ throw new Error(`Linux secure-store key at ${keyPath} must be exactly 32 bytes.`);
10836
+ }
10837
+ return key;
10838
+ }
10839
+ function readOrCreateLinuxKey(keyPath) {
10840
+ const directory = import_node_path.default.dirname(keyPath);
10841
+ (0, import_node_fs.mkdirSync)(directory, { recursive: true, mode: 448 });
10842
+ (0, import_node_fs.chmodSync)(directory, 448);
10843
+ if ((0, import_node_fs.existsSync)(keyPath)) {
10844
+ (0, import_node_fs.chmodSync)(keyPath, 384);
10845
+ return readLinuxKey(keyPath);
10846
+ }
10847
+ const key = (0, import_node_crypto.randomBytes)(32);
10848
+ try {
10849
+ (0, import_node_fs.writeFileSync)(keyPath, key, { flag: "wx", mode: 384 });
10850
+ } catch (error) {
10851
+ if (error.code !== "EEXIST") {
10852
+ throw error;
10853
+ }
10854
+ }
10855
+ (0, import_node_fs.chmodSync)(keyPath, 384);
10856
+ return readLinuxKey(keyPath);
10857
+ }
10858
+ function protectLinuxSecret(account, value) {
10859
+ const keyPath = linuxKeyPathForAccount(account);
10860
+ const key = readOrCreateLinuxKey(keyPath);
10861
+ const iv = (0, import_node_crypto.randomBytes)(12);
10862
+ const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
10863
+ const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
10864
+ const tag = cipher.getAuthTag();
10865
+ const encodedKeyPath = Buffer.from(keyPath, "utf8").toString("base64url");
10866
+ return [
10867
+ LINUX_PROTECTED_PREFIX,
10868
+ encodedKeyPath,
10869
+ iv.toString("base64url"),
10870
+ tag.toString("base64url"),
10871
+ ciphertext.toString("base64url")
10872
+ ].join(".");
10873
+ }
10874
+ function unprotectLinuxSecret(protectedValue) {
10875
+ const parts = protectedValue.split(".");
10876
+ if (parts.length !== 5 || parts[0] !== LINUX_PROTECTED_PREFIX) {
10877
+ throw new Error("Unsupported Linux secure-store payload.");
10878
+ }
10879
+ const [, encodedKeyPath, encodedIv, encodedTag, encodedCiphertext] = parts;
10880
+ const keyPath = Buffer.from(encodedKeyPath, "base64url").toString("utf8");
10881
+ const key = readLinuxKey(keyPath);
10882
+ const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, Buffer.from(encodedIv, "base64url"));
10883
+ decipher.setAuthTag(Buffer.from(encodedTag, "base64url"));
10884
+ const plaintext = Buffer.concat([decipher.update(Buffer.from(encodedCiphertext, "base64url")), decipher.final()]);
10885
+ return plaintext.toString("utf8");
10886
+ }
10812
10887
  function readSecureSecret(locator, deps = {}) {
10813
10888
  const platform = deps.platform ?? process.platform;
10814
10889
  const run = deps.execFileSync ?? import_node_child_process.execFileSync;
10815
10890
  if (platform === "darwin") {
10816
10891
  if (!locator.account) {
10892
+ if (isLinuxProtectedValue(locator.protectedValue)) {
10893
+ return {
10894
+ status: "error",
10895
+ message: "This config contains a Linux encrypted local-store value, but it is being read on macOS. Re-store the secret on this machine."
10896
+ };
10897
+ }
10817
10898
  return locator.protectedValue ? {
10818
10899
  status: "error",
10819
10900
  message: "This config contains a Windows DPAPI value, but it is being read on macOS. Re-store the secret on this machine."
@@ -10836,6 +10917,12 @@ function readSecureSecret(locator, deps = {}) {
10836
10917
  }
10837
10918
  }
10838
10919
  if (platform === "win32") {
10920
+ if (isLinuxProtectedValue(locator.protectedValue)) {
10921
+ return {
10922
+ status: "error",
10923
+ message: "This config contains a Linux encrypted local-store value, but it is being read on Windows. Re-store the secret for the current Windows user."
10924
+ };
10925
+ }
10839
10926
  if (!locator.protectedValue) {
10840
10927
  return locator.account ? {
10841
10928
  status: "error",
@@ -10855,14 +10942,40 @@ function readSecureSecret(locator, deps = {}) {
10855
10942
  } catch (error) {
10856
10943
  return {
10857
10944
  status: "error",
10858
- message: `Unable to decrypt the Windows DPAPI value for the current user: ${errorText(error)}. Re-store the secret.`
10945
+ message: `Unable to decrypt the Windows DPAPI value for the current user: ${errorText(error)}. Re-store the secret for the current Windows user.`
10946
+ };
10947
+ }
10948
+ }
10949
+ if (platform === "linux" && linuxSecureStoreEnabled()) {
10950
+ if (locator.account && !locator.protectedValue) {
10951
+ return {
10952
+ status: "error",
10953
+ message: "This config references a macOS Keychain secret, but it is being read on Linux. Re-store the secret on this machine."
10954
+ };
10955
+ }
10956
+ if (!locator.protectedValue) {
10957
+ return { status: "missing" };
10958
+ }
10959
+ if (!isLinuxProtectedValue(locator.protectedValue)) {
10960
+ return {
10961
+ status: "error",
10962
+ message: "This config contains a foreign protected value that cannot be decrypted by the Linux local store. Re-store the secret on this machine."
10963
+ };
10964
+ }
10965
+ try {
10966
+ const value = unprotectLinuxSecret(locator.protectedValue);
10967
+ return value ? { status: "available", value, source: "secure-store" } : { status: "error", message: "Linux encrypted local store returned an empty value. Re-store the secret." };
10968
+ } catch (error) {
10969
+ return {
10970
+ status: "error",
10971
+ message: `Unable to decrypt the Linux encrypted local-store value: ${errorText(error)} Re-store the secret.`
10859
10972
  };
10860
10973
  }
10861
10974
  }
10862
10975
  if (locator.account || locator.protectedValue) {
10863
10976
  return {
10864
10977
  status: "error",
10865
- message: "This config contains local secure-store metadata, but local secure storage is supported only on macOS and Windows. Use an environment variable on Linux/CI."
10978
+ message: "This config contains local secure-store metadata, but local secure storage is disabled on Linux/CI. Use environment variables or explicitly enable TESTOPS_LINUX_SECURE_STORE=1 on a trusted persistent Linux host."
10866
10979
  };
10867
10980
  }
10868
10981
  return { status: "missing" };
@@ -10898,8 +11011,15 @@ function writeSecureSecret(account, value, deps = {}) {
10898
11011
  }
10899
11012
  return { source: "secure-store", protectedValue };
10900
11013
  }
11014
+ if (platform === "linux" && linuxSecureStoreEnabled()) {
11015
+ try {
11016
+ return { source: "secure-store", protectedValue: protectLinuxSecret(account, value) };
11017
+ } catch {
11018
+ throw new Error("Unable to store the secret in the Linux encrypted local store.");
11019
+ }
11020
+ }
10901
11021
  throw new Error(
10902
- "Secure local secret storage is supported on macOS and Windows only. On Linux/CI, keep secrets in environment variables."
11022
+ "Secure local secret storage is supported on macOS and Windows. On trusted persistent Linux hosts, set TESTOPS_LINUX_SECURE_STORE=1; otherwise keep secrets in environment variables."
10903
11023
  );
10904
11024
  }
10905
11025
  function deleteSecureSecret(locator, deps = {}) {
@@ -10924,6 +11044,9 @@ function deleteSecureSecret(locator, deps = {}) {
10924
11044
  if (platform === "win32") {
10925
11045
  return locator.protectedValue ? { status: "deleted" } : { status: "missing" };
10926
11046
  }
11047
+ if (platform === "linux") {
11048
+ return locator.account || locator.protectedValue ? { status: "deleted" } : { status: "missing" };
11049
+ }
10927
11050
  return locator.account || locator.protectedValue ? { status: "deleted" } : { status: "missing" };
10928
11051
  }
10929
11052
 
@@ -10972,11 +11095,11 @@ function firstStringValue(...values) {
10972
11095
  return "";
10973
11096
  }
10974
11097
  function readConfigFile(configPath) {
10975
- if (!(0, import_node_fs.existsSync)(configPath)) {
11098
+ if (!(0, import_node_fs2.existsSync)(configPath)) {
10976
11099
  return {};
10977
11100
  }
10978
11101
  try {
10979
- const raw = (0, import_node_fs.readFileSync)(configPath, "utf8");
11102
+ const raw = (0, import_node_fs2.readFileSync)(configPath, "utf8");
10980
11103
  const parsed = JSON.parse(raw);
10981
11104
  return typeof parsed === "object" && parsed !== null ? parsed : {};
10982
11105
  } catch {
@@ -10984,11 +11107,11 @@ function readConfigFile(configPath) {
10984
11107
  }
10985
11108
  }
10986
11109
  function readConfigFileForMutation(configPath) {
10987
- if (!(0, import_node_fs.existsSync)(configPath)) {
11110
+ if (!(0, import_node_fs2.existsSync)(configPath)) {
10988
11111
  return { file: {} };
10989
11112
  }
10990
11113
  try {
10991
- const raw = (0, import_node_fs.readFileSync)(configPath, "utf8");
11114
+ const raw = (0, import_node_fs2.readFileSync)(configPath, "utf8");
10992
11115
  const parsed = JSON.parse(raw);
10993
11116
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
10994
11117
  return {
@@ -11005,10 +11128,10 @@ function readConfigFileForMutation(configPath) {
11005
11128
  }
11006
11129
  }
11007
11130
  function writeConfigFile(configPath, file) {
11008
- (0, import_node_fs.writeFileSync)(configPath, `${JSON.stringify(file, null, 2)}
11131
+ (0, import_node_fs2.writeFileSync)(configPath, `${JSON.stringify(file, null, 2)}
11009
11132
  `, "utf8");
11010
11133
  if (process.platform !== "win32") {
11011
- (0, import_node_fs.chmodSync)(configPath, 384);
11134
+ (0, import_node_fs2.chmodSync)(configPath, 384);
11012
11135
  }
11013
11136
  }
11014
11137
  function normalizeBaseUrl(value) {
@@ -11021,10 +11144,10 @@ function readAuthMode(input) {
11021
11144
  return input === "api-token" ? "api-token" : "none";
11022
11145
  }
11023
11146
  function defaultKeychainAccount(configPath) {
11024
- return import_node_path.default.resolve(configPath);
11147
+ return import_node_path2.default.resolve(configPath);
11025
11148
  }
11026
11149
  function defaultJiraApiTokenKeychainAccount(configPath) {
11027
- return `${import_node_path.default.resolve(configPath)}:jiraApiToken`;
11150
+ return `${import_node_path2.default.resolve(configPath)}:jiraApiToken`;
11028
11151
  }
11029
11152
  function valueFromPrecedence(key, flagValue, envValue, fileValue, fallback = "") {
11030
11153
  if (flagValue) {
@@ -11101,9 +11224,9 @@ function clearSecretFields(file, key) {
11101
11224
  }
11102
11225
  function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
11103
11226
  const { flags, unknownFlags } = parseFlags(args);
11104
- const configPathFlag = flags["--config"] ? import_node_path.default.resolve(cwd, flags["--config"]) : "";
11227
+ const configPathFlag = flags["--config"] ? import_node_path2.default.resolve(cwd, flags["--config"]) : "";
11105
11228
  const configPathEnv = toStringValue(env.TESTOPS_CONFIG_PATH);
11106
- const configPath = configPathFlag || (configPathEnv ? import_node_path.default.resolve(cwd, configPathEnv) : import_node_path.default.resolve(cwd, DEFAULT_CONFIG_FILENAME));
11229
+ const configPath = configPathFlag || (configPathEnv ? import_node_path2.default.resolve(cwd, configPathEnv) : import_node_path2.default.resolve(cwd, DEFAULT_CONFIG_FILENAME));
11107
11230
  const configPathSource = configPathFlag ? "flag" : configPathEnv ? "env" : "default";
11108
11231
  const file = readConfigFile(configPath);
11109
11232
  const baseUrlResolved = valueFromPrecedence(
@@ -11194,7 +11317,7 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
11194
11317
  if (unknownFlags.length > 0) {
11195
11318
  warnings.push(`Ignored unknown config flags: ${unknownFlags.join(", ")}`);
11196
11319
  }
11197
- if (!(0, import_node_fs.existsSync)(configPath) && sources.configPath !== "default") {
11320
+ if (!(0, import_node_fs2.existsSync)(configPath) && sources.configPath !== "default") {
11198
11321
  warnings.push(`Config file not found at ${configPath}; using env/flags/defaults.`);
11199
11322
  }
11200
11323
  return { values: config, sources, warnings, errors };
@@ -11458,7 +11581,7 @@ function createConfigHandler(env = process.env, cwd = process.cwd()) {
11458
11581
  const [key, ...valueParts] = stripConfigPathArgs(subArgs);
11459
11582
  const normalizedKey = normalizeConfigSetKey(key ?? "");
11460
11583
  const readValueFromStdin = (normalizedKey === "forgeAuthToken" || normalizedKey === "jiraApiToken") && valueParts.length === 1 && valueParts[0] === "--stdin";
11461
- const value = readValueFromStdin ? (0, import_node_fs.readFileSync)(0, "utf8").trim() : valueParts.join(" ").trim();
11584
+ const value = readValueFromStdin ? (0, import_node_fs2.readFileSync)(0, "utf8").trim() : valueParts.join(" ").trim();
11462
11585
  if (!key || !value) {
11463
11586
  return {
11464
11587
  exitCode: ExitCode.UsageError,
@@ -11773,7 +11896,7 @@ function diagExecutionNoWork() {
11773
11896
  }
11774
11897
 
11775
11898
  // src/autoDiscover.ts
11776
- var import_node_path2 = __toESM(require("node:path"), 1);
11899
+ var import_node_path3 = __toESM(require("node:path"), 1);
11777
11900
  var RESULT_FOLDER_NAMES = /* @__PURE__ */ new Set([
11778
11901
  "results",
11779
11902
  "result",
@@ -11785,7 +11908,7 @@ var RESULT_FOLDER_NAMES = /* @__PURE__ */ new Set([
11785
11908
  "failsafe-reports"
11786
11909
  ]);
11787
11910
  function toPosix2(value) {
11788
- return value.split(import_node_path2.default.sep).join("/");
11911
+ return value.split(import_node_path3.default.sep).join("/");
11789
11912
  }
11790
11913
  function isJUnitXmlArtifact(filePath) {
11791
11914
  const normalized = toPosix2(filePath);
@@ -11879,8 +12002,8 @@ function discoverFeatureFiles(scannedFiles) {
11879
12002
  }
11880
12003
 
11881
12004
  // src/autoMapping.ts
11882
- var import_node_fs2 = require("node:fs");
11883
- var import_node_path3 = __toESM(require("node:path"), 1);
12005
+ var import_node_fs3 = require("node:fs");
12006
+ var import_node_path4 = __toESM(require("node:path"), 1);
11884
12007
  var ISSUE_KEY_REGEX = /\b[A-Z][A-Z0-9]+-\d+\b/g;
11885
12008
  function normalizeName(value) {
11886
12009
  return value.trim().toLowerCase();
@@ -11890,10 +12013,10 @@ function extractIssueKeysFromText(text) {
11890
12013
  return [...new Set(found)].sort((a, b) => a.localeCompare(b));
11891
12014
  }
11892
12015
  function parseFeatureFile(featureAbsolutePath) {
11893
- if (!(0, import_node_fs2.existsSync)(featureAbsolutePath)) {
12016
+ if (!(0, import_node_fs3.existsSync)(featureAbsolutePath)) {
11894
12017
  return { names: [], issueKeys: [] };
11895
12018
  }
11896
- const raw = (0, import_node_fs2.readFileSync)(featureAbsolutePath, "utf8");
12019
+ const raw = (0, import_node_fs3.readFileSync)(featureAbsolutePath, "utf8");
11897
12020
  const lines = raw.split(/\r?\n/);
11898
12021
  const names = /* @__PURE__ */ new Set();
11899
12022
  const issueKeys = new Set(extractIssueKeysFromText(raw));
@@ -11951,7 +12074,7 @@ function buildMappingPreview(input) {
11951
12074
  const localNamesSet = /* @__PURE__ */ new Set();
11952
12075
  const issueCandidatesSet = /* @__PURE__ */ new Set();
11953
12076
  for (const featurePath of input.featurePaths) {
11954
- const absolutePath = import_node_path3.default.join(input.rootPath, featurePath);
12077
+ const absolutePath = import_node_path4.default.join(input.rootPath, featurePath);
11955
12078
  const parsed = parseFeatureFile(absolutePath);
11956
12079
  for (const name of parsed.names) {
11957
12080
  localNamesSet.add(name);
@@ -12000,8 +12123,8 @@ function buildMappingPreview(input) {
12000
12123
  }
12001
12124
 
12002
12125
  // src/autoScanner.ts
12003
- var import_node_fs3 = require("node:fs");
12004
- var import_node_path4 = __toESM(require("node:path"), 1);
12126
+ var import_node_fs4 = require("node:fs");
12127
+ var import_node_path5 = __toESM(require("node:path"), 1);
12005
12128
  var DEFAULT_MAX_DEPTH = 5;
12006
12129
  var DEFAULT_EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
12007
12130
  ".cache",
@@ -12016,7 +12139,7 @@ var DEFAULT_EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
12016
12139
  "node_modules"
12017
12140
  ]);
12018
12141
  function normalizeRelativePath(value) {
12019
- return value.split(import_node_path4.default.sep).join("/");
12142
+ return value.split(import_node_path5.default.sep).join("/");
12020
12143
  }
12021
12144
  function normalizePatterns(values) {
12022
12145
  if (!values) {
@@ -12070,16 +12193,16 @@ function scanProjectFiles(options) {
12070
12193
  const includePatterns = normalizePatterns(options.includePatterns);
12071
12194
  const excludePatterns = normalizePatterns(options.excludePatterns);
12072
12195
  const maxDepth = safeMaxDepth(options.maxDepth);
12073
- const rootPath = import_node_path4.default.resolve(options.rootPath);
12196
+ const rootPath = import_node_path5.default.resolve(options.rootPath);
12074
12197
  const scannedFiles = [];
12075
12198
  const visit = (absoluteDirectory, depth) => {
12076
12199
  if (depth > maxDepth) {
12077
12200
  return;
12078
12201
  }
12079
- const entries = (0, import_node_fs3.readdirSync)(absoluteDirectory, { withFileTypes: true }).slice().sort((a, b) => a.name.localeCompare(b.name));
12202
+ const entries = (0, import_node_fs4.readdirSync)(absoluteDirectory, { withFileTypes: true }).slice().sort((a, b) => a.name.localeCompare(b.name));
12080
12203
  for (const entry of entries) {
12081
- const absoluteEntryPath = import_node_path4.default.join(absoluteDirectory, entry.name);
12082
- const relativeEntryPath = normalizeRelativePath(import_node_path4.default.relative(rootPath, absoluteEntryPath));
12204
+ const absoluteEntryPath = import_node_path5.default.join(absoluteDirectory, entry.name);
12205
+ const relativeEntryPath = normalizeRelativePath(import_node_path5.default.relative(rootPath, absoluteEntryPath));
12083
12206
  if (!relativeEntryPath) {
12084
12207
  continue;
12085
12208
  }
@@ -12181,10 +12304,10 @@ function resolveRoot(rootFlag, cwd) {
12181
12304
  if (!rootFlag.trim()) {
12182
12305
  return cwd;
12183
12306
  }
12184
- return import_node_path5.default.resolve(cwd, rootFlag);
12307
+ return import_node_path6.default.resolve(cwd, rootFlag);
12185
12308
  }
12186
12309
  function countScenariosInFeatureFile(featurePath) {
12187
- const lines = (0, import_node_fs4.readFileSync)(featurePath, "utf8").split(/\r?\n/).map((line) => line.trim().toLowerCase());
12310
+ const lines = (0, import_node_fs5.readFileSync)(featurePath, "utf8").split(/\r?\n/).map((line) => line.trim().toLowerCase());
12188
12311
  let count = 0;
12189
12312
  for (const line of lines) {
12190
12313
  if (line.startsWith("scenario:") || line.startsWith("scenario outline:")) {
@@ -12196,8 +12319,8 @@ function countScenariosInFeatureFile(featurePath) {
12196
12319
  function buildScenarioStats(rootPath, featurePaths) {
12197
12320
  let scenarios = 0;
12198
12321
  for (const relativePath of featurePaths) {
12199
- const absolutePath = import_node_path5.default.join(rootPath, relativePath);
12200
- if (!(0, import_node_fs4.existsSync)(absolutePath)) {
12322
+ const absolutePath = import_node_path6.default.join(rootPath, relativePath);
12323
+ if (!(0, import_node_fs5.existsSync)(absolutePath)) {
12201
12324
  continue;
12202
12325
  }
12203
12326
  scenarios += countScenariosInFeatureFile(absolutePath);
@@ -12237,7 +12360,7 @@ function deriveFeatureNameFromGherkin(gherkin, fallbackPath) {
12237
12360
  return match[1].trim();
12238
12361
  }
12239
12362
  }
12240
- return import_node_path5.default.basename(fallbackPath, import_node_path5.default.extname(fallbackPath));
12363
+ return import_node_path6.default.basename(fallbackPath, import_node_path6.default.extname(fallbackPath));
12241
12364
  }
12242
12365
  function mapStepStatus(status) {
12243
12366
  const normalized = status.trim().toLowerCase();
@@ -12253,13 +12376,13 @@ function mapStepStatus(status) {
12253
12376
  return StepResult.Blocked;
12254
12377
  }
12255
12378
  function parseCucumberRunsFromArtifact(rootPath, artifactPath) {
12256
- const absolutePath = import_node_path5.default.join(rootPath, artifactPath);
12257
- if (!(0, import_node_fs4.existsSync)(absolutePath)) {
12379
+ const absolutePath = import_node_path6.default.join(rootPath, artifactPath);
12380
+ if (!(0, import_node_fs5.existsSync)(absolutePath)) {
12258
12381
  return [];
12259
12382
  }
12260
12383
  let parsed;
12261
12384
  try {
12262
- parsed = JSON.parse((0, import_node_fs4.readFileSync)(absolutePath, "utf8"));
12385
+ parsed = JSON.parse((0, import_node_fs5.readFileSync)(absolutePath, "utf8"));
12263
12386
  } catch {
12264
12387
  return [];
12265
12388
  }
@@ -12354,15 +12477,15 @@ async function executeUploadFlow(input) {
12354
12477
  const diagnostics = [];
12355
12478
  const orderedFeatures = [...input.featurePaths].sort((a, b) => a.localeCompare(b));
12356
12479
  for (const featurePath of orderedFeatures) {
12357
- const absolutePath = import_node_path5.default.join(input.rootPath, featurePath);
12358
- if (!(0, import_node_fs4.existsSync)(absolutePath)) {
12480
+ const absolutePath = import_node_path6.default.join(input.rootPath, featurePath);
12481
+ if (!(0, import_node_fs5.existsSync)(absolutePath)) {
12359
12482
  featureFailed += 1;
12360
12483
  const diagnostic = diagUploadFeatureFailure(featurePath, "missing feature file");
12361
12484
  errors.push(diagnostic.message);
12362
12485
  diagnostics.push(diagnostic);
12363
12486
  continue;
12364
12487
  }
12365
- const gherkin = (0, import_node_fs4.readFileSync)(absolutePath, "utf8");
12488
+ const gherkin = (0, import_node_fs5.readFileSync)(absolutePath, "utf8");
12366
12489
  const name = deriveFeatureNameFromGherkin(gherkin, featurePath);
12367
12490
  const payload = {
12368
12491
  context: {
@@ -12701,7 +12824,7 @@ function createAutoHandler(deps = {}) {
12701
12824
  stderr: ["ERROR: --output must be either 'plain' or 'json'."]
12702
12825
  };
12703
12826
  }
12704
- if (!(0, import_node_fs4.existsSync)(rootPath)) {
12827
+ if (!(0, import_node_fs5.existsSync)(rootPath)) {
12705
12828
  return {
12706
12829
  exitCode: ExitCode.ValidationError,
12707
12830
  stderr: [`ERROR: Root path does not exist: ${rootPath}`]
@@ -12804,8 +12927,8 @@ function createAutoHandler(deps = {}) {
12804
12927
  }
12805
12928
 
12806
12929
  // src/allure.ts
12807
- var import_node_path10 = __toESM(require("node:path"), 1);
12808
- var import_node_fs9 = require("node:fs");
12930
+ var import_node_path11 = __toESM(require("node:path"), 1);
12931
+ var import_node_fs10 = require("node:fs");
12809
12932
 
12810
12933
  // ../../packages/jira-client/dist/adf.js
12811
12934
  function paragraph(text = "") {
@@ -12851,8 +12974,8 @@ function buildAllureEvidenceCommentAdf(input) {
12851
12974
  }
12852
12975
 
12853
12976
  // ../../packages/jira-client/dist/jiraClient.js
12854
- var import_node_fs5 = require("node:fs");
12855
- var import_node_path6 = __toESM(require("node:path"), 1);
12977
+ var import_node_fs6 = require("node:fs");
12978
+ var import_node_path7 = __toESM(require("node:path"), 1);
12856
12979
  function normalizeSite(site) {
12857
12980
  return site.trim().replace(/\/+$/, "");
12858
12981
  }
@@ -12865,10 +12988,10 @@ async function readError(response) {
12865
12988
  }
12866
12989
  async function uploadIssueAttachment(params) {
12867
12990
  const site = normalizeSite(params.site);
12868
- const filename = import_node_path6.default.basename(params.filePath);
12869
- const fileStat = (0, import_node_fs5.statSync)(params.filePath);
12991
+ const filename = import_node_path7.default.basename(params.filePath);
12992
+ const fileStat = (0, import_node_fs6.statSync)(params.filePath);
12870
12993
  const body = new FormData();
12871
- const fileBuffer = (0, import_node_fs5.readFileSync)(params.filePath);
12994
+ const fileBuffer = (0, import_node_fs6.readFileSync)(params.filePath);
12872
12995
  body.append("file", new Blob([new Uint8Array(fileBuffer)], { type: "application/zip" }), filename);
12873
12996
  const response = await fetch(`${site}/rest/api/3/issue/${encodeURIComponent(params.issueKey)}/attachments`, {
12874
12997
  method: "POST",
@@ -12937,7 +13060,7 @@ async function downloadIssueAttachment(params) {
12937
13060
  throw new Error(`Jira attachment download failed: ${await readError(response)}`);
12938
13061
  }
12939
13062
  const bytes = new Uint8Array(await response.arrayBuffer());
12940
- (0, import_node_fs5.writeFileSync)(params.outputPath, bytes);
13063
+ (0, import_node_fs6.writeFileSync)(params.outputPath, bytes);
12941
13064
  return {
12942
13065
  filename: params.attachment.filename,
12943
13066
  id: params.attachment.id,
@@ -12977,8 +13100,8 @@ async function addIssueComment(params) {
12977
13100
  }
12978
13101
 
12979
13102
  // ../../packages/allure-core/dist/parseAllureZip.js
12980
- var import_node_fs6 = require("node:fs");
12981
- var import_node_path7 = __toESM(require("node:path"), 1);
13103
+ var import_node_fs7 = require("node:fs");
13104
+ var import_node_path8 = __toESM(require("node:path"), 1);
12982
13105
  var import_jszip = __toESM(require_lib3(), 1);
12983
13106
  var SUMMARY_PATH_SUFFIX = "/widgets/summary.json";
12984
13107
  var MISSING_SUMMARY_ERROR = "Could not find widgets/summary.json. Please upload a generated Allure HTML report ZIP, not raw allure-results.";
@@ -13006,13 +13129,13 @@ function parseCounts(summary) {
13006
13129
  };
13007
13130
  }
13008
13131
  async function parseAllureReportZip(zipPath) {
13009
- if (!(0, import_node_fs6.existsSync)(zipPath)) {
13132
+ if (!(0, import_node_fs7.existsSync)(zipPath)) {
13010
13133
  throw new Error(`Allure report ZIP not found: ${zipPath}`);
13011
13134
  }
13012
- if (import_node_path7.default.extname(zipPath).toLowerCase() !== ".zip") {
13135
+ if (import_node_path8.default.extname(zipPath).toLowerCase() !== ".zip") {
13013
13136
  throw new Error("Allure report must be a .zip file.");
13014
13137
  }
13015
- const zip = await import_jszip.default.loadAsync((0, import_node_fs6.readFileSync)(zipPath));
13138
+ const zip = await import_jszip.default.loadAsync((0, import_node_fs7.readFileSync)(zipPath));
13016
13139
  const filePaths = Object.keys(zip.files).filter((entryPath) => !zip.files[entryPath]?.dir);
13017
13140
  const summaryPath = findSummaryPath(filePaths);
13018
13141
  if (!summaryPath) {
@@ -13039,8 +13162,8 @@ async function parseAllureReportZip(zipPath) {
13039
13162
  }
13040
13163
 
13041
13164
  // ../../packages/allure-core/dist/extractAllureZip.js
13042
- var import_node_fs7 = require("node:fs");
13043
- var import_node_path8 = __toESM(require("node:path"), 1);
13165
+ var import_node_fs8 = require("node:fs");
13166
+ var import_node_path9 = __toESM(require("node:path"), 1);
13044
13167
  var import_jszip2 = __toESM(require_lib3(), 1);
13045
13168
  var INDEX_HTML = "index.html";
13046
13169
  var MISSING_INDEX_ERROR = "Could not find index.html. Please provide a generated Allure HTML report ZIP, not raw allure-results.";
@@ -13048,41 +13171,41 @@ function findIndexEntry(paths) {
13048
13171
  return paths.find((entryPath) => entryPath === INDEX_HTML) ?? paths.find((entryPath) => entryPath === `allure-report/${INDEX_HTML}`) ?? paths.find((entryPath) => entryPath.endsWith(`/${INDEX_HTML}`));
13049
13172
  }
13050
13173
  function ensureInsideRoot(rootDir, candidate) {
13051
- const resolved = import_node_path8.default.resolve(rootDir, candidate);
13052
- const rootWithSep = rootDir.endsWith(import_node_path8.default.sep) ? rootDir : rootDir + import_node_path8.default.sep;
13174
+ const resolved = import_node_path9.default.resolve(rootDir, candidate);
13175
+ const rootWithSep = rootDir.endsWith(import_node_path9.default.sep) ? rootDir : rootDir + import_node_path9.default.sep;
13053
13176
  if (resolved !== rootDir && !resolved.startsWith(rootWithSep)) {
13054
13177
  throw new Error(`Refusing to write zip entry outside extract directory: ${candidate}`);
13055
13178
  }
13056
13179
  return resolved;
13057
13180
  }
13058
13181
  async function extractAllureReportZip(zipPath, extractDir) {
13059
- if (!(0, import_node_fs7.existsSync)(zipPath)) {
13182
+ if (!(0, import_node_fs8.existsSync)(zipPath)) {
13060
13183
  throw new Error(`Allure report ZIP not found: ${zipPath}`);
13061
13184
  }
13062
- if (import_node_path8.default.extname(zipPath).toLowerCase() !== ".zip") {
13185
+ if (import_node_path9.default.extname(zipPath).toLowerCase() !== ".zip") {
13063
13186
  throw new Error("Allure report must be a .zip file.");
13064
13187
  }
13065
- const zip = await import_jszip2.default.loadAsync((0, import_node_fs7.readFileSync)(zipPath));
13188
+ const zip = await import_jszip2.default.loadAsync((0, import_node_fs8.readFileSync)(zipPath));
13066
13189
  const entries = Object.entries(zip.files);
13067
13190
  const filePaths = entries.filter(([, file]) => !file.dir).map(([entryPath]) => entryPath);
13068
13191
  const indexEntry = findIndexEntry(filePaths);
13069
13192
  if (!indexEntry) {
13070
13193
  throw new Error(MISSING_INDEX_ERROR);
13071
13194
  }
13072
- const resolvedExtract = import_node_path8.default.resolve(extractDir);
13073
- (0, import_node_fs7.mkdirSync)(resolvedExtract, { recursive: true });
13195
+ const resolvedExtract = import_node_path9.default.resolve(extractDir);
13196
+ (0, import_node_fs8.mkdirSync)(resolvedExtract, { recursive: true });
13074
13197
  for (const [entryPath, file] of entries) {
13075
13198
  const target = ensureInsideRoot(resolvedExtract, entryPath);
13076
13199
  if (file.dir) {
13077
- (0, import_node_fs7.mkdirSync)(target, { recursive: true });
13200
+ (0, import_node_fs8.mkdirSync)(target, { recursive: true });
13078
13201
  continue;
13079
13202
  }
13080
- (0, import_node_fs7.mkdirSync)(import_node_path8.default.dirname(target), { recursive: true });
13203
+ (0, import_node_fs8.mkdirSync)(import_node_path9.default.dirname(target), { recursive: true });
13081
13204
  const buffer = await file.async("nodebuffer");
13082
- (0, import_node_fs7.writeFileSync)(target, buffer);
13205
+ (0, import_node_fs8.writeFileSync)(target, buffer);
13083
13206
  }
13084
- const indexAbsolute = import_node_path8.default.join(resolvedExtract, indexEntry);
13085
- const rootDir = import_node_path8.default.dirname(indexAbsolute);
13207
+ const indexAbsolute = import_node_path9.default.join(resolvedExtract, indexEntry);
13208
+ const rootDir = import_node_path9.default.dirname(indexAbsolute);
13086
13209
  return {
13087
13210
  zipPath,
13088
13211
  extractDir: resolvedExtract,
@@ -13095,11 +13218,11 @@ async function extractAllureReportZip(zipPath, extractDir) {
13095
13218
  var import_jszip3 = __toESM(require_lib3(), 1);
13096
13219
 
13097
13220
  // src/allureOpen.ts
13098
- var import_node_fs8 = require("node:fs");
13221
+ var import_node_fs9 = require("node:fs");
13099
13222
  var import_promises = require("node:fs/promises");
13100
13223
  var import_node_http = __toESM(require("node:http"), 1);
13101
13224
  var import_node_os = require("node:os");
13102
- var import_node_path9 = __toESM(require("node:path"), 1);
13225
+ var import_node_path10 = __toESM(require("node:path"), 1);
13103
13226
  var import_node_child_process2 = require("node:child_process");
13104
13227
  function parseArgs2(args) {
13105
13228
  const positionals = [];
@@ -13168,7 +13291,7 @@ var MIME_TYPES = {
13168
13291
  ".txt": "text/plain; charset=utf-8"
13169
13292
  };
13170
13293
  function contentTypeFor(filePath) {
13171
- return MIME_TYPES[import_node_path9.default.extname(filePath).toLowerCase()] ?? "application/octet-stream";
13294
+ return MIME_TYPES[import_node_path10.default.extname(filePath).toLowerCase()] ?? "application/octet-stream";
13172
13295
  }
13173
13296
  function resolveRequestedPath(rootDir, urlPath) {
13174
13297
  let decoded;
@@ -13177,10 +13300,10 @@ function resolveRequestedPath(rootDir, urlPath) {
13177
13300
  } catch {
13178
13301
  return void 0;
13179
13302
  }
13180
- const normalized = import_node_path9.default.normalize(decoded);
13303
+ const normalized = import_node_path10.default.normalize(decoded);
13181
13304
  const trimmed = normalized.replace(/^[/\\]+/, "");
13182
- const candidate = import_node_path9.default.resolve(rootDir, trimmed);
13183
- const rootWithSep = rootDir.endsWith(import_node_path9.default.sep) ? rootDir : rootDir + import_node_path9.default.sep;
13305
+ const candidate = import_node_path10.default.resolve(rootDir, trimmed);
13306
+ const rootWithSep = rootDir.endsWith(import_node_path10.default.sep) ? rootDir : rootDir + import_node_path10.default.sep;
13184
13307
  if (candidate !== rootDir && !candidate.startsWith(rootWithSep)) {
13185
13308
  return void 0;
13186
13309
  }
@@ -13202,16 +13325,16 @@ function createStaticServerStarter() {
13202
13325
  }
13203
13326
  let target = resolved;
13204
13327
  try {
13205
- const stat = (0, import_node_fs8.statSync)(target);
13328
+ const stat = (0, import_node_fs9.statSync)(target);
13206
13329
  if (stat.isDirectory()) {
13207
- target = import_node_path9.default.join(target, "index.html");
13330
+ target = import_node_path10.default.join(target, "index.html");
13208
13331
  }
13209
13332
  } catch {
13210
13333
  res.statusCode = 404;
13211
13334
  res.end("Not Found");
13212
13335
  return;
13213
13336
  }
13214
- if (!(0, import_node_fs8.existsSync)(target)) {
13337
+ if (!(0, import_node_fs9.existsSync)(target)) {
13215
13338
  res.statusCode = 404;
13216
13339
  res.end("Not Found");
13217
13340
  return;
@@ -13222,7 +13345,7 @@ function createStaticServerStarter() {
13222
13345
  res.end();
13223
13346
  return;
13224
13347
  }
13225
- const stream = (0, import_node_fs8.createReadStream)(target);
13348
+ const stream = (0, import_node_fs9.createReadStream)(target);
13226
13349
  stream.on("error", () => {
13227
13350
  res.statusCode = 500;
13228
13351
  res.end("Internal Server Error");
@@ -13327,7 +13450,7 @@ function createAllureOpenHandler(deps = {}) {
13327
13450
  const cwd = deps.cwd ?? process.cwd();
13328
13451
  const parseZip = deps.parseAllureReportZip ?? parseAllureReportZip;
13329
13452
  const extractZip = deps.extractAllureReportZip ?? extractAllureReportZip;
13330
- const createTempDir = deps.createTempDir ?? (() => (0, import_promises.mkdtemp)(import_node_path9.default.join((0, import_node_os.tmpdir)(), "automatify-allure-open-")));
13453
+ const createTempDir = deps.createTempDir ?? (() => (0, import_promises.mkdtemp)(import_node_path10.default.join((0, import_node_os.tmpdir)(), "automatify-allure-open-")));
13331
13454
  const startServer = deps.startServer ?? createStaticServerStarter();
13332
13455
  const openInBrowser = deps.openInBrowser ?? createOsBrowserOpener();
13333
13456
  const waitForShutdown = deps.waitForShutdown ?? waitForProcessSignals;
@@ -13362,7 +13485,7 @@ function createAllureOpenHandler(deps = {}) {
13362
13485
  };
13363
13486
  }
13364
13487
  const host = parsed.flags["--host"] ?? "127.0.0.1";
13365
- const zipPath = import_node_path9.default.resolve(cwd, zipPathInput);
13488
+ const zipPath = import_node_path10.default.resolve(cwd, zipPathInput);
13366
13489
  let summary;
13367
13490
  try {
13368
13491
  summary = await parseZip(zipPath);
@@ -13372,9 +13495,9 @@ function createAllureOpenHandler(deps = {}) {
13372
13495
  stderr: [`ERROR: ${normalizeError(error)}`]
13373
13496
  };
13374
13497
  }
13375
- const extractDirRaw = userExtractDir ? import_node_path9.default.resolve(cwd, userExtractDir) : await createTempDir();
13498
+ const extractDirRaw = userExtractDir ? import_node_path10.default.resolve(cwd, userExtractDir) : await createTempDir();
13376
13499
  if (dryRun) {
13377
- const indexPath = import_node_path9.default.join(extractDirRaw, "index.html");
13500
+ const indexPath = import_node_path10.default.join(extractDirRaw, "index.html");
13378
13501
  if (useJson) {
13379
13502
  return {
13380
13503
  exitCode: ExitCode.Success,
@@ -13739,8 +13862,8 @@ function createAllureHandler(deps = {}) {
13739
13862
  attachmentId: parsed.flags["--attachment-id"],
13740
13863
  filename: parsed.flags["--filename"]
13741
13864
  });
13742
- const outputDir = import_node_path10.default.resolve(cwd, parsed.flags["--output-dir"] ?? ".");
13743
- const outputPath = import_node_path10.default.join(outputDir, selected.filename);
13865
+ const outputDir = import_node_path11.default.resolve(cwd, parsed.flags["--output-dir"] ?? ".");
13866
+ const outputPath = import_node_path11.default.join(outputDir, selected.filename);
13744
13867
  if (dryRun) {
13745
13868
  if (useJson) {
13746
13869
  return {
@@ -13765,8 +13888,8 @@ function createAllureHandler(deps = {}) {
13765
13888
  ]
13766
13889
  };
13767
13890
  }
13768
- (0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
13769
- if ((0, import_node_fs9.existsSync)(outputPath) && !force) {
13891
+ (0, import_node_fs10.mkdirSync)(outputDir, { recursive: true });
13892
+ if ((0, import_node_fs10.existsSync)(outputPath) && !force) {
13770
13893
  return {
13771
13894
  exitCode: ExitCode.ValidationError,
13772
13895
  stderr: [`ERROR: Output file already exists: ${outputPath}. Pass --force to overwrite.`]
@@ -13815,8 +13938,8 @@ function createAllureHandler(deps = {}) {
13815
13938
  };
13816
13939
  }
13817
13940
  const resolved = resolveJiraConfig(parsed, env, cwd);
13818
- const zipPath = import_node_path10.default.resolve(cwd, zipPathInput);
13819
- const attachmentFilename = import_node_path10.default.basename(zipPath);
13941
+ const zipPath = import_node_path11.default.resolve(cwd, zipPathInput);
13942
+ const attachmentFilename = import_node_path11.default.basename(zipPath);
13820
13943
  if (!resolved.config) {
13821
13944
  return {
13822
13945
  exitCode: ExitCode.ValidationError,
@@ -13914,7 +14037,7 @@ function createAllureHandler(deps = {}) {
13914
14037
  }
13915
14038
 
13916
14039
  // src/auth.ts
13917
- var import_node_fs10 = require("node:fs");
14040
+ var import_node_fs11 = require("node:fs");
13918
14041
  function parseAuthArgs(args, allowLoginFlags) {
13919
14042
  const configArgs = [];
13920
14043
  const unknown = [];
@@ -13980,7 +14103,7 @@ function sourceLabel(source) {
13980
14103
  case "keychain":
13981
14104
  return "macOS Keychain";
13982
14105
  case "secure-store":
13983
- return "Windows DPAPI";
14106
+ return "local secure store";
13984
14107
  case "env":
13985
14108
  return "environment";
13986
14109
  case "file":
@@ -14059,12 +14182,13 @@ function createLoginResponse(args, env, cwd, readStdin, platform, context) {
14059
14182
  ]
14060
14183
  };
14061
14184
  }
14062
- if (parsed.tokenStdin && platform !== "darwin" && platform !== "win32") {
14185
+ const linuxSecureStoreEnabled2 = platform === "linux" && env.TESTOPS_LINUX_SECURE_STORE?.trim() === "1";
14186
+ if (parsed.tokenStdin && platform !== "darwin" && platform !== "win32" && !linuxSecureStoreEnabled2) {
14063
14187
  return {
14064
14188
  exitCode: ExitCode.UsageError,
14065
14189
  stderr: [
14066
- "Local secure secret storage is supported only on macOS and Windows.",
14067
- "On Linux/CI, set JIRA_API_TOKEN in the environment and run auth login without --token-stdin."
14190
+ "Local secure secret storage is enabled by default only on macOS and Windows.",
14191
+ "On Linux/CI, either set TESTOPS_LINUX_SECURE_STORE=1 on a trusted persistent host or keep JIRA_API_TOKEN in the environment and run auth login without --token-stdin."
14068
14192
  ]
14069
14193
  };
14070
14194
  }
@@ -14091,7 +14215,7 @@ function createLoginResponse(args, env, cwd, readStdin, platform, context) {
14091
14215
  }
14092
14216
  completed.push(step.label);
14093
14217
  }
14094
- const tokenSource = stdinToken ? platform === "darwin" ? "macOS Keychain" : "Windows DPAPI" : sourceLabel(
14218
+ const tokenSource = stdinToken ? platform === "darwin" ? "macOS Keychain" : platform === "win32" ? "Windows DPAPI" : "Linux encrypted local store" : sourceLabel(
14095
14219
  current.sources.jiraApiToken === "default" && env.JIRA_API_TOKEN ? "env" : current.sources.jiraApiToken
14096
14220
  );
14097
14221
  return {
@@ -14140,7 +14264,7 @@ function createLogoutResponse(args, env, cwd, context) {
14140
14264
  function createAuthHandler(deps = {}) {
14141
14265
  const env = deps.env ?? process.env;
14142
14266
  const cwd = deps.cwd ?? process.cwd();
14143
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs10.readFileSync)(0, "utf8"));
14267
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs11.readFileSync)(0, "utf8"));
14144
14268
  const platform = deps.platform ?? process.platform;
14145
14269
  return (request, context) => {
14146
14270
  const [subcommand, ...args] = request.args;
@@ -14161,8 +14285,8 @@ function createAuthHandler(deps = {}) {
14161
14285
  }
14162
14286
 
14163
14287
  // src/bdd.ts
14164
- var import_node_fs11 = require("node:fs");
14165
- var import_node_path11 = __toESM(require("node:path"), 1);
14288
+ var import_node_fs12 = require("node:fs");
14289
+ var import_node_path12 = __toESM(require("node:path"), 1);
14166
14290
  var import_yazl = __toESM(require_yazl(), 1);
14167
14291
 
14168
14292
  // src/forgeClient.ts
@@ -14466,7 +14590,7 @@ function parseScenarioFeatureFile(raw, filePath) {
14466
14590
  }
14467
14591
  const scenarioTag = tags.map((tag) => tag.match(SCENARIO_METADATA_PATTERN)).find((match) => Boolean(match));
14468
14592
  const scenarioKeyFromTag = scenarioTag?.[1]?.trim().toUpperCase() ?? "";
14469
- const fileKeyMatch = import_node_path11.default.basename(filePath ?? "").match(/^(SC-\d+)\.feature$/i);
14593
+ const fileKeyMatch = import_node_path12.default.basename(filePath ?? "").match(/^(SC-\d+)\.feature$/i);
14470
14594
  const scenarioKeyFromFile = fileKeyMatch?.[1]?.trim().toUpperCase() ?? "";
14471
14595
  const scenarioKey = scenarioKeyFromTag || scenarioKeyFromFile;
14472
14596
  if (scenarioKeyFromTag && scenarioKeyFromFile && scenarioKeyFromTag !== scenarioKeyFromFile) {
@@ -14586,7 +14710,7 @@ function toJsonExportManifestItems(items) {
14586
14710
  async function defaultCreateZipArchive(archivePath, entries) {
14587
14711
  await new Promise((resolve, reject) => {
14588
14712
  const zip = new import_yazl.ZipFile();
14589
- const output = zip.outputStream.pipe((0, import_node_fs11.createWriteStream)(archivePath));
14713
+ const output = zip.outputStream.pipe((0, import_node_fs12.createWriteStream)(archivePath));
14590
14714
  output.on("close", () => resolve());
14591
14715
  output.on("error", reject);
14592
14716
  zip.outputStream.on("error", reject);
@@ -14616,10 +14740,10 @@ function missingProjectResponse() {
14616
14740
  function createBddHandler(deps = {}) {
14617
14741
  const cwd = deps.cwd ?? process.cwd();
14618
14742
  const env = deps.env ?? process.env;
14619
- const mkdir = deps.mkdir ?? ((targetPath, options) => (0, import_node_fs11.mkdirSync)(targetPath, options));
14620
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs11.readFileSync)(filePath, "utf8"));
14621
- const readDir = deps.readDir ?? ((dirPath) => (0, import_node_fs11.readdirSync)(dirPath));
14622
- const writeFile = deps.writeFile ?? ((filePath, content) => (0, import_node_fs11.writeFileSync)(filePath, content, "utf8"));
14743
+ const mkdir = deps.mkdir ?? ((targetPath, options) => (0, import_node_fs12.mkdirSync)(targetPath, options));
14744
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs12.readFileSync)(filePath, "utf8"));
14745
+ const readDir = deps.readDir ?? ((dirPath) => (0, import_node_fs12.readdirSync)(dirPath));
14746
+ const writeFile = deps.writeFile ?? ((filePath, content) => (0, import_node_fs12.writeFileSync)(filePath, content, "utf8"));
14623
14747
  const createZipArchive = deps.createZipArchive ?? defaultCreateZipArchive;
14624
14748
  return async (request, context) => {
14625
14749
  const [subcommand, ...restArgs] = request.args;
@@ -14663,7 +14787,7 @@ function createBddHandler(deps = {}) {
14663
14787
  stderr: ["ERROR: Missing required --output-dir for testops bdd features export."]
14664
14788
  };
14665
14789
  }
14666
- const outputDir = import_node_path11.default.resolve(cwd, outputDirFlag);
14790
+ const outputDir = import_node_path12.default.resolve(cwd, outputDirFlag);
14667
14791
  try {
14668
14792
  const result = await context.invokeForgeContract("listBddFeatures", {
14669
14793
  context: {
@@ -14701,7 +14825,7 @@ function createBddHandler(deps = {}) {
14701
14825
  `
14702
14826
  }));
14703
14827
  for (let index = 0; index < features.length; index += 1) {
14704
- writeFile(import_node_path11.default.join(outputDir, fileNames[index]), exportEntries[index].content);
14828
+ writeFile(import_node_path12.default.join(outputDir, fileNames[index]), exportEntries[index].content);
14705
14829
  }
14706
14830
  const manifest = {
14707
14831
  action: "bdd-features-export",
@@ -14714,7 +14838,7 @@ function createBddHandler(deps = {}) {
14714
14838
  };
14715
14839
  const manifestContent = `${JSON.stringify(manifest, null, 2)}
14716
14840
  `;
14717
- writeFile(import_node_path11.default.join(outputDir, "manifest.json"), manifestContent);
14841
+ writeFile(import_node_path12.default.join(outputDir, "manifest.json"), manifestContent);
14718
14842
  const archivePath = `${outputDir}.zip`;
14719
14843
  if (zipRequested) {
14720
14844
  await createZipArchive(archivePath, [
@@ -14784,7 +14908,7 @@ function createBddHandler(deps = {}) {
14784
14908
  stderr: ["ERROR: Missing required --output-dir for testops bdd scenarios export."]
14785
14909
  };
14786
14910
  }
14787
- const outputDir = import_node_path11.default.resolve(cwd, outputDirFlag);
14911
+ const outputDir = import_node_path12.default.resolve(cwd, outputDirFlag);
14788
14912
  const zipRequested = parsed.boolFlags.has("--zip");
14789
14913
  try {
14790
14914
  const scenariosResult = await context.invokeForgeContract("listBddScenarios", {
@@ -14821,7 +14945,7 @@ function createBddHandler(deps = {}) {
14821
14945
  const fileName = scenarioToExportFileName(scenario);
14822
14946
  const fileContent = `${scenarioToFeatureLines(scenario).join("\n")}
14823
14947
  `;
14824
- writeFile(import_node_path11.default.join(outputDir, fileName), fileContent);
14948
+ writeFile(import_node_path12.default.join(outputDir, fileName), fileContent);
14825
14949
  return {
14826
14950
  scenario,
14827
14951
  fileName,
@@ -14847,7 +14971,7 @@ function createBddHandler(deps = {}) {
14847
14971
  };
14848
14972
  const manifestContent = `${JSON.stringify(manifest, null, 2)}
14849
14973
  `;
14850
- writeFile(import_node_path11.default.join(outputDir, "manifest.json"), manifestContent);
14974
+ writeFile(import_node_path12.default.join(outputDir, "manifest.json"), manifestContent);
14851
14975
  const archivePath = `${outputDir}.zip`;
14852
14976
  if (zipRequested) {
14853
14977
  await createZipArchive(archivePath, [
@@ -14895,8 +15019,8 @@ function createBddHandler(deps = {}) {
14895
15019
  }
14896
15020
  }
14897
15021
  if (nested === "import") {
14898
- const sourceFile = parsed.flags["--file"] ? import_node_path11.default.resolve(cwd, parsed.flags["--file"]) : "";
14899
- const inputDir = parsed.flags["--input-dir"] ? import_node_path11.default.resolve(cwd, parsed.flags["--input-dir"]) : "";
15022
+ const sourceFile = parsed.flags["--file"] ? import_node_path12.default.resolve(cwd, parsed.flags["--file"]) : "";
15023
+ const inputDir = parsed.flags["--input-dir"] ? import_node_path12.default.resolve(cwd, parsed.flags["--input-dir"]) : "";
14900
15024
  const dryRun = parsed.boolFlags.has("--dry-run");
14901
15025
  if (!sourceFile && !inputDir) {
14902
15026
  return {
@@ -14905,7 +15029,7 @@ function createBddHandler(deps = {}) {
14905
15029
  };
14906
15030
  }
14907
15031
  try {
14908
- const scenarioFiles = sourceFile ? [sourceFile] : readDir(inputDir).filter((entry) => entry.toLowerCase().endsWith(".feature")).map((entry) => import_node_path11.default.join(inputDir, entry)).sort((left, right) => left.localeCompare(right));
15032
+ const scenarioFiles = sourceFile ? [sourceFile] : readDir(inputDir).filter((entry) => entry.toLowerCase().endsWith(".feature")).map((entry) => import_node_path12.default.join(inputDir, entry)).sort((left, right) => left.localeCompare(right));
14909
15033
  if (scenarioFiles.length === 0) {
14910
15034
  return {
14911
15035
  exitCode: ExitCode.ValidationError,
@@ -15215,6 +15339,138 @@ function createBddHandler(deps = {}) {
15215
15339
  };
15216
15340
  }
15217
15341
 
15342
+ // src/bddCommand.ts
15343
+ var CONFIG_VALUE_FLAGS = /* @__PURE__ */ new Set([
15344
+ "--config",
15345
+ "--base-url",
15346
+ "--project-key",
15347
+ "--issue-key",
15348
+ "--auth-mode",
15349
+ "--jira-email",
15350
+ "--jira-api-token"
15351
+ ]);
15352
+ function parseListArgs(args) {
15353
+ let json = false;
15354
+ const configArgs = [];
15355
+ const invalid = [];
15356
+ for (let index = 0; index < args.length; index += 1) {
15357
+ const token = args[index];
15358
+ if (token === "--json") {
15359
+ json = true;
15360
+ continue;
15361
+ }
15362
+ if (!CONFIG_VALUE_FLAGS.has(token)) {
15363
+ invalid.push(token);
15364
+ continue;
15365
+ }
15366
+ const value = args[index + 1];
15367
+ if (!value || value.startsWith("--")) {
15368
+ invalid.push(token);
15369
+ continue;
15370
+ }
15371
+ configArgs.push(token, value);
15372
+ index += 1;
15373
+ }
15374
+ return { json, configArgs, invalid };
15375
+ }
15376
+ function normalizeError4(error) {
15377
+ if (error instanceof ForgeClientError) {
15378
+ return `${error.code}: ${error.message}`;
15379
+ }
15380
+ if (error instanceof Error) {
15381
+ return error.message;
15382
+ }
15383
+ return "Unknown BDD scenario list transport error.";
15384
+ }
15385
+ function scenarioToJson(scenario) {
15386
+ return {
15387
+ id: scenario.id,
15388
+ key: scenario.key ?? null,
15389
+ featureId: scenario.featureId,
15390
+ featureName: scenario.featureName,
15391
+ name: scenario.name,
15392
+ tags: [...scenario.tags],
15393
+ linkedIssueKeys: [...scenario.linkedIssueKeys],
15394
+ stepCount: scenario.steps.length,
15395
+ createdAt: scenario.createdAt,
15396
+ updatedAt: scenario.updatedAt
15397
+ };
15398
+ }
15399
+ function scenarioToLine(scenario) {
15400
+ const selector = scenario.key ?? scenario.id;
15401
+ const issues = scenario.linkedIssueKeys.length > 0 ? scenario.linkedIssueKeys.join(", ") : "none";
15402
+ return `${selector} | ${scenario.name} | Feature: ${scenario.featureName} | Steps: ${scenario.steps.length} | Issues: ${issues}`;
15403
+ }
15404
+ function createBddCommandHandler(deps = {}) {
15405
+ const cwd = deps.cwd ?? process.cwd();
15406
+ const env = deps.env ?? process.env;
15407
+ const delegate = deps.delegate ?? createBddHandler({ cwd, env });
15408
+ return async (request, context) => {
15409
+ if (request.args[0] !== "scenarios" || request.args[1] !== "list") {
15410
+ return delegate(request, context);
15411
+ }
15412
+ const parsed = parseListArgs(request.args.slice(2));
15413
+ if (parsed.invalid.length > 0) {
15414
+ return {
15415
+ exitCode: ExitCode.UsageError,
15416
+ stderr: [`ERROR: Unknown or invalid arguments: ${parsed.invalid.join(", ")}`]
15417
+ };
15418
+ }
15419
+ const config = resolveCliConfig(parsed.configArgs, env, cwd).values;
15420
+ if (!config.projectKey) {
15421
+ return {
15422
+ exitCode: ExitCode.ValidationError,
15423
+ stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
15424
+ };
15425
+ }
15426
+ try {
15427
+ const result = await context.invokeForgeContract("listBddScenarios", {
15428
+ context: {
15429
+ projectKey: config.projectKey,
15430
+ issueKey: config.issueKey || void 0
15431
+ }
15432
+ });
15433
+ if (!result.ok) {
15434
+ return {
15435
+ exitCode: ExitCode.RemoteError,
15436
+ stderr: [`ERROR: ${result.error.code}: ${result.error.message}`]
15437
+ };
15438
+ }
15439
+ const scenarios = result.data;
15440
+ if (parsed.json) {
15441
+ return {
15442
+ exitCode: ExitCode.Success,
15443
+ stdout: toJsonLine({
15444
+ action: "bdd-scenarios-list",
15445
+ projectKey: config.projectKey,
15446
+ issueKey: config.issueKey || null,
15447
+ count: scenarios.length,
15448
+ items: scenarios.map(scenarioToJson)
15449
+ })
15450
+ };
15451
+ }
15452
+ if (scenarios.length === 0) {
15453
+ return {
15454
+ exitCode: ExitCode.Success,
15455
+ stdout: [`No BDD scenarios found for project ${config.projectKey}.`]
15456
+ };
15457
+ }
15458
+ return {
15459
+ exitCode: ExitCode.Success,
15460
+ stdout: [
15461
+ `BDD scenarios (${scenarios.length}) for project ${config.projectKey}:`,
15462
+ ...scenarios.map(scenarioToLine)
15463
+ ]
15464
+ };
15465
+ } catch (error) {
15466
+ return {
15467
+ exitCode: ExitCode.TransportError,
15468
+ stderr: [`ERROR: ${normalizeError4(error)}`]
15469
+ };
15470
+ }
15471
+ };
15472
+ }
15473
+
15218
15474
  // src/cases.ts
15219
15475
  var CONFIG_FLAGS4 = /* @__PURE__ */ new Set([
15220
15476
  "--config",
@@ -15263,7 +15519,7 @@ function pickConfigArgs4(parsed) {
15263
15519
  }
15264
15520
  return args;
15265
15521
  }
15266
- function normalizeError4(error) {
15522
+ function normalizeError5(error) {
15267
15523
  if (error instanceof ForgeClientError) {
15268
15524
  return `${error.code}: ${error.message}`;
15269
15525
  }
@@ -15441,7 +15697,7 @@ function createCasesHandler(deps = {}) {
15441
15697
  } catch (error) {
15442
15698
  return {
15443
15699
  exitCode: ExitCode.TransportError,
15444
- stderr: [`ERROR: ${normalizeError4(error)}`]
15700
+ stderr: [`ERROR: ${normalizeError5(error)}`]
15445
15701
  };
15446
15702
  }
15447
15703
  }
@@ -15483,7 +15739,7 @@ function createCasesHandler(deps = {}) {
15483
15739
  } catch (error) {
15484
15740
  return {
15485
15741
  exitCode: ExitCode.TransportError,
15486
- stderr: [`ERROR: ${normalizeError4(error)}`]
15742
+ stderr: [`ERROR: ${normalizeError5(error)}`]
15487
15743
  };
15488
15744
  }
15489
15745
  }
@@ -15546,7 +15802,7 @@ function createCasesHandler(deps = {}) {
15546
15802
  } catch (error) {
15547
15803
  return {
15548
15804
  exitCode: ExitCode.TransportError,
15549
- stderr: [`ERROR: ${normalizeError4(error)}`]
15805
+ stderr: [`ERROR: ${normalizeError5(error)}`]
15550
15806
  };
15551
15807
  }
15552
15808
  }
@@ -15609,7 +15865,7 @@ function createCasesHandler(deps = {}) {
15609
15865
  } catch (error) {
15610
15866
  return {
15611
15867
  exitCode: ExitCode.TransportError,
15612
- stderr: [`ERROR: ${normalizeError4(error)}`]
15868
+ stderr: [`ERROR: ${normalizeError5(error)}`]
15613
15869
  };
15614
15870
  }
15615
15871
  }
@@ -15954,8 +16210,8 @@ function createDoctorHandler(deps = {}) {
15954
16210
  }
15955
16211
 
15956
16212
  // src/ingestFeature.ts
15957
- var import_node_fs12 = require("node:fs");
15958
- var import_node_path12 = __toESM(require("node:path"), 1);
16213
+ var import_node_fs13 = require("node:fs");
16214
+ var import_node_path13 = __toESM(require("node:path"), 1);
15959
16215
  var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
15960
16216
  "--config",
15961
16217
  "--base-url",
@@ -16031,7 +16287,7 @@ function summarizeSuccess(result) {
16031
16287
  }
16032
16288
  return lines;
16033
16289
  }
16034
- function normalizeError5(error) {
16290
+ function normalizeError6(error) {
16035
16291
  if (error instanceof ForgeClientError) {
16036
16292
  return `${error.code}: ${error.message}`;
16037
16293
  }
@@ -16041,8 +16297,8 @@ function normalizeError5(error) {
16041
16297
  return "Unknown ingest error.";
16042
16298
  }
16043
16299
  function createIngestFeatureHandler(deps = {}) {
16044
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs12.readFileSync)(filePath, "utf8"));
16045
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8"));
16300
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs13.readFileSync)(filePath, "utf8"));
16301
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs13.readFileSync)(0, "utf8"));
16046
16302
  const cwd = deps.cwd ?? process.cwd();
16047
16303
  const env = deps.env ?? process.env;
16048
16304
  return async (request, context) => {
@@ -16052,7 +16308,7 @@ function createIngestFeatureHandler(deps = {}) {
16052
16308
  const config = resolveCliConfig(configArgs, env, cwd);
16053
16309
  const projectKey = config.values.projectKey;
16054
16310
  const issueKey = config.values.issueKey;
16055
- const sourceFile = parsed.flags["--file"] ? import_node_path12.default.resolve(cwd, parsed.flags["--file"]) : "";
16311
+ const sourceFile = parsed.flags["--file"] ? import_node_path13.default.resolve(cwd, parsed.flags["--file"]) : "";
16056
16312
  const useStdin = parsed.boolFlags.has("--stdin");
16057
16313
  const useJson = parsed.boolFlags.has("--json");
16058
16314
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -16068,7 +16324,7 @@ function createIngestFeatureHandler(deps = {}) {
16068
16324
  } catch (error) {
16069
16325
  return {
16070
16326
  exitCode: ExitCode.InternalError,
16071
- stderr: [`ERROR: Failed to read feature source: ${normalizeError5(error)}`]
16327
+ stderr: [`ERROR: Failed to read feature source: ${normalizeError6(error)}`]
16072
16328
  };
16073
16329
  }
16074
16330
  const name = (parsed.flags["--name"] ?? deriveNameFromGherkin(gherkin)).trim();
@@ -16159,14 +16415,14 @@ function createIngestFeatureHandler(deps = {}) {
16159
16415
  status: "failed",
16160
16416
  featureName: name,
16161
16417
  source: useStdin ? "stdin" : sourceFile,
16162
- errorMessage: normalizeError5(error)
16418
+ errorMessage: normalizeError6(error)
16163
16419
  })
16164
16420
  };
16165
16421
  }
16166
16422
  return {
16167
16423
  exitCode: ExitCode.TransportError,
16168
16424
  stdout: ["Feature ingestion: FAILED", `Feature: ${name}`, `Source: ${useStdin ? "stdin" : sourceFile}`],
16169
- stderr: [`ERROR: ${normalizeError5(error)}`]
16425
+ stderr: [`ERROR: ${normalizeError6(error)}`]
16170
16426
  };
16171
16427
  }
16172
16428
  };
@@ -16221,7 +16477,7 @@ function pickConfigArgs7(parsed) {
16221
16477
  }
16222
16478
  return args;
16223
16479
  }
16224
- function normalizeError6(error) {
16480
+ function normalizeError7(error) {
16225
16481
  if (error instanceof ForgeClientError) {
16226
16482
  return `${error.code}: ${error.message}`;
16227
16483
  }
@@ -16364,7 +16620,7 @@ function createProfilesHandler(deps = {}) {
16364
16620
  } catch (error) {
16365
16621
  return {
16366
16622
  exitCode: ExitCode.TransportError,
16367
- stderr: [`ERROR: ${normalizeError6(error)}`]
16623
+ stderr: [`ERROR: ${normalizeError7(error)}`]
16368
16624
  };
16369
16625
  }
16370
16626
  }
@@ -16450,7 +16706,7 @@ function createProfilesHandler(deps = {}) {
16450
16706
  } catch (error) {
16451
16707
  return {
16452
16708
  exitCode: ExitCode.TransportError,
16453
- stderr: [`ERROR: ${normalizeError6(error)}`]
16709
+ stderr: [`ERROR: ${normalizeError7(error)}`]
16454
16710
  };
16455
16711
  }
16456
16712
  }
@@ -16548,7 +16804,7 @@ function createProfilesHandler(deps = {}) {
16548
16804
  } catch (error) {
16549
16805
  return {
16550
16806
  exitCode: ExitCode.TransportError,
16551
- stderr: [`ERROR: ${normalizeError6(error)}`]
16807
+ stderr: [`ERROR: ${normalizeError7(error)}`]
16552
16808
  };
16553
16809
  }
16554
16810
  }
@@ -16659,7 +16915,7 @@ function createProfilesHandler(deps = {}) {
16659
16915
  } catch (error) {
16660
16916
  return {
16661
16917
  exitCode: ExitCode.TransportError,
16662
- stderr: [`ERROR: ${normalizeError6(error)}`]
16918
+ stderr: [`ERROR: ${normalizeError7(error)}`]
16663
16919
  };
16664
16920
  }
16665
16921
  }
@@ -16671,8 +16927,8 @@ function createProfilesHandler(deps = {}) {
16671
16927
  }
16672
16928
 
16673
16929
  // src/runUpload.ts
16674
- var import_node_fs13 = require("node:fs");
16675
- var import_node_path13 = __toESM(require("node:path"), 1);
16930
+ var import_node_fs14 = require("node:fs");
16931
+ var import_node_path14 = __toESM(require("node:path"), 1);
16676
16932
  var STEP_RESULTS = [
16677
16933
  StepResult.Passed,
16678
16934
  StepResult.Failed,
@@ -16734,7 +16990,7 @@ function pickConfigArgs8(parsed) {
16734
16990
  }
16735
16991
  return configArgs;
16736
16992
  }
16737
- function normalizeError7(error) {
16993
+ function normalizeError8(error) {
16738
16994
  if (error instanceof ForgeClientError) {
16739
16995
  return `${error.code}: ${error.message}`;
16740
16996
  }
@@ -16813,8 +17069,8 @@ function summarizeRunResult(result) {
16813
17069
  return lines;
16814
17070
  }
16815
17071
  function createRunUploadHandler(deps = {}) {
16816
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs13.readFileSync)(filePath, "utf8"));
16817
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs13.readFileSync)(0, "utf8"));
17072
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs14.readFileSync)(filePath, "utf8"));
17073
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs14.readFileSync)(0, "utf8"));
16818
17074
  const cwd = deps.cwd ?? process.cwd();
16819
17075
  const env = deps.env ?? process.env;
16820
17076
  return async (request, context) => {
@@ -16825,7 +17081,7 @@ function createRunUploadHandler(deps = {}) {
16825
17081
  const projectKey = config.values.projectKey;
16826
17082
  const issueKey = config.values.issueKey;
16827
17083
  const testCaseKey = parsed.flags["--test-case-key"]?.trim().toUpperCase() ?? "";
16828
- const sourceFile = parsed.flags["--file"] ? import_node_path13.default.resolve(cwd, parsed.flags["--file"]) : "";
17084
+ const sourceFile = parsed.flags["--file"] ? import_node_path14.default.resolve(cwd, parsed.flags["--file"]) : "";
16829
17085
  const useStdin = parsed.boolFlags.has("--stdin");
16830
17086
  const useJson = parsed.boolFlags.has("--json");
16831
17087
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -16853,7 +17109,7 @@ function createRunUploadHandler(deps = {}) {
16853
17109
  } catch (error) {
16854
17110
  return {
16855
17111
  exitCode: ExitCode.InternalError,
16856
- stderr: [`ERROR: Failed to read run payload source: ${normalizeError7(error)}`]
17112
+ stderr: [`ERROR: Failed to read run payload source: ${normalizeError8(error)}`]
16857
17113
  };
16858
17114
  }
16859
17115
  const payloadFromSource = parseRunPayload(raw);
@@ -16883,7 +17139,7 @@ function createRunUploadHandler(deps = {}) {
16883
17139
  } catch (error) {
16884
17140
  return {
16885
17141
  exitCode: ExitCode.TransportError,
16886
- stderr: [`ERROR: ${normalizeError7(error)}`]
17142
+ stderr: [`ERROR: ${normalizeError8(error)}`]
16887
17143
  };
16888
17144
  }
16889
17145
  }
@@ -16963,7 +17219,7 @@ function createRunUploadHandler(deps = {}) {
16963
17219
  featureName: runInput.featureName,
16964
17220
  scenarioName: runInput.scenarioName,
16965
17221
  executedAt: runInput.executedAt,
16966
- errorMessage: normalizeError7(error)
17222
+ errorMessage: normalizeError8(error)
16967
17223
  })
16968
17224
  };
16969
17225
  }
@@ -16975,7 +17231,7 @@ function createRunUploadHandler(deps = {}) {
16975
17231
  `Scenario: ${runInput.scenarioName}`,
16976
17232
  `ExecutedAt: ${runInput.executedAt}`
16977
17233
  ],
16978
- stderr: [`ERROR: ${normalizeError7(error)}`]
17234
+ stderr: [`ERROR: ${normalizeError8(error)}`]
16979
17235
  };
16980
17236
  }
16981
17237
  };
@@ -17029,7 +17285,7 @@ function pickConfigArgs9(parsed) {
17029
17285
  }
17030
17286
  return args;
17031
17287
  }
17032
- function normalizeError8(error) {
17288
+ function normalizeError9(error) {
17033
17289
  if (error instanceof ForgeClientError) {
17034
17290
  return `${error.code}: ${error.message}`;
17035
17291
  }
@@ -17147,7 +17403,7 @@ function createRunsHandler(deps = {}) {
17147
17403
  } catch (error) {
17148
17404
  return {
17149
17405
  exitCode: ExitCode.TransportError,
17150
- stderr: [`ERROR: ${normalizeError8(error)}`]
17406
+ stderr: [`ERROR: ${normalizeError9(error)}`]
17151
17407
  };
17152
17408
  }
17153
17409
  }
@@ -17159,16 +17415,16 @@ function createRunsHandler(deps = {}) {
17159
17415
  }
17160
17416
 
17161
17417
  // src/setup.ts
17162
- var import_node_fs15 = require("node:fs");
17163
- var import_node_path15 = __toESM(require("node:path"), 1);
17418
+ var import_node_fs16 = require("node:fs");
17419
+ var import_node_path16 = __toESM(require("node:path"), 1);
17164
17420
 
17165
17421
  // src/githubSetupProvider.ts
17166
17422
  var import_node_child_process4 = require("node:child_process");
17167
17423
 
17168
17424
  // src/setupPlan.ts
17169
- var import_node_crypto = require("node:crypto");
17170
- var import_node_fs14 = require("node:fs");
17171
- var import_node_path14 = __toESM(require("node:path"), 1);
17425
+ var import_node_crypto2 = require("node:crypto");
17426
+ var import_node_fs15 = require("node:fs");
17427
+ var import_node_path15 = __toESM(require("node:path"), 1);
17172
17428
  var SETUP_PLAN_SCHEMA_VERSION = "automatify.testops.setup/v1";
17173
17429
  var SETUP_PLAN_KIND = "AutomatifyTestOpsSetupPlan";
17174
17430
  var ACTIONS = [
@@ -17192,7 +17448,7 @@ var ACTIONS = [
17192
17448
  }
17193
17449
  ];
17194
17450
  function sha256(value) {
17195
- return (0, import_node_crypto.createHash)("sha256").update(value, "utf8").digest("hex");
17451
+ return (0, import_node_crypto2.createHash)("sha256").update(value, "utf8").digest("hex");
17196
17452
  }
17197
17453
  function sortJsonValue(value) {
17198
17454
  if (Array.isArray(value)) {
@@ -17237,8 +17493,8 @@ function normalizeRepoPart(value, field) {
17237
17493
  return normalized;
17238
17494
  }
17239
17495
  function normalizeWorkflowPath(value) {
17240
- const normalized = import_node_path14.default.posix.normalize(requireNonEmpty(value, "workflow path").replaceAll("\\", "/"));
17241
- if (import_node_path14.default.posix.isAbsolute(normalized) || normalized.startsWith("../") || !normalized.startsWith(".github/workflows/") || !/\.ya?ml$/i.test(normalized)) {
17496
+ const normalized = import_node_path15.default.posix.normalize(requireNonEmpty(value, "workflow path").replaceAll("\\", "/"));
17497
+ if (import_node_path15.default.posix.isAbsolute(normalized) || normalized.startsWith("../") || !normalized.startsWith(".github/workflows/") || !/\.ya?ml$/i.test(normalized)) {
17242
17498
  throw new Error("workflow path must be a relative .github/workflows/*.yml or *.yaml path without '..'.");
17243
17499
  }
17244
17500
  return normalized;
@@ -17263,7 +17519,7 @@ function buildGitHubSetupPlan(input) {
17263
17519
  if (endpointSecretName === tokenSecretName) {
17264
17520
  throw new Error("callback endpoint and auth token secret names must be different.");
17265
17521
  }
17266
- const workflowContent = (0, import_node_fs14.readFileSync)(input.workflowSourcePath, "utf8");
17522
+ const workflowContent = (0, import_node_fs15.readFileSync)(input.workflowSourcePath, "utf8");
17267
17523
  if (!workflowContent.trim()) {
17268
17524
  throw new Error("workflow source file is empty.");
17269
17525
  }
@@ -17296,7 +17552,7 @@ function buildGitHubSetupPlan(input) {
17296
17552
  workflowId,
17297
17553
  workflowFile: {
17298
17554
  path: workflowPath,
17299
- sourcePath: import_node_path14.default.resolve(input.workflowSourcePath),
17555
+ sourcePath: import_node_path15.default.resolve(input.workflowSourcePath),
17300
17556
  sha256: sha256(workflowContent)
17301
17557
  },
17302
17558
  requiredSecrets: [
@@ -17897,8 +18153,8 @@ var AzureDevOpsSetupProvider = class {
17897
18153
  };
17898
18154
 
17899
18155
  // src/azureDevOpsSetupPlan.ts
17900
- var import_node_crypto2 = require("node:crypto");
17901
- var digest = (v) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(v)).digest("hex")}`;
18156
+ var import_node_crypto3 = require("node:crypto");
18157
+ var digest = (v) => `sha256:${(0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(v)).digest("hex")}`;
17902
18158
  var required = (value, field) => {
17903
18159
  const result = value.trim();
17904
18160
  if (!result) throw new Error(`${field} is required.`);
@@ -18206,10 +18462,10 @@ function errorCodeForError(error, fallback) {
18206
18462
  return error instanceof SetupCommandError ? error.cliCode : fallback;
18207
18463
  }
18208
18464
  function loadPlan(planPath, cwd) {
18209
- const absolute = import_node_path15.default.resolve(cwd, planPath);
18465
+ const absolute = import_node_path16.default.resolve(cwd, planPath);
18210
18466
  let parsed;
18211
18467
  try {
18212
- parsed = JSON.parse((0, import_node_fs15.readFileSync)(absolute, "utf8"));
18468
+ parsed = JSON.parse((0, import_node_fs16.readFileSync)(absolute, "utf8"));
18213
18469
  } catch (error) {
18214
18470
  throw new SetupCommandError(
18215
18471
  "PLAN_READ_ERROR",
@@ -18434,9 +18690,9 @@ function rollbackFor(plan, actionId) {
18434
18690
  return plan.rollback.find((item) => item.actionId === actionId)?.strategy ?? "Review the plan rollback section.";
18435
18691
  }
18436
18692
  function resolveWorkflowTarget(plan, repoRoot) {
18437
- const resolvedRoot = import_node_path15.default.resolve(repoRoot);
18438
- const target = import_node_path15.default.resolve(resolvedRoot, plan.github.workflowFile.path);
18439
- if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${import_node_path15.default.sep}`)) {
18693
+ const resolvedRoot = import_node_path16.default.resolve(repoRoot);
18694
+ const target = import_node_path16.default.resolve(resolvedRoot, plan.github.workflowFile.path);
18695
+ if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${import_node_path16.default.sep}`)) {
18440
18696
  throw new SetupCommandError(
18441
18697
  "WORKFLOW_PATH_ERROR",
18442
18698
  "Workflow target escapes the approved repository root.",
@@ -18448,7 +18704,7 @@ function resolveWorkflowTarget(plan, repoRoot) {
18448
18704
  function applyWorkflowFile(plan, repoRoot, dryRun) {
18449
18705
  const action = plan.actions.find((item) => item.scope === "workflow-file");
18450
18706
  const target = resolveWorkflowTarget(plan, repoRoot);
18451
- const sourceContent = (0, import_node_fs15.readFileSync)(plan.github.workflowFile.sourcePath, "utf8");
18707
+ const sourceContent = (0, import_node_fs16.readFileSync)(plan.github.workflowFile.sourcePath, "utf8");
18452
18708
  if (hashSetupContent(sourceContent) !== plan.github.workflowFile.sha256) {
18453
18709
  throw new SetupCommandError(
18454
18710
  "WORKFLOW_SOURCE_CHANGED",
@@ -18456,8 +18712,8 @@ function applyWorkflowFile(plan, repoRoot, dryRun) {
18456
18712
  ExitCode.ValidationError
18457
18713
  );
18458
18714
  }
18459
- const existed = (0, import_node_fs15.existsSync)(target);
18460
- const previousContent = existed ? (0, import_node_fs15.readFileSync)(target, "utf8") : void 0;
18715
+ const existed = (0, import_node_fs16.existsSync)(target);
18716
+ const previousContent = existed ? (0, import_node_fs16.readFileSync)(target, "utf8") : void 0;
18461
18717
  const matches = previousContent !== void 0 && hashSetupContent(previousContent) === plan.github.workflowFile.sha256;
18462
18718
  if (matches) {
18463
18719
  return {
@@ -18469,8 +18725,8 @@ function applyWorkflowFile(plan, repoRoot, dryRun) {
18469
18725
  };
18470
18726
  }
18471
18727
  if (!dryRun) {
18472
- (0, import_node_fs15.mkdirSync)(import_node_path15.default.dirname(target), { recursive: true });
18473
- (0, import_node_fs15.writeFileSync)(target, sourceContent, { encoding: "utf8", mode: 420 });
18728
+ (0, import_node_fs16.mkdirSync)(import_node_path16.default.dirname(target), { recursive: true });
18729
+ (0, import_node_fs16.writeFileSync)(target, sourceContent, { encoding: "utf8", mode: 420 });
18474
18730
  }
18475
18731
  return {
18476
18732
  id: action.id,
@@ -18640,14 +18896,14 @@ async function inspectPlan(plan, repoRoot, secrets, context, deps) {
18640
18896
  const checks = [];
18641
18897
  let forgeTransportFailure = false;
18642
18898
  const target = resolveWorkflowTarget(plan, repoRoot);
18643
- if (!(0, import_node_fs15.existsSync)(target)) {
18899
+ if (!(0, import_node_fs16.existsSync)(target)) {
18644
18900
  checks.push({
18645
18901
  id: "workflow-local",
18646
18902
  status: "fail",
18647
18903
  message: `Local workflow file ${plan.github.workflowFile.path} does not exist.`
18648
18904
  });
18649
18905
  } else {
18650
- const localHash = hashSetupContent((0, import_node_fs15.readFileSync)(target, "utf8"));
18906
+ const localHash = hashSetupContent((0, import_node_fs16.readFileSync)(target, "utf8"));
18651
18907
  checks.push({
18652
18908
  id: "workflow-local",
18653
18909
  status: localHash === plan.github.workflowFile.sha256 ? "pass" : "fail",
@@ -18783,7 +19039,7 @@ function planCommand(args, deps) {
18783
19039
  ref: parsed.flags["--ref"] ?? "main",
18784
19040
  workflowId: parsed.flags["--workflow-id"] ?? "scenario-dispatch.yml",
18785
19041
  workflowPath: parsed.flags["--workflow-path"] ?? ".github/workflows/scenario-dispatch.yml",
18786
- workflowSourcePath: import_node_path15.default.resolve(deps.cwd, parsed.flags["--workflow-source"] ?? ""),
19042
+ workflowSourcePath: import_node_path16.default.resolve(deps.cwd, parsed.flags["--workflow-source"] ?? ""),
18787
19043
  profileLabel: parsed.flags["--profile-label"] ?? "GitHub Actions",
18788
19044
  enabled: !parsed.boolFlags.has("--disabled"),
18789
19045
  setProjectDefault: parsed.boolFlags.has("--set-default"),
@@ -18823,7 +19079,7 @@ async function applyCommand(args, context, deps) {
18823
19079
  return await applyAzureCommand(plan, parsed, context, deps, secrets);
18824
19080
  }
18825
19081
  const scopes = approvedScopes(plan, parsed, dryRun);
18826
- const repoRoot = import_node_path15.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
19082
+ const repoRoot = import_node_path16.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
18827
19083
  const rotateSecrets = parsed.boolFlags.has("--rotate-secrets");
18828
19084
  const rotateProviderToken = parsed.boolFlags.has("--rotate-provider-token");
18829
19085
  const state = await prepareState(plan, scopes, secrets, context, deps, dryRun, rotateSecrets, rotateProviderToken);
@@ -19115,7 +19371,7 @@ async function doctorCommand(args, context, deps) {
19115
19371
  exitCode
19116
19372
  );
19117
19373
  }
19118
- const repoRoot = import_node_path15.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
19374
+ const repoRoot = import_node_path16.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
19119
19375
  const summary = await inspectPlan(plan, repoRoot, secrets, context, deps);
19120
19376
  return jsonResponse(summary, summary.exitCode);
19121
19377
  } catch (error) {
@@ -19199,7 +19455,7 @@ function pickConfigArgs10(parsed) {
19199
19455
  }
19200
19456
  return args;
19201
19457
  }
19202
- function normalizeError9(error) {
19458
+ function normalizeError10(error) {
19203
19459
  if (error instanceof ForgeClientError) {
19204
19460
  return `${error.code}: ${error.message}`;
19205
19461
  }
@@ -19325,7 +19581,7 @@ function createSuitesHandler(deps = {}) {
19325
19581
  } catch (error) {
19326
19582
  return {
19327
19583
  exitCode: ExitCode.TransportError,
19328
- stderr: [`ERROR: ${normalizeError9(error)}`]
19584
+ stderr: [`ERROR: ${normalizeError10(error)}`]
19329
19585
  };
19330
19586
  }
19331
19587
  }
@@ -19366,7 +19622,7 @@ function createSuitesHandler(deps = {}) {
19366
19622
  } catch (error) {
19367
19623
  return {
19368
19624
  exitCode: ExitCode.TransportError,
19369
- stderr: [`ERROR: ${normalizeError9(error)}`]
19625
+ stderr: [`ERROR: ${normalizeError10(error)}`]
19370
19626
  };
19371
19627
  }
19372
19628
  }
@@ -19430,7 +19686,7 @@ function createSuitesHandler(deps = {}) {
19430
19686
  } catch (error) {
19431
19687
  return {
19432
19688
  exitCode: ExitCode.TransportError,
19433
- stderr: [`ERROR: ${normalizeError9(error)}`]
19689
+ stderr: [`ERROR: ${normalizeError10(error)}`]
19434
19690
  };
19435
19691
  }
19436
19692
  }
@@ -19490,7 +19746,7 @@ function pickConfigArgs11(parsed) {
19490
19746
  }
19491
19747
  return args;
19492
19748
  }
19493
- function normalizeError10(error) {
19749
+ function normalizeError11(error) {
19494
19750
  if (error instanceof ForgeClientError) {
19495
19751
  return `${error.code}: ${error.message}`;
19496
19752
  }
@@ -19593,7 +19849,7 @@ function createSyncHandler(deps = {}) {
19593
19849
  } catch (error) {
19594
19850
  return {
19595
19851
  exitCode: ExitCode.TransportError,
19596
- stderr: [`ERROR: ${normalizeError10(error)}`]
19852
+ stderr: [`ERROR: ${normalizeError11(error)}`]
19597
19853
  };
19598
19854
  }
19599
19855
  }
@@ -19639,7 +19895,7 @@ function createSyncHandler(deps = {}) {
19639
19895
  } catch (error) {
19640
19896
  return {
19641
19897
  exitCode: ExitCode.TransportError,
19642
- stderr: [`ERROR: ${normalizeError10(error)}`]
19898
+ stderr: [`ERROR: ${normalizeError11(error)}`]
19643
19899
  };
19644
19900
  }
19645
19901
  }
@@ -19673,9 +19929,9 @@ var COMMAND_REGISTRY = [
19673
19929
  },
19674
19930
  {
19675
19931
  name: "bdd",
19676
- description: "BDD scenario inspection and feature export commands",
19932
+ description: "BDD scenario listing, inspection, and feature export commands",
19677
19933
  subcommands: ["features", "scenarios"],
19678
- handler: createBddHandler()
19934
+ handler: createBddCommandHandler()
19679
19935
  },
19680
19936
  {
19681
19937
  name: "cases",