@automatify-au/cli 0.1.15 → 0.1.17

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 (2) hide show
  1. package/dist/automatify.cjs +842 -214
  2. 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,
@@ -15954,8 +16078,8 @@ function createDoctorHandler(deps = {}) {
15954
16078
  }
15955
16079
 
15956
16080
  // src/ingestFeature.ts
15957
- var import_node_fs12 = require("node:fs");
15958
- var import_node_path12 = __toESM(require("node:path"), 1);
16081
+ var import_node_fs13 = require("node:fs");
16082
+ var import_node_path13 = __toESM(require("node:path"), 1);
15959
16083
  var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
15960
16084
  "--config",
15961
16085
  "--base-url",
@@ -16041,8 +16165,8 @@ function normalizeError5(error) {
16041
16165
  return "Unknown ingest error.";
16042
16166
  }
16043
16167
  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"));
16168
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs13.readFileSync)(filePath, "utf8"));
16169
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs13.readFileSync)(0, "utf8"));
16046
16170
  const cwd = deps.cwd ?? process.cwd();
16047
16171
  const env = deps.env ?? process.env;
16048
16172
  return async (request, context) => {
@@ -16052,7 +16176,7 @@ function createIngestFeatureHandler(deps = {}) {
16052
16176
  const config = resolveCliConfig(configArgs, env, cwd);
16053
16177
  const projectKey = config.values.projectKey;
16054
16178
  const issueKey = config.values.issueKey;
16055
- const sourceFile = parsed.flags["--file"] ? import_node_path12.default.resolve(cwd, parsed.flags["--file"]) : "";
16179
+ const sourceFile = parsed.flags["--file"] ? import_node_path13.default.resolve(cwd, parsed.flags["--file"]) : "";
16056
16180
  const useStdin = parsed.boolFlags.has("--stdin");
16057
16181
  const useJson = parsed.boolFlags.has("--json");
16058
16182
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -16172,16 +16296,514 @@ function createIngestFeatureHandler(deps = {}) {
16172
16296
  };
16173
16297
  }
16174
16298
 
16299
+ // src/profiles.ts
16300
+ var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
16301
+ "--config",
16302
+ "--base-url",
16303
+ "--project-key",
16304
+ "--issue-key",
16305
+ "--auth-mode",
16306
+ "--jira-email",
16307
+ "--jira-api-token"
16308
+ ]);
16309
+ function parseArgs8(args) {
16310
+ const flags = {};
16311
+ const boolFlags = /* @__PURE__ */ new Set();
16312
+ const unknownFlags = [];
16313
+ const supportedValueFlags = /* @__PURE__ */ new Set(["--id", ...CONFIG_FLAGS7]);
16314
+ const supportedBoolFlags = /* @__PURE__ */ new Set(["--json", "--dry-run", "--confirm", "--detach"]);
16315
+ for (let index = 0; index < args.length; index += 1) {
16316
+ const token = args[index];
16317
+ if (!token.startsWith("--")) {
16318
+ unknownFlags.push(token);
16319
+ continue;
16320
+ }
16321
+ if (supportedBoolFlags.has(token)) {
16322
+ boolFlags.add(token);
16323
+ continue;
16324
+ }
16325
+ if (!supportedValueFlags.has(token)) {
16326
+ unknownFlags.push(token);
16327
+ continue;
16328
+ }
16329
+ const value = args[index + 1];
16330
+ if (!value || value.startsWith("--")) {
16331
+ unknownFlags.push(token);
16332
+ continue;
16333
+ }
16334
+ flags[token] = value;
16335
+ index += 1;
16336
+ }
16337
+ return { flags, boolFlags, unknownFlags };
16338
+ }
16339
+ function pickConfigArgs7(parsed) {
16340
+ const args = [];
16341
+ for (const [flag, value] of Object.entries(parsed.flags)) {
16342
+ if (CONFIG_FLAGS7.has(flag)) {
16343
+ args.push(flag, value);
16344
+ }
16345
+ }
16346
+ return args;
16347
+ }
16348
+ function normalizeError6(error) {
16349
+ if (error instanceof ForgeClientError) {
16350
+ return `${error.code}: ${error.message}`;
16351
+ }
16352
+ if (error instanceof Error) {
16353
+ return error.message;
16354
+ }
16355
+ return "Unknown profiles command error.";
16356
+ }
16357
+ function missingProjectResponse3() {
16358
+ return {
16359
+ exitCode: ExitCode.ValidationError,
16360
+ stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
16361
+ };
16362
+ }
16363
+ function confirmationRequiredResponse(command) {
16364
+ return {
16365
+ exitCode: ExitCode.ValidationError,
16366
+ stderr: [`ERROR: ${command} is destructive and requires --confirm. Use --dry-run first to preview the cleanup.`]
16367
+ };
16368
+ }
16369
+ function remoteErrorResponse(code, message, stdout = []) {
16370
+ return {
16371
+ exitCode: ExitCode.RemoteError,
16372
+ stdout,
16373
+ stderr: [`ERROR: ${code}: ${message}`]
16374
+ };
16375
+ }
16376
+ function toJsonProfile(profile) {
16377
+ return {
16378
+ id: profile.id,
16379
+ label: profile.label,
16380
+ provider: profile.provider,
16381
+ endpointSummary: profile.endpointSummary,
16382
+ enabled: profile.enabled,
16383
+ isProjectDefault: profile.isProjectDefault ?? false,
16384
+ hasSecret: profile.hasSecret,
16385
+ createdAt: profile.createdAt,
16386
+ updatedAt: profile.updatedAt,
16387
+ lastUsedAt: profile.lastUsedAt ?? null
16388
+ };
16389
+ }
16390
+ function toJsonBinding(automation) {
16391
+ return {
16392
+ id: automation.id,
16393
+ scenarioId: automation.scenarioId,
16394
+ label: automation.label,
16395
+ profileId: automation.profileId ?? null,
16396
+ profileLabel: automation.profileLabel ?? null,
16397
+ materializedFromProjectDefault: automation.materializedFromProjectDefault ?? false,
16398
+ enabled: automation.enabled
16399
+ };
16400
+ }
16401
+ function profileLines(profiles) {
16402
+ if (profiles.length === 0) {
16403
+ return ["Automation profiles: 0", "No automation profiles found for the selected project."];
16404
+ }
16405
+ return [
16406
+ `Automation profiles: ${profiles.length}`,
16407
+ ...profiles.map((profile) => {
16408
+ const flags = [profile.isProjectDefault ? "default" : "", profile.enabled ? "enabled" : "disabled"].filter(
16409
+ Boolean
16410
+ );
16411
+ return `- ${profile.id} [${flags.join(", ")}] ${profile.label} (${profile.provider}) ${profile.endpointSummary}`;
16412
+ })
16413
+ ];
16414
+ }
16415
+ function bindingLines(bindings) {
16416
+ return bindings.map(
16417
+ (binding) => `- ${binding.id} scenario=${binding.scenarioId} ${binding.label}` + (binding.materializedFromProjectDefault ? " [project-default materialized]" : "")
16418
+ );
16419
+ }
16420
+ function profileBindings(automations, profileId) {
16421
+ return automations.filter((automation) => automation.mode === "profile" && automation.profileId === profileId);
16422
+ }
16423
+ function allProfileBindings(automations) {
16424
+ return automations.filter((automation) => automation.mode === "profile");
16425
+ }
16426
+ function createProfilesHandler(deps = {}) {
16427
+ const cwd = deps.cwd ?? process.cwd();
16428
+ const env = deps.env ?? process.env;
16429
+ return async (request, context) => {
16430
+ const [subcommand, ...restArgs] = request.args;
16431
+ const parsed = parseArgs8(restArgs);
16432
+ const useJson = parsed.boolFlags.has("--json");
16433
+ const dryRun = parsed.boolFlags.has("--dry-run");
16434
+ const confirmed = parsed.boolFlags.has("--confirm");
16435
+ const detach = parsed.boolFlags.has("--detach");
16436
+ const profileId = parsed.flags["--id"]?.trim() ?? "";
16437
+ const config = resolveCliConfig(pickConfigArgs7(parsed), env, cwd);
16438
+ const projectKey = config.values.projectKey;
16439
+ const issueKey = config.values.issueKey;
16440
+ const forgeContext = {
16441
+ projectKey: projectKey || void 0,
16442
+ issueKey: issueKey || void 0
16443
+ };
16444
+ if (parsed.unknownFlags.length > 0) {
16445
+ return {
16446
+ exitCode: ExitCode.UsageError,
16447
+ stderr: [`ERROR: Unknown or invalid arguments: ${parsed.unknownFlags.join(", ")}`]
16448
+ };
16449
+ }
16450
+ if (!projectKey) {
16451
+ return missingProjectResponse3();
16452
+ }
16453
+ if (dryRun && confirmed) {
16454
+ return {
16455
+ exitCode: ExitCode.UsageError,
16456
+ stderr: ["ERROR: Use either --dry-run or --confirm, not both."]
16457
+ };
16458
+ }
16459
+ if (subcommand === "list") {
16460
+ if (dryRun || confirmed || detach || profileId) {
16461
+ return {
16462
+ exitCode: ExitCode.UsageError,
16463
+ stderr: ["ERROR: profiles list accepts only configuration flags and optional --json."]
16464
+ };
16465
+ }
16466
+ try {
16467
+ const result = await context.invokeForgeContract("listScenarioAutomationProfiles", {
16468
+ context: forgeContext
16469
+ });
16470
+ if (!result.ok) {
16471
+ return remoteErrorResponse(result.error.code, result.error.message);
16472
+ }
16473
+ if (useJson) {
16474
+ return {
16475
+ exitCode: ExitCode.Success,
16476
+ stdout: toJsonLine({
16477
+ action: "profiles-list",
16478
+ projectKey,
16479
+ count: result.data.length,
16480
+ items: result.data.map(toJsonProfile)
16481
+ })
16482
+ };
16483
+ }
16484
+ return {
16485
+ exitCode: ExitCode.Success,
16486
+ stdout: profileLines(result.data)
16487
+ };
16488
+ } catch (error) {
16489
+ return {
16490
+ exitCode: ExitCode.TransportError,
16491
+ stderr: [`ERROR: ${normalizeError6(error)}`]
16492
+ };
16493
+ }
16494
+ }
16495
+ if (subcommand === "detach") {
16496
+ if (!profileId) {
16497
+ return {
16498
+ exitCode: ExitCode.ValidationError,
16499
+ stderr: ["ERROR: Missing required --id <PROFILE_ID> for profiles detach."]
16500
+ };
16501
+ }
16502
+ if (detach) {
16503
+ return {
16504
+ exitCode: ExitCode.UsageError,
16505
+ stderr: [
16506
+ "ERROR: --detach is only valid with profiles delete; profiles detach already performs that operation."
16507
+ ]
16508
+ };
16509
+ }
16510
+ if (!dryRun && !confirmed) {
16511
+ return confirmationRequiredResponse("profiles detach");
16512
+ }
16513
+ try {
16514
+ const result = await context.invokeForgeContract("listScenarioAutomations", {
16515
+ context: forgeContext
16516
+ });
16517
+ if (!result.ok) {
16518
+ return remoteErrorResponse(result.error.code, result.error.message);
16519
+ }
16520
+ const bindings = profileBindings(result.data, profileId);
16521
+ if (dryRun) {
16522
+ if (useJson) {
16523
+ return {
16524
+ exitCode: ExitCode.Success,
16525
+ stdout: toJsonLine({
16526
+ action: "profiles-detach",
16527
+ dryRun: true,
16528
+ projectKey,
16529
+ profileId,
16530
+ bindingCount: bindings.length,
16531
+ bindings: bindings.map(toJsonBinding)
16532
+ })
16533
+ };
16534
+ }
16535
+ return {
16536
+ exitCode: ExitCode.Success,
16537
+ stdout: [
16538
+ `Dry run: profile ${profileId} has ${bindings.length} automation binding(s) to detach.`,
16539
+ ...bindingLines(bindings),
16540
+ "No changes made."
16541
+ ]
16542
+ };
16543
+ }
16544
+ const deletedIds = [];
16545
+ for (const binding of bindings) {
16546
+ const deleted = await context.invokeForgeContract("deleteScenarioAutomation", {
16547
+ context: forgeContext,
16548
+ automationId: binding.id
16549
+ });
16550
+ if (!deleted.ok) {
16551
+ return remoteErrorResponse(deleted.error.code, deleted.error.message, [
16552
+ `Detached ${deletedIds.length} of ${bindings.length} automation binding(s) before the failure.`
16553
+ ]);
16554
+ }
16555
+ deletedIds.push(binding.id);
16556
+ }
16557
+ if (useJson) {
16558
+ return {
16559
+ exitCode: ExitCode.Success,
16560
+ stdout: toJsonLine({
16561
+ action: "profiles-detach",
16562
+ dryRun: false,
16563
+ projectKey,
16564
+ profileId,
16565
+ detachedCount: deletedIds.length,
16566
+ detachedAutomationIds: deletedIds
16567
+ })
16568
+ };
16569
+ }
16570
+ return {
16571
+ exitCode: ExitCode.Success,
16572
+ stdout: [`Detached ${deletedIds.length} automation binding(s) from profile ${profileId}.`]
16573
+ };
16574
+ } catch (error) {
16575
+ return {
16576
+ exitCode: ExitCode.TransportError,
16577
+ stderr: [`ERROR: ${normalizeError6(error)}`]
16578
+ };
16579
+ }
16580
+ }
16581
+ if (subcommand === "delete") {
16582
+ if (!profileId) {
16583
+ return {
16584
+ exitCode: ExitCode.ValidationError,
16585
+ stderr: ["ERROR: Missing required --id <PROFILE_ID> for profiles delete."]
16586
+ };
16587
+ }
16588
+ if (!dryRun && !confirmed) {
16589
+ return confirmationRequiredResponse("profiles delete");
16590
+ }
16591
+ try {
16592
+ let bindings = [];
16593
+ if (detach || dryRun) {
16594
+ const automations = await context.invokeForgeContract("listScenarioAutomations", {
16595
+ context: forgeContext
16596
+ });
16597
+ if (!automations.ok) {
16598
+ return remoteErrorResponse(automations.error.code, automations.error.message);
16599
+ }
16600
+ bindings = profileBindings(automations.data, profileId);
16601
+ }
16602
+ if (dryRun) {
16603
+ if (useJson) {
16604
+ return {
16605
+ exitCode: ExitCode.Success,
16606
+ stdout: toJsonLine({
16607
+ action: "profiles-delete",
16608
+ dryRun: true,
16609
+ projectKey,
16610
+ profileId,
16611
+ detach,
16612
+ bindingCount: bindings.length,
16613
+ bindings: bindings.map(toJsonBinding)
16614
+ })
16615
+ };
16616
+ }
16617
+ return {
16618
+ exitCode: ExitCode.Success,
16619
+ stdout: [
16620
+ `Dry run: profile ${profileId} would be deleted.`,
16621
+ detach ? `The command would first detach ${bindings.length} automation binding(s).` : `The command would not detach ${bindings.length} automation binding(s); Forge will block deletion if the profile is in use.`,
16622
+ ...bindingLines(bindings),
16623
+ "No changes made."
16624
+ ]
16625
+ };
16626
+ }
16627
+ const detachedIds = [];
16628
+ if (detach) {
16629
+ for (const binding of bindings) {
16630
+ const deletedBinding = await context.invokeForgeContract("deleteScenarioAutomation", {
16631
+ context: forgeContext,
16632
+ automationId: binding.id
16633
+ });
16634
+ if (!deletedBinding.ok) {
16635
+ return remoteErrorResponse(deletedBinding.error.code, deletedBinding.error.message, [
16636
+ `Detached ${detachedIds.length} of ${bindings.length} automation binding(s); profile ${profileId} was not deleted.`
16637
+ ]);
16638
+ }
16639
+ detachedIds.push(binding.id);
16640
+ }
16641
+ }
16642
+ const deletedProfile = await context.invokeForgeContract("deleteScenarioAutomationProfile", {
16643
+ context: forgeContext,
16644
+ profileId
16645
+ });
16646
+ if (!deletedProfile.ok) {
16647
+ return remoteErrorResponse(deletedProfile.error.code, deletedProfile.error.message, [
16648
+ `Detached ${detachedIds.length} automation binding(s) before profile deletion was rejected.`
16649
+ ]);
16650
+ }
16651
+ if (useJson) {
16652
+ return {
16653
+ exitCode: ExitCode.Success,
16654
+ stdout: toJsonLine({
16655
+ action: "profiles-delete",
16656
+ dryRun: false,
16657
+ projectKey,
16658
+ profileId,
16659
+ detachedCount: detachedIds.length,
16660
+ detachedAutomationIds: detachedIds,
16661
+ deleted: true
16662
+ })
16663
+ };
16664
+ }
16665
+ return {
16666
+ exitCode: ExitCode.Success,
16667
+ stdout: [
16668
+ `Deleted automation profile ${profileId}.`,
16669
+ detach ? `Detached ${detachedIds.length} automation binding(s) first.` : "No automatic detach was requested."
16670
+ ]
16671
+ };
16672
+ } catch (error) {
16673
+ return {
16674
+ exitCode: ExitCode.TransportError,
16675
+ stderr: [`ERROR: ${normalizeError6(error)}`]
16676
+ };
16677
+ }
16678
+ }
16679
+ if (subcommand === "clear") {
16680
+ if (profileId || detach) {
16681
+ return {
16682
+ exitCode: ExitCode.UsageError,
16683
+ stderr: [
16684
+ "ERROR: profiles clear clears every profile and profile-based binding; do not pass --id or --detach."
16685
+ ]
16686
+ };
16687
+ }
16688
+ if (!dryRun && !confirmed) {
16689
+ return confirmationRequiredResponse("profiles clear");
16690
+ }
16691
+ try {
16692
+ const [profilesResult, automationsResult] = await Promise.all([
16693
+ context.invokeForgeContract("listScenarioAutomationProfiles", {
16694
+ context: forgeContext
16695
+ }),
16696
+ context.invokeForgeContract("listScenarioAutomations", {
16697
+ context: forgeContext
16698
+ })
16699
+ ]);
16700
+ if (!profilesResult.ok) {
16701
+ return remoteErrorResponse(profilesResult.error.code, profilesResult.error.message);
16702
+ }
16703
+ if (!automationsResult.ok) {
16704
+ return remoteErrorResponse(automationsResult.error.code, automationsResult.error.message);
16705
+ }
16706
+ const profiles = profilesResult.data;
16707
+ const bindings = allProfileBindings(automationsResult.data);
16708
+ if (dryRun) {
16709
+ if (useJson) {
16710
+ return {
16711
+ exitCode: ExitCode.Success,
16712
+ stdout: toJsonLine({
16713
+ action: "profiles-clear",
16714
+ dryRun: true,
16715
+ projectKey,
16716
+ profileCount: profiles.length,
16717
+ bindingCount: bindings.length,
16718
+ profiles: profiles.map(toJsonProfile),
16719
+ bindings: bindings.map(toJsonBinding)
16720
+ })
16721
+ };
16722
+ }
16723
+ return {
16724
+ exitCode: ExitCode.Success,
16725
+ stdout: [
16726
+ `Dry run: would detach ${bindings.length} profile-based automation binding(s) and delete ${profiles.length} automation profile(s).`,
16727
+ ...bindingLines(bindings),
16728
+ ...profiles.map((profile) => `- profile ${profile.id} ${profile.label}`),
16729
+ "Direct (non-profile) scenario automations are preserved.",
16730
+ "No changes made."
16731
+ ]
16732
+ };
16733
+ }
16734
+ const detachedIds = [];
16735
+ for (const binding of bindings) {
16736
+ const deletedBinding = await context.invokeForgeContract("deleteScenarioAutomation", {
16737
+ context: forgeContext,
16738
+ automationId: binding.id
16739
+ });
16740
+ if (!deletedBinding.ok) {
16741
+ return remoteErrorResponse(deletedBinding.error.code, deletedBinding.error.message, [
16742
+ `Detached ${detachedIds.length} of ${bindings.length} profile-based automation binding(s). No profiles were deleted after the failure.`
16743
+ ]);
16744
+ }
16745
+ detachedIds.push(binding.id);
16746
+ }
16747
+ const deletedProfileIds = [];
16748
+ for (const profile of profiles) {
16749
+ const deletedProfile = await context.invokeForgeContract("deleteScenarioAutomationProfile", {
16750
+ context: forgeContext,
16751
+ profileId: profile.id
16752
+ });
16753
+ if (!deletedProfile.ok) {
16754
+ return remoteErrorResponse(deletedProfile.error.code, deletedProfile.error.message, [
16755
+ `Detached ${detachedIds.length} profile-based automation binding(s).`,
16756
+ `Deleted ${deletedProfileIds.length} of ${profiles.length} automation profile(s) before the failure.`
16757
+ ]);
16758
+ }
16759
+ deletedProfileIds.push(profile.id);
16760
+ }
16761
+ if (useJson) {
16762
+ return {
16763
+ exitCode: ExitCode.Success,
16764
+ stdout: toJsonLine({
16765
+ action: "profiles-clear",
16766
+ dryRun: false,
16767
+ projectKey,
16768
+ detachedCount: detachedIds.length,
16769
+ detachedAutomationIds: detachedIds,
16770
+ deletedProfileCount: deletedProfileIds.length,
16771
+ deletedProfileIds
16772
+ })
16773
+ };
16774
+ }
16775
+ return {
16776
+ exitCode: ExitCode.Success,
16777
+ stdout: [
16778
+ `Detached ${detachedIds.length} profile-based automation binding(s).`,
16779
+ `Deleted ${deletedProfileIds.length} automation profile(s).`,
16780
+ "Direct (non-profile) scenario automations were preserved."
16781
+ ]
16782
+ };
16783
+ } catch (error) {
16784
+ return {
16785
+ exitCode: ExitCode.TransportError,
16786
+ stderr: [`ERROR: ${normalizeError6(error)}`]
16787
+ };
16788
+ }
16789
+ }
16790
+ return {
16791
+ exitCode: ExitCode.UsageError,
16792
+ stderr: ["ERROR: Unsupported profiles subcommand. Use: list, detach, delete, or clear."]
16793
+ };
16794
+ };
16795
+ }
16796
+
16175
16797
  // src/runUpload.ts
16176
- var import_node_fs13 = require("node:fs");
16177
- var import_node_path13 = __toESM(require("node:path"), 1);
16798
+ var import_node_fs14 = require("node:fs");
16799
+ var import_node_path14 = __toESM(require("node:path"), 1);
16178
16800
  var STEP_RESULTS = [
16179
16801
  StepResult.Passed,
16180
16802
  StepResult.Failed,
16181
16803
  StepResult.Skipped,
16182
16804
  StepResult.Blocked
16183
16805
  ];
16184
- var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
16806
+ var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
16185
16807
  "--config",
16186
16808
  "--base-url",
16187
16809
  "--project-key",
@@ -16190,7 +16812,7 @@ var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
16190
16812
  "--jira-email",
16191
16813
  "--jira-api-token"
16192
16814
  ]);
16193
- function parseArgs8(args) {
16815
+ function parseArgs9(args) {
16194
16816
  const flags = {};
16195
16817
  const boolFlags = /* @__PURE__ */ new Set();
16196
16818
  const unknownFlags = [];
@@ -16201,7 +16823,7 @@ function parseArgs8(args) {
16201
16823
  "--feature-name",
16202
16824
  "--scenario-name",
16203
16825
  "--executed-at",
16204
- ...CONFIG_FLAGS7
16826
+ ...CONFIG_FLAGS8
16205
16827
  ]);
16206
16828
  const supportedBoolFlags = /* @__PURE__ */ new Set(["--stdin", "--json"]);
16207
16829
  for (let i = 0; i < args.length; i += 1) {
@@ -16227,16 +16849,16 @@ function parseArgs8(args) {
16227
16849
  }
16228
16850
  return { flags, boolFlags, unknownFlags };
16229
16851
  }
16230
- function pickConfigArgs7(parsed) {
16852
+ function pickConfigArgs8(parsed) {
16231
16853
  const configArgs = [];
16232
16854
  for (const [flag, value] of Object.entries(parsed.flags)) {
16233
- if (CONFIG_FLAGS7.has(flag)) {
16855
+ if (CONFIG_FLAGS8.has(flag)) {
16234
16856
  configArgs.push(flag, value);
16235
16857
  }
16236
16858
  }
16237
16859
  return configArgs;
16238
16860
  }
16239
- function normalizeError6(error) {
16861
+ function normalizeError7(error) {
16240
16862
  if (error instanceof ForgeClientError) {
16241
16863
  return `${error.code}: ${error.message}`;
16242
16864
  }
@@ -16315,19 +16937,19 @@ function summarizeRunResult(result) {
16315
16937
  return lines;
16316
16938
  }
16317
16939
  function createRunUploadHandler(deps = {}) {
16318
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs13.readFileSync)(filePath, "utf8"));
16319
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs13.readFileSync)(0, "utf8"));
16940
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs14.readFileSync)(filePath, "utf8"));
16941
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs14.readFileSync)(0, "utf8"));
16320
16942
  const cwd = deps.cwd ?? process.cwd();
16321
16943
  const env = deps.env ?? process.env;
16322
16944
  return async (request, context) => {
16323
16945
  const [, ...subArgs] = request.args;
16324
- const parsed = parseArgs8(subArgs);
16325
- const configArgs = pickConfigArgs7(parsed);
16946
+ const parsed = parseArgs9(subArgs);
16947
+ const configArgs = pickConfigArgs8(parsed);
16326
16948
  const config = resolveCliConfig(configArgs, env, cwd);
16327
16949
  const projectKey = config.values.projectKey;
16328
16950
  const issueKey = config.values.issueKey;
16329
16951
  const testCaseKey = parsed.flags["--test-case-key"]?.trim().toUpperCase() ?? "";
16330
- const sourceFile = parsed.flags["--file"] ? import_node_path13.default.resolve(cwd, parsed.flags["--file"]) : "";
16952
+ const sourceFile = parsed.flags["--file"] ? import_node_path14.default.resolve(cwd, parsed.flags["--file"]) : "";
16331
16953
  const useStdin = parsed.boolFlags.has("--stdin");
16332
16954
  const useJson = parsed.boolFlags.has("--json");
16333
16955
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -16355,7 +16977,7 @@ function createRunUploadHandler(deps = {}) {
16355
16977
  } catch (error) {
16356
16978
  return {
16357
16979
  exitCode: ExitCode.InternalError,
16358
- stderr: [`ERROR: Failed to read run payload source: ${normalizeError6(error)}`]
16980
+ stderr: [`ERROR: Failed to read run payload source: ${normalizeError7(error)}`]
16359
16981
  };
16360
16982
  }
16361
16983
  const payloadFromSource = parseRunPayload(raw);
@@ -16385,7 +17007,7 @@ function createRunUploadHandler(deps = {}) {
16385
17007
  } catch (error) {
16386
17008
  return {
16387
17009
  exitCode: ExitCode.TransportError,
16388
- stderr: [`ERROR: ${normalizeError6(error)}`]
17010
+ stderr: [`ERROR: ${normalizeError7(error)}`]
16389
17011
  };
16390
17012
  }
16391
17013
  }
@@ -16465,7 +17087,7 @@ function createRunUploadHandler(deps = {}) {
16465
17087
  featureName: runInput.featureName,
16466
17088
  scenarioName: runInput.scenarioName,
16467
17089
  executedAt: runInput.executedAt,
16468
- errorMessage: normalizeError6(error)
17090
+ errorMessage: normalizeError7(error)
16469
17091
  })
16470
17092
  };
16471
17093
  }
@@ -16477,14 +17099,14 @@ function createRunUploadHandler(deps = {}) {
16477
17099
  `Scenario: ${runInput.scenarioName}`,
16478
17100
  `ExecutedAt: ${runInput.executedAt}`
16479
17101
  ],
16480
- stderr: [`ERROR: ${normalizeError6(error)}`]
17102
+ stderr: [`ERROR: ${normalizeError7(error)}`]
16481
17103
  };
16482
17104
  }
16483
17105
  };
16484
17106
  }
16485
17107
 
16486
17108
  // src/runs.ts
16487
- var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
17109
+ var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
16488
17110
  "--config",
16489
17111
  "--base-url",
16490
17112
  "--project-key",
@@ -16493,11 +17115,11 @@ var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
16493
17115
  "--jira-email",
16494
17116
  "--jira-api-token"
16495
17117
  ]);
16496
- function parseArgs9(args) {
17118
+ function parseArgs10(args) {
16497
17119
  const flags = {};
16498
17120
  const boolFlags = /* @__PURE__ */ new Set();
16499
17121
  const unknownFlags = [];
16500
- const supportedValueFlags = /* @__PURE__ */ new Set(["--id", ...CONFIG_FLAGS8]);
17122
+ const supportedValueFlags = /* @__PURE__ */ new Set(["--id", ...CONFIG_FLAGS9]);
16501
17123
  const supportedBoolFlags = /* @__PURE__ */ new Set(["--json"]);
16502
17124
  for (let index = 0; index < args.length; index += 1) {
16503
17125
  const token = args[index];
@@ -16522,16 +17144,16 @@ function parseArgs9(args) {
16522
17144
  }
16523
17145
  return { flags, boolFlags, unknownFlags };
16524
17146
  }
16525
- function pickConfigArgs8(parsed) {
17147
+ function pickConfigArgs9(parsed) {
16526
17148
  const args = [];
16527
17149
  for (const [flag, value] of Object.entries(parsed.flags)) {
16528
- if (CONFIG_FLAGS8.has(flag)) {
17150
+ if (CONFIG_FLAGS9.has(flag)) {
16529
17151
  args.push(flag, value);
16530
17152
  }
16531
17153
  }
16532
17154
  return args;
16533
17155
  }
16534
- function normalizeError7(error) {
17156
+ function normalizeError8(error) {
16535
17157
  if (error instanceof ForgeClientError) {
16536
17158
  return `${error.code}: ${error.message}`;
16537
17159
  }
@@ -16584,7 +17206,7 @@ function runToLines(run) {
16584
17206
  }
16585
17207
  return lines;
16586
17208
  }
16587
- function missingProjectResponse3() {
17209
+ function missingProjectResponse4() {
16588
17210
  return {
16589
17211
  exitCode: ExitCode.ValidationError,
16590
17212
  stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
@@ -16595,9 +17217,9 @@ function createRunsHandler(deps = {}) {
16595
17217
  const env = deps.env ?? process.env;
16596
17218
  return async (request, context) => {
16597
17219
  const [subcommand, ...restArgs] = request.args;
16598
- const parsed = parseArgs9(restArgs);
17220
+ const parsed = parseArgs10(restArgs);
16599
17221
  const useJson = parsed.boolFlags.has("--json");
16600
- const config = resolveCliConfig(pickConfigArgs8(parsed), env, cwd);
17222
+ const config = resolveCliConfig(pickConfigArgs9(parsed), env, cwd);
16601
17223
  const projectKey = config.values.projectKey;
16602
17224
  const issueKey = config.values.issueKey;
16603
17225
  if (parsed.unknownFlags.length > 0) {
@@ -16607,7 +17229,7 @@ function createRunsHandler(deps = {}) {
16607
17229
  };
16608
17230
  }
16609
17231
  if (!projectKey) {
16610
- return missingProjectResponse3();
17232
+ return missingProjectResponse4();
16611
17233
  }
16612
17234
  if (subcommand === "show") {
16613
17235
  const runId = parsed.flags["--id"]?.trim() ?? "";
@@ -16649,7 +17271,7 @@ function createRunsHandler(deps = {}) {
16649
17271
  } catch (error) {
16650
17272
  return {
16651
17273
  exitCode: ExitCode.TransportError,
16652
- stderr: [`ERROR: ${normalizeError7(error)}`]
17274
+ stderr: [`ERROR: ${normalizeError8(error)}`]
16653
17275
  };
16654
17276
  }
16655
17277
  }
@@ -16661,16 +17283,16 @@ function createRunsHandler(deps = {}) {
16661
17283
  }
16662
17284
 
16663
17285
  // src/setup.ts
16664
- var import_node_fs15 = require("node:fs");
16665
- var import_node_path15 = __toESM(require("node:path"), 1);
17286
+ var import_node_fs16 = require("node:fs");
17287
+ var import_node_path16 = __toESM(require("node:path"), 1);
16666
17288
 
16667
17289
  // src/githubSetupProvider.ts
16668
17290
  var import_node_child_process4 = require("node:child_process");
16669
17291
 
16670
17292
  // src/setupPlan.ts
16671
- var import_node_crypto = require("node:crypto");
16672
- var import_node_fs14 = require("node:fs");
16673
- var import_node_path14 = __toESM(require("node:path"), 1);
17293
+ var import_node_crypto2 = require("node:crypto");
17294
+ var import_node_fs15 = require("node:fs");
17295
+ var import_node_path15 = __toESM(require("node:path"), 1);
16674
17296
  var SETUP_PLAN_SCHEMA_VERSION = "automatify.testops.setup/v1";
16675
17297
  var SETUP_PLAN_KIND = "AutomatifyTestOpsSetupPlan";
16676
17298
  var ACTIONS = [
@@ -16694,7 +17316,7 @@ var ACTIONS = [
16694
17316
  }
16695
17317
  ];
16696
17318
  function sha256(value) {
16697
- return (0, import_node_crypto.createHash)("sha256").update(value, "utf8").digest("hex");
17319
+ return (0, import_node_crypto2.createHash)("sha256").update(value, "utf8").digest("hex");
16698
17320
  }
16699
17321
  function sortJsonValue(value) {
16700
17322
  if (Array.isArray(value)) {
@@ -16739,8 +17361,8 @@ function normalizeRepoPart(value, field) {
16739
17361
  return normalized;
16740
17362
  }
16741
17363
  function normalizeWorkflowPath(value) {
16742
- const normalized = import_node_path14.default.posix.normalize(requireNonEmpty(value, "workflow path").replaceAll("\\", "/"));
16743
- if (import_node_path14.default.posix.isAbsolute(normalized) || normalized.startsWith("../") || !normalized.startsWith(".github/workflows/") || !/\.ya?ml$/i.test(normalized)) {
17364
+ const normalized = import_node_path15.default.posix.normalize(requireNonEmpty(value, "workflow path").replaceAll("\\", "/"));
17365
+ if (import_node_path15.default.posix.isAbsolute(normalized) || normalized.startsWith("../") || !normalized.startsWith(".github/workflows/") || !/\.ya?ml$/i.test(normalized)) {
16744
17366
  throw new Error("workflow path must be a relative .github/workflows/*.yml or *.yaml path without '..'.");
16745
17367
  }
16746
17368
  return normalized;
@@ -16765,7 +17387,7 @@ function buildGitHubSetupPlan(input) {
16765
17387
  if (endpointSecretName === tokenSecretName) {
16766
17388
  throw new Error("callback endpoint and auth token secret names must be different.");
16767
17389
  }
16768
- const workflowContent = (0, import_node_fs14.readFileSync)(input.workflowSourcePath, "utf8");
17390
+ const workflowContent = (0, import_node_fs15.readFileSync)(input.workflowSourcePath, "utf8");
16769
17391
  if (!workflowContent.trim()) {
16770
17392
  throw new Error("workflow source file is empty.");
16771
17393
  }
@@ -16798,7 +17420,7 @@ function buildGitHubSetupPlan(input) {
16798
17420
  workflowId,
16799
17421
  workflowFile: {
16800
17422
  path: workflowPath,
16801
- sourcePath: import_node_path14.default.resolve(input.workflowSourcePath),
17423
+ sourcePath: import_node_path15.default.resolve(input.workflowSourcePath),
16802
17424
  sha256: sha256(workflowContent)
16803
17425
  },
16804
17426
  requiredSecrets: [
@@ -17399,8 +18021,8 @@ var AzureDevOpsSetupProvider = class {
17399
18021
  };
17400
18022
 
17401
18023
  // src/azureDevOpsSetupPlan.ts
17402
- var import_node_crypto2 = require("node:crypto");
17403
- var digest = (v) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(v)).digest("hex")}`;
18024
+ var import_node_crypto3 = require("node:crypto");
18025
+ var digest = (v) => `sha256:${(0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(v)).digest("hex")}`;
17404
18026
  var required = (value, field) => {
17405
18027
  const result = value.trim();
17406
18028
  if (!result) throw new Error(`${field} is required.`);
@@ -17629,7 +18251,7 @@ function defaultReadStdin() {
17629
18251
  process.stdin.on("error", reject);
17630
18252
  });
17631
18253
  }
17632
- function parseArgs10(args, valueFlags, boolFlags, allowApplyCollections = false) {
18254
+ function parseArgs11(args, valueFlags, boolFlags, allowApplyCollections = false) {
17633
18255
  const flags = {};
17634
18256
  const enabled = /* @__PURE__ */ new Set();
17635
18257
  const approvals = [];
@@ -17708,10 +18330,10 @@ function errorCodeForError(error, fallback) {
17708
18330
  return error instanceof SetupCommandError ? error.cliCode : fallback;
17709
18331
  }
17710
18332
  function loadPlan(planPath, cwd) {
17711
- const absolute = import_node_path15.default.resolve(cwd, planPath);
18333
+ const absolute = import_node_path16.default.resolve(cwd, planPath);
17712
18334
  let parsed;
17713
18335
  try {
17714
- parsed = JSON.parse((0, import_node_fs15.readFileSync)(absolute, "utf8"));
18336
+ parsed = JSON.parse((0, import_node_fs16.readFileSync)(absolute, "utf8"));
17715
18337
  } catch (error) {
17716
18338
  throw new SetupCommandError(
17717
18339
  "PLAN_READ_ERROR",
@@ -17936,9 +18558,9 @@ function rollbackFor(plan, actionId) {
17936
18558
  return plan.rollback.find((item) => item.actionId === actionId)?.strategy ?? "Review the plan rollback section.";
17937
18559
  }
17938
18560
  function resolveWorkflowTarget(plan, repoRoot) {
17939
- const resolvedRoot = import_node_path15.default.resolve(repoRoot);
17940
- const target = import_node_path15.default.resolve(resolvedRoot, plan.github.workflowFile.path);
17941
- if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${import_node_path15.default.sep}`)) {
18561
+ const resolvedRoot = import_node_path16.default.resolve(repoRoot);
18562
+ const target = import_node_path16.default.resolve(resolvedRoot, plan.github.workflowFile.path);
18563
+ if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${import_node_path16.default.sep}`)) {
17942
18564
  throw new SetupCommandError(
17943
18565
  "WORKFLOW_PATH_ERROR",
17944
18566
  "Workflow target escapes the approved repository root.",
@@ -17950,7 +18572,7 @@ function resolveWorkflowTarget(plan, repoRoot) {
17950
18572
  function applyWorkflowFile(plan, repoRoot, dryRun) {
17951
18573
  const action = plan.actions.find((item) => item.scope === "workflow-file");
17952
18574
  const target = resolveWorkflowTarget(plan, repoRoot);
17953
- const sourceContent = (0, import_node_fs15.readFileSync)(plan.github.workflowFile.sourcePath, "utf8");
18575
+ const sourceContent = (0, import_node_fs16.readFileSync)(plan.github.workflowFile.sourcePath, "utf8");
17954
18576
  if (hashSetupContent(sourceContent) !== plan.github.workflowFile.sha256) {
17955
18577
  throw new SetupCommandError(
17956
18578
  "WORKFLOW_SOURCE_CHANGED",
@@ -17958,8 +18580,8 @@ function applyWorkflowFile(plan, repoRoot, dryRun) {
17958
18580
  ExitCode.ValidationError
17959
18581
  );
17960
18582
  }
17961
- const existed = (0, import_node_fs15.existsSync)(target);
17962
- const previousContent = existed ? (0, import_node_fs15.readFileSync)(target, "utf8") : void 0;
18583
+ const existed = (0, import_node_fs16.existsSync)(target);
18584
+ const previousContent = existed ? (0, import_node_fs16.readFileSync)(target, "utf8") : void 0;
17963
18585
  const matches = previousContent !== void 0 && hashSetupContent(previousContent) === plan.github.workflowFile.sha256;
17964
18586
  if (matches) {
17965
18587
  return {
@@ -17971,8 +18593,8 @@ function applyWorkflowFile(plan, repoRoot, dryRun) {
17971
18593
  };
17972
18594
  }
17973
18595
  if (!dryRun) {
17974
- (0, import_node_fs15.mkdirSync)(import_node_path15.default.dirname(target), { recursive: true });
17975
- (0, import_node_fs15.writeFileSync)(target, sourceContent, { encoding: "utf8", mode: 420 });
18596
+ (0, import_node_fs16.mkdirSync)(import_node_path16.default.dirname(target), { recursive: true });
18597
+ (0, import_node_fs16.writeFileSync)(target, sourceContent, { encoding: "utf8", mode: 420 });
17976
18598
  }
17977
18599
  return {
17978
18600
  id: action.id,
@@ -18142,14 +18764,14 @@ async function inspectPlan(plan, repoRoot, secrets, context, deps) {
18142
18764
  const checks = [];
18143
18765
  let forgeTransportFailure = false;
18144
18766
  const target = resolveWorkflowTarget(plan, repoRoot);
18145
- if (!(0, import_node_fs15.existsSync)(target)) {
18767
+ if (!(0, import_node_fs16.existsSync)(target)) {
18146
18768
  checks.push({
18147
18769
  id: "workflow-local",
18148
18770
  status: "fail",
18149
18771
  message: `Local workflow file ${plan.github.workflowFile.path} does not exist.`
18150
18772
  });
18151
18773
  } else {
18152
- const localHash = hashSetupContent((0, import_node_fs15.readFileSync)(target, "utf8"));
18774
+ const localHash = hashSetupContent((0, import_node_fs16.readFileSync)(target, "utf8"));
18153
18775
  checks.push({
18154
18776
  id: "workflow-local",
18155
18777
  status: localHash === plan.github.workflowFile.sha256 ? "pass" : "fail",
@@ -18239,7 +18861,7 @@ async function inspectPlan(plan, repoRoot, secrets, context, deps) {
18239
18861
  };
18240
18862
  }
18241
18863
  function planCommand(args, deps) {
18242
- const parsed = parseArgs10(args, PLAN_VALUE_FLAGS, /* @__PURE__ */ new Set(["--set-default", "--disabled"]));
18864
+ const parsed = parseArgs11(args, PLAN_VALUE_FLAGS, /* @__PURE__ */ new Set(["--set-default", "--disabled"]));
18243
18865
  if (parsed.errors.length > 0) {
18244
18866
  return jsonResponse(
18245
18867
  errorPayload("USAGE_ERROR", "Invalid setup plan arguments.", parsed.errors),
@@ -18285,7 +18907,7 @@ function planCommand(args, deps) {
18285
18907
  ref: parsed.flags["--ref"] ?? "main",
18286
18908
  workflowId: parsed.flags["--workflow-id"] ?? "scenario-dispatch.yml",
18287
18909
  workflowPath: parsed.flags["--workflow-path"] ?? ".github/workflows/scenario-dispatch.yml",
18288
- workflowSourcePath: import_node_path15.default.resolve(deps.cwd, parsed.flags["--workflow-source"] ?? ""),
18910
+ workflowSourcePath: import_node_path16.default.resolve(deps.cwd, parsed.flags["--workflow-source"] ?? ""),
18289
18911
  profileLabel: parsed.flags["--profile-label"] ?? "GitHub Actions",
18290
18912
  enabled: !parsed.boolFlags.has("--disabled"),
18291
18913
  setProjectDefault: parsed.boolFlags.has("--set-default"),
@@ -18298,7 +18920,7 @@ function planCommand(args, deps) {
18298
18920
  }
18299
18921
  }
18300
18922
  async function applyCommand(args, context, deps) {
18301
- const parsed = parseArgs10(args, APPLY_VALUE_FLAGS, APPLY_BOOL_FLAGS, true);
18923
+ const parsed = parseArgs11(args, APPLY_VALUE_FLAGS, APPLY_BOOL_FLAGS, true);
18302
18924
  if (parsed.errors.length > 0) {
18303
18925
  return jsonResponse(
18304
18926
  errorPayload("USAGE_ERROR", "Invalid setup apply arguments.", parsed.errors),
@@ -18325,7 +18947,7 @@ async function applyCommand(args, context, deps) {
18325
18947
  return await applyAzureCommand(plan, parsed, context, deps, secrets);
18326
18948
  }
18327
18949
  const scopes = approvedScopes(plan, parsed, dryRun);
18328
- const repoRoot = import_node_path15.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
18950
+ const repoRoot = import_node_path16.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
18329
18951
  const rotateSecrets = parsed.boolFlags.has("--rotate-secrets");
18330
18952
  const rotateProviderToken = parsed.boolFlags.has("--rotate-provider-token");
18331
18953
  const state = await prepareState(plan, scopes, secrets, context, deps, dryRun, rotateSecrets, rotateProviderToken);
@@ -18541,7 +19163,7 @@ async function applyAzureCommand(plan, parsed, context, deps, secrets) {
18541
19163
  });
18542
19164
  }
18543
19165
  async function doctorCommand(args, context, deps) {
18544
- const parsed = parseArgs10(args, APPLY_VALUE_FLAGS, /* @__PURE__ */ new Set(["--secrets-stdin"]), true);
19166
+ const parsed = parseArgs11(args, APPLY_VALUE_FLAGS, /* @__PURE__ */ new Set(["--secrets-stdin"]), true);
18545
19167
  if (parsed.approvals.length > 0) {
18546
19168
  return jsonResponse(errorPayload("USAGE_ERROR", "setup doctor does not accept --approve."), ExitCode.UsageError);
18547
19169
  }
@@ -18617,7 +19239,7 @@ async function doctorCommand(args, context, deps) {
18617
19239
  exitCode
18618
19240
  );
18619
19241
  }
18620
- const repoRoot = import_node_path15.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
19242
+ const repoRoot = import_node_path16.default.resolve(deps.cwd, parsed.flags["--repo-root"] ?? ".");
18621
19243
  const summary = await inspectPlan(plan, repoRoot, secrets, context, deps);
18622
19244
  return jsonResponse(summary, summary.exitCode);
18623
19245
  } catch (error) {
@@ -18654,7 +19276,7 @@ function createSetupHandler(overrides = {}) {
18654
19276
  }
18655
19277
 
18656
19278
  // src/suites.ts
18657
- var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
19279
+ var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
18658
19280
  "--config",
18659
19281
  "--base-url",
18660
19282
  "--project-key",
@@ -18663,11 +19285,11 @@ var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
18663
19285
  "--jira-email",
18664
19286
  "--jira-api-token"
18665
19287
  ]);
18666
- function parseArgs11(args) {
19288
+ function parseArgs12(args) {
18667
19289
  const flags = {};
18668
19290
  const boolFlags = /* @__PURE__ */ new Set();
18669
19291
  const unknownFlags = [];
18670
- const supportedValueFlags = /* @__PURE__ */ new Set(["--key", ...CONFIG_FLAGS9]);
19292
+ const supportedValueFlags = /* @__PURE__ */ new Set(["--key", ...CONFIG_FLAGS10]);
18671
19293
  const supportedBoolFlags = /* @__PURE__ */ new Set(["--json"]);
18672
19294
  for (let index = 0; index < args.length; index += 1) {
18673
19295
  const token = args[index];
@@ -18692,16 +19314,16 @@ function parseArgs11(args) {
18692
19314
  }
18693
19315
  return { flags, boolFlags, unknownFlags };
18694
19316
  }
18695
- function pickConfigArgs9(parsed) {
19317
+ function pickConfigArgs10(parsed) {
18696
19318
  const args = [];
18697
19319
  for (const [flag, value] of Object.entries(parsed.flags)) {
18698
- if (CONFIG_FLAGS9.has(flag)) {
19320
+ if (CONFIG_FLAGS10.has(flag)) {
18699
19321
  args.push(flag, value);
18700
19322
  }
18701
19323
  }
18702
19324
  return args;
18703
19325
  }
18704
- function normalizeError8(error) {
19326
+ function normalizeError9(error) {
18705
19327
  if (error instanceof ForgeClientError) {
18706
19328
  return `${error.code}: ${error.message}`;
18707
19329
  }
@@ -18763,7 +19385,7 @@ function suiteCasesToLines(suite, items) {
18763
19385
  ...items.map((item) => `- ${item.key} [${item.status}] ${item.title}`)
18764
19386
  ];
18765
19387
  }
18766
- function missingProjectResponse4() {
19388
+ function missingProjectResponse5() {
18767
19389
  return {
18768
19390
  exitCode: ExitCode.ValidationError,
18769
19391
  stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
@@ -18780,9 +19402,9 @@ function createSuitesHandler(deps = {}) {
18780
19402
  const env = deps.env ?? process.env;
18781
19403
  return async (request, context) => {
18782
19404
  const [subcommand, ...restArgs] = request.args;
18783
- const parsed = parseArgs11(restArgs);
19405
+ const parsed = parseArgs12(restArgs);
18784
19406
  const useJson = parsed.boolFlags.has("--json");
18785
- const config = resolveCliConfig(pickConfigArgs9(parsed), env, cwd);
19407
+ const config = resolveCliConfig(pickConfigArgs10(parsed), env, cwd);
18786
19408
  const projectKey = config.values.projectKey;
18787
19409
  const issueKey = config.values.issueKey;
18788
19410
  if (parsed.unknownFlags.length > 0) {
@@ -18792,7 +19414,7 @@ function createSuitesHandler(deps = {}) {
18792
19414
  };
18793
19415
  }
18794
19416
  if (!projectKey) {
18795
- return missingProjectResponse4();
19417
+ return missingProjectResponse5();
18796
19418
  }
18797
19419
  if (subcommand === "list") {
18798
19420
  try {
@@ -18827,7 +19449,7 @@ function createSuitesHandler(deps = {}) {
18827
19449
  } catch (error) {
18828
19450
  return {
18829
19451
  exitCode: ExitCode.TransportError,
18830
- stderr: [`ERROR: ${normalizeError8(error)}`]
19452
+ stderr: [`ERROR: ${normalizeError9(error)}`]
18831
19453
  };
18832
19454
  }
18833
19455
  }
@@ -18868,7 +19490,7 @@ function createSuitesHandler(deps = {}) {
18868
19490
  } catch (error) {
18869
19491
  return {
18870
19492
  exitCode: ExitCode.TransportError,
18871
- stderr: [`ERROR: ${normalizeError8(error)}`]
19493
+ stderr: [`ERROR: ${normalizeError9(error)}`]
18872
19494
  };
18873
19495
  }
18874
19496
  }
@@ -18932,7 +19554,7 @@ function createSuitesHandler(deps = {}) {
18932
19554
  } catch (error) {
18933
19555
  return {
18934
19556
  exitCode: ExitCode.TransportError,
18935
- stderr: [`ERROR: ${normalizeError8(error)}`]
19557
+ stderr: [`ERROR: ${normalizeError9(error)}`]
18936
19558
  };
18937
19559
  }
18938
19560
  }
@@ -18944,7 +19566,7 @@ function createSuitesHandler(deps = {}) {
18944
19566
  }
18945
19567
 
18946
19568
  // src/sync.ts
18947
- var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
19569
+ var CONFIG_FLAGS11 = /* @__PURE__ */ new Set([
18948
19570
  "--config",
18949
19571
  "--base-url",
18950
19572
  "--project-key",
@@ -18954,11 +19576,11 @@ var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
18954
19576
  "--jira-api-token"
18955
19577
  ]);
18956
19578
  var RECONCILE_CONFIRM_TOKEN = "RECONCILE";
18957
- function parseArgs12(args) {
19579
+ function parseArgs13(args) {
18958
19580
  const flags = {};
18959
19581
  const boolFlags = /* @__PURE__ */ new Set();
18960
19582
  const unknownFlags = [];
18961
- const supportedValueFlags = /* @__PURE__ */ new Set(["--confirm", ...CONFIG_FLAGS10]);
19583
+ const supportedValueFlags = /* @__PURE__ */ new Set(["--confirm", ...CONFIG_FLAGS11]);
18962
19584
  const supportedBoolFlags = /* @__PURE__ */ new Set(["--json"]);
18963
19585
  for (let i = 0; i < args.length; i += 1) {
18964
19586
  const token = args[i];
@@ -18983,16 +19605,16 @@ function parseArgs12(args) {
18983
19605
  }
18984
19606
  return { flags, boolFlags, unknownFlags };
18985
19607
  }
18986
- function pickConfigArgs10(parsed) {
19608
+ function pickConfigArgs11(parsed) {
18987
19609
  const args = [];
18988
19610
  for (const [flag, value] of Object.entries(parsed.flags)) {
18989
- if (CONFIG_FLAGS10.has(flag)) {
19611
+ if (CONFIG_FLAGS11.has(flag)) {
18990
19612
  args.push(flag, value);
18991
19613
  }
18992
19614
  }
18993
19615
  return args;
18994
19616
  }
18995
- function normalizeError9(error) {
19617
+ function normalizeError10(error) {
18996
19618
  if (error instanceof ForgeClientError) {
18997
19619
  return `${error.code}: ${error.message}`;
18998
19620
  }
@@ -19043,9 +19665,9 @@ function createSyncHandler(deps = {}) {
19043
19665
  const env = deps.env ?? process.env;
19044
19666
  return async (request, context) => {
19045
19667
  const [subcommand, ...restArgs] = request.args;
19046
- const parsed = parseArgs12(restArgs);
19668
+ const parsed = parseArgs13(restArgs);
19047
19669
  const useJson = parsed.boolFlags.has("--json");
19048
- const config = resolveCliConfig(pickConfigArgs10(parsed), env, cwd);
19670
+ const config = resolveCliConfig(pickConfigArgs11(parsed), env, cwd);
19049
19671
  const projectKey = config.values.projectKey;
19050
19672
  const issueKey = config.values.issueKey;
19051
19673
  if (parsed.unknownFlags.length > 0) {
@@ -19095,7 +19717,7 @@ function createSyncHandler(deps = {}) {
19095
19717
  } catch (error) {
19096
19718
  return {
19097
19719
  exitCode: ExitCode.TransportError,
19098
- stderr: [`ERROR: ${normalizeError9(error)}`]
19720
+ stderr: [`ERROR: ${normalizeError10(error)}`]
19099
19721
  };
19100
19722
  }
19101
19723
  }
@@ -19141,7 +19763,7 @@ function createSyncHandler(deps = {}) {
19141
19763
  } catch (error) {
19142
19764
  return {
19143
19765
  exitCode: ExitCode.TransportError,
19144
- stderr: [`ERROR: ${normalizeError9(error)}`]
19766
+ stderr: [`ERROR: ${normalizeError10(error)}`]
19145
19767
  };
19146
19768
  }
19147
19769
  }
@@ -19197,6 +19819,12 @@ var COMMAND_REGISTRY = [
19197
19819
  subcommands: ["feature"],
19198
19820
  handler: createIngestFeatureHandler()
19199
19821
  },
19822
+ {
19823
+ name: "profiles",
19824
+ description: "Automation profile inspection, detach, delete, and demo cleanup commands",
19825
+ subcommands: ["list", "detach", "delete", "clear"],
19826
+ handler: createProfilesHandler()
19827
+ },
19200
19828
  {
19201
19829
  name: "run",
19202
19830
  description: "Execution result upload commands",