@automatify-au/cli 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/automatify.cjs +1301 -257
- package/package.json +1 -1
package/dist/automatify.cjs
CHANGED
|
@@ -10765,10 +10765,170 @@ var Priority;
|
|
|
10765
10765
|
|
|
10766
10766
|
// src/config.ts
|
|
10767
10767
|
var import_node_fs = require("node:fs");
|
|
10768
|
-
var import_node_child_process = require("node:child_process");
|
|
10769
10768
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
10770
|
-
|
|
10769
|
+
|
|
10770
|
+
// src/secureStore.ts
|
|
10771
|
+
var import_node_child_process = require("node:child_process");
|
|
10771
10772
|
var KEYCHAIN_SERVICE = "automatify-testops-cli";
|
|
10773
|
+
function errorText(error) {
|
|
10774
|
+
return error instanceof Error && error.message ? error.message : "unknown secure-storage error";
|
|
10775
|
+
}
|
|
10776
|
+
function windowsProtectScript() {
|
|
10777
|
+
return [
|
|
10778
|
+
"Add-Type -AssemblyName System.Security",
|
|
10779
|
+
"$plain = [Console]::In.ReadToEnd()",
|
|
10780
|
+
"$bytes = [Text.Encoding]::UTF8.GetBytes($plain)",
|
|
10781
|
+
"$cipher = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
10782
|
+
"[Console]::Out.Write([Convert]::ToBase64String($cipher))"
|
|
10783
|
+
].join("; ");
|
|
10784
|
+
}
|
|
10785
|
+
function windowsUnprotectScript() {
|
|
10786
|
+
return [
|
|
10787
|
+
"Add-Type -AssemblyName System.Security",
|
|
10788
|
+
"$encoded = [Console]::In.ReadToEnd()",
|
|
10789
|
+
"$cipher = [Convert]::FromBase64String($encoded)",
|
|
10790
|
+
"$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect($cipher, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
10791
|
+
"[Console]::Out.Write([Text.Encoding]::UTF8.GetString($bytes))"
|
|
10792
|
+
].join("; ");
|
|
10793
|
+
}
|
|
10794
|
+
function quoteSecurityInteractiveArg(value) {
|
|
10795
|
+
if (/\r|\n|\0/.test(value)) {
|
|
10796
|
+
throw new Error("macOS Keychain values cannot contain newline or NUL characters.");
|
|
10797
|
+
}
|
|
10798
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
10799
|
+
}
|
|
10800
|
+
function macOSKeychainWriteInput(account, value) {
|
|
10801
|
+
return [
|
|
10802
|
+
"add-generic-password",
|
|
10803
|
+
"-U",
|
|
10804
|
+
"-s",
|
|
10805
|
+
quoteSecurityInteractiveArg(KEYCHAIN_SERVICE),
|
|
10806
|
+
"-a",
|
|
10807
|
+
quoteSecurityInteractiveArg(account),
|
|
10808
|
+
"-w",
|
|
10809
|
+
quoteSecurityInteractiveArg(value)
|
|
10810
|
+
].join(" ") + "\n";
|
|
10811
|
+
}
|
|
10812
|
+
function readSecureSecret(locator, deps = {}) {
|
|
10813
|
+
const platform = deps.platform ?? process.platform;
|
|
10814
|
+
const run = deps.execFileSync ?? import_node_child_process.execFileSync;
|
|
10815
|
+
if (platform === "darwin") {
|
|
10816
|
+
if (!locator.account) {
|
|
10817
|
+
return locator.protectedValue ? {
|
|
10818
|
+
status: "error",
|
|
10819
|
+
message: "This config contains a Windows DPAPI value, but it is being read on macOS. Re-store the secret on this machine."
|
|
10820
|
+
} : { status: "missing" };
|
|
10821
|
+
}
|
|
10822
|
+
try {
|
|
10823
|
+
const value = run("security", ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", locator.account, "-w"], {
|
|
10824
|
+
encoding: "utf8",
|
|
10825
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
10826
|
+
}).trim();
|
|
10827
|
+
return value ? { status: "available", value, source: "keychain" } : {
|
|
10828
|
+
status: "error",
|
|
10829
|
+
message: `macOS Keychain returned an empty value for account ${locator.account}. Re-store the secret.`
|
|
10830
|
+
};
|
|
10831
|
+
} catch (error) {
|
|
10832
|
+
return {
|
|
10833
|
+
status: "error",
|
|
10834
|
+
message: `Unable to read macOS Keychain account ${locator.account}: ${errorText(error)}. Re-store the secret.`
|
|
10835
|
+
};
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
10838
|
+
if (platform === "win32") {
|
|
10839
|
+
if (!locator.protectedValue) {
|
|
10840
|
+
return locator.account ? {
|
|
10841
|
+
status: "error",
|
|
10842
|
+
message: "This config references a macOS Keychain secret, but it is being read on Windows. Re-store the secret for the current Windows user."
|
|
10843
|
+
} : { status: "missing" };
|
|
10844
|
+
}
|
|
10845
|
+
try {
|
|
10846
|
+
const value = run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", windowsUnprotectScript()], {
|
|
10847
|
+
input: locator.protectedValue,
|
|
10848
|
+
encoding: "utf8",
|
|
10849
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10850
|
+
}).trim();
|
|
10851
|
+
return value ? { status: "available", value, source: "secure-store" } : {
|
|
10852
|
+
status: "error",
|
|
10853
|
+
message: "Windows DPAPI returned an empty value. Re-store the secret for the current Windows user."
|
|
10854
|
+
};
|
|
10855
|
+
} catch (error) {
|
|
10856
|
+
return {
|
|
10857
|
+
status: "error",
|
|
10858
|
+
message: `Unable to decrypt the Windows DPAPI value for the current user: ${errorText(error)}. Re-store the secret.`
|
|
10859
|
+
};
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
if (locator.account || locator.protectedValue) {
|
|
10863
|
+
return {
|
|
10864
|
+
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."
|
|
10866
|
+
};
|
|
10867
|
+
}
|
|
10868
|
+
return { status: "missing" };
|
|
10869
|
+
}
|
|
10870
|
+
function writeSecureSecret(account, value, deps = {}) {
|
|
10871
|
+
const platform = deps.platform ?? process.platform;
|
|
10872
|
+
const run = deps.execFileSync ?? import_node_child_process.execFileSync;
|
|
10873
|
+
if (platform === "darwin") {
|
|
10874
|
+
try {
|
|
10875
|
+
run("security", ["-q", "-i"], {
|
|
10876
|
+
input: macOSKeychainWriteInput(account, value),
|
|
10877
|
+
encoding: "utf8",
|
|
10878
|
+
stdio: ["pipe", "ignore", "pipe"]
|
|
10879
|
+
});
|
|
10880
|
+
} catch {
|
|
10881
|
+
throw new Error("Unable to store the secret in macOS Keychain.");
|
|
10882
|
+
}
|
|
10883
|
+
return { source: "keychain", account };
|
|
10884
|
+
}
|
|
10885
|
+
if (platform === "win32") {
|
|
10886
|
+
let protectedValue;
|
|
10887
|
+
try {
|
|
10888
|
+
protectedValue = run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", windowsProtectScript()], {
|
|
10889
|
+
input: value,
|
|
10890
|
+
encoding: "utf8",
|
|
10891
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10892
|
+
}).trim();
|
|
10893
|
+
} catch {
|
|
10894
|
+
throw new Error("Unable to protect the secret with Windows DPAPI for the current user.");
|
|
10895
|
+
}
|
|
10896
|
+
if (!protectedValue) {
|
|
10897
|
+
throw new Error("Windows DPAPI returned an empty protected value.");
|
|
10898
|
+
}
|
|
10899
|
+
return { source: "secure-store", protectedValue };
|
|
10900
|
+
}
|
|
10901
|
+
throw new Error(
|
|
10902
|
+
"Secure local secret storage is supported on macOS and Windows only. On Linux/CI, keep secrets in environment variables."
|
|
10903
|
+
);
|
|
10904
|
+
}
|
|
10905
|
+
function deleteSecureSecret(locator, deps = {}) {
|
|
10906
|
+
const platform = deps.platform ?? process.platform;
|
|
10907
|
+
const run = deps.execFileSync ?? import_node_child_process.execFileSync;
|
|
10908
|
+
if (platform === "darwin") {
|
|
10909
|
+
if (!locator.account) {
|
|
10910
|
+
return { status: "missing" };
|
|
10911
|
+
}
|
|
10912
|
+
try {
|
|
10913
|
+
run("security", ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", locator.account], {
|
|
10914
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
10915
|
+
});
|
|
10916
|
+
return { status: "deleted" };
|
|
10917
|
+
} catch (error) {
|
|
10918
|
+
return {
|
|
10919
|
+
status: "error",
|
|
10920
|
+
message: `Unable to delete macOS Keychain account ${locator.account}: ${errorText(error)}.`
|
|
10921
|
+
};
|
|
10922
|
+
}
|
|
10923
|
+
}
|
|
10924
|
+
if (platform === "win32") {
|
|
10925
|
+
return locator.protectedValue ? { status: "deleted" } : { status: "missing" };
|
|
10926
|
+
}
|
|
10927
|
+
return locator.account || locator.protectedValue ? { status: "deleted" } : { status: "missing" };
|
|
10928
|
+
}
|
|
10929
|
+
|
|
10930
|
+
// src/config.ts
|
|
10931
|
+
var DEFAULT_CONFIG_FILENAME = ".testops-cli.json";
|
|
10772
10932
|
function parseFlags(args) {
|
|
10773
10933
|
const flags = {};
|
|
10774
10934
|
const unknownFlags = [];
|
|
@@ -10802,6 +10962,15 @@ function parseFlags(args) {
|
|
|
10802
10962
|
function toStringValue(value) {
|
|
10803
10963
|
return typeof value === "string" ? value.trim() : "";
|
|
10804
10964
|
}
|
|
10965
|
+
function firstStringValue(...values) {
|
|
10966
|
+
for (const value of values) {
|
|
10967
|
+
const normalized = toStringValue(value);
|
|
10968
|
+
if (normalized) {
|
|
10969
|
+
return normalized;
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
return "";
|
|
10973
|
+
}
|
|
10805
10974
|
function readConfigFile(configPath) {
|
|
10806
10975
|
if (!(0, import_node_fs.existsSync)(configPath)) {
|
|
10807
10976
|
return {};
|
|
@@ -10814,6 +10983,27 @@ function readConfigFile(configPath) {
|
|
|
10814
10983
|
return {};
|
|
10815
10984
|
}
|
|
10816
10985
|
}
|
|
10986
|
+
function readConfigFileForMutation(configPath) {
|
|
10987
|
+
if (!(0, import_node_fs.existsSync)(configPath)) {
|
|
10988
|
+
return { file: {} };
|
|
10989
|
+
}
|
|
10990
|
+
try {
|
|
10991
|
+
const raw = (0, import_node_fs.readFileSync)(configPath, "utf8");
|
|
10992
|
+
const parsed = JSON.parse(raw);
|
|
10993
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10994
|
+
return {
|
|
10995
|
+
file: {},
|
|
10996
|
+
error: `Unable to update config at ${configPath}: existing config must contain a JSON object.`
|
|
10997
|
+
};
|
|
10998
|
+
}
|
|
10999
|
+
return { file: parsed };
|
|
11000
|
+
} catch {
|
|
11001
|
+
return {
|
|
11002
|
+
file: {},
|
|
11003
|
+
error: `Unable to update config at ${configPath}: existing config is not valid JSON. Fix or remove it before retrying.`
|
|
11004
|
+
};
|
|
11005
|
+
}
|
|
11006
|
+
}
|
|
10817
11007
|
function writeConfigFile(configPath, file) {
|
|
10818
11008
|
(0, import_node_fs.writeFileSync)(configPath, `${JSON.stringify(file, null, 2)}
|
|
10819
11009
|
`, "utf8");
|
|
@@ -10833,63 +11023,46 @@ function readAuthMode(input) {
|
|
|
10833
11023
|
function defaultKeychainAccount(configPath) {
|
|
10834
11024
|
return import_node_path.default.resolve(configPath);
|
|
10835
11025
|
}
|
|
10836
|
-
function
|
|
10837
|
-
|
|
10838
|
-
|
|
11026
|
+
function defaultJiraApiTokenKeychainAccount(configPath) {
|
|
11027
|
+
return `${import_node_path.default.resolve(configPath)}:jiraApiToken`;
|
|
11028
|
+
}
|
|
11029
|
+
function valueFromPrecedence(key, flagValue, envValue, fileValue, fallback = "") {
|
|
11030
|
+
if (flagValue) {
|
|
11031
|
+
return { value: flagValue, source: "flag" };
|
|
10839
11032
|
}
|
|
10840
|
-
|
|
10841
|
-
return
|
|
10842
|
-
encoding: "utf8",
|
|
10843
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
10844
|
-
}).trim();
|
|
10845
|
-
} catch {
|
|
10846
|
-
return "";
|
|
11033
|
+
if (envValue) {
|
|
11034
|
+
return { value: envValue, source: "env" };
|
|
10847
11035
|
}
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
if (process.platform !== "darwin") {
|
|
10851
|
-
throw new Error("Keychain-backed secrets are only supported on macOS.");
|
|
11036
|
+
if (fileValue) {
|
|
11037
|
+
return { value: fileValue, source: "file" };
|
|
10852
11038
|
}
|
|
10853
|
-
(
|
|
10854
|
-
|
|
10855
|
-
}
|
|
11039
|
+
if (fallback) {
|
|
11040
|
+
return { value: fallback, source: "default" };
|
|
11041
|
+
}
|
|
11042
|
+
return { value: "", source: "default" };
|
|
10856
11043
|
}
|
|
10857
|
-
function
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
"$plain = [Console]::In.ReadToEnd()",
|
|
10861
|
-
"$bytes = [Text.Encoding]::UTF8.GetBytes($plain)",
|
|
10862
|
-
"$cipher = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
|
|
10863
|
-
"[Console]::Out.Write([Convert]::ToBase64String($cipher))"
|
|
10864
|
-
].join("; ");
|
|
10865
|
-
return (0, import_node_child_process.execFileSync)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
10866
|
-
input: value,
|
|
10867
|
-
encoding: "utf8",
|
|
10868
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
10869
|
-
}).trim();
|
|
10870
|
-
}
|
|
10871
|
-
function unprotectWindowsSecret(protectedValue) {
|
|
10872
|
-
if (!protectedValue || process.platform !== "win32") {
|
|
10873
|
-
return "";
|
|
11044
|
+
function secureAccountForKey(key, file, configPath) {
|
|
11045
|
+
if (key === "forgeAuthToken") {
|
|
11046
|
+
return toStringValue(file.forgeAuthTokenKeychainAccount) || defaultKeychainAccount(configPath);
|
|
10874
11047
|
}
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
return (0, import_node_child_process.execFileSync)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
10884
|
-
input: protectedValue,
|
|
10885
|
-
encoding: "utf8",
|
|
10886
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
10887
|
-
}).trim();
|
|
10888
|
-
} catch {
|
|
10889
|
-
return "";
|
|
11048
|
+
return toStringValue(file.jiraApiTokenKeychainAccount) || defaultJiraApiTokenKeychainAccount(configPath);
|
|
11049
|
+
}
|
|
11050
|
+
function secureLocatorForKey(key, file) {
|
|
11051
|
+
if (key === "forgeAuthToken") {
|
|
11052
|
+
return {
|
|
11053
|
+
account: toStringValue(file.forgeAuthTokenKeychainAccount) || void 0,
|
|
11054
|
+
protectedValue: toStringValue(file.forgeAuthTokenProtected) || void 0
|
|
11055
|
+
};
|
|
10890
11056
|
}
|
|
11057
|
+
return {
|
|
11058
|
+
account: toStringValue(file.jiraApiTokenKeychainAccount) || void 0,
|
|
11059
|
+
protectedValue: toStringValue(file.jiraApiTokenProtected) || void 0
|
|
11060
|
+
};
|
|
10891
11061
|
}
|
|
10892
|
-
function
|
|
11062
|
+
function secretEnvironmentName(key) {
|
|
11063
|
+
return key === "jiraApiToken" ? "JIRA_API_TOKEN" : "TESTOPS_FORGE_AUTH_TOKEN";
|
|
11064
|
+
}
|
|
11065
|
+
function resolveSecretValue(key, flagValue, envValue, fileValue, file) {
|
|
10893
11066
|
if (flagValue) {
|
|
10894
11067
|
return { value: flagValue, source: "flag" };
|
|
10895
11068
|
}
|
|
@@ -10899,11 +11072,33 @@ function valueFromPrecedence(key, flagValue, envValue, fileValue, fallback = "")
|
|
|
10899
11072
|
if (fileValue) {
|
|
10900
11073
|
return { value: fileValue, source: "file" };
|
|
10901
11074
|
}
|
|
10902
|
-
|
|
10903
|
-
|
|
11075
|
+
const secure = readSecureSecret(secureLocatorForKey(key, file));
|
|
11076
|
+
if (secure.status === "available") {
|
|
11077
|
+
return { value: secure.value, source: secure.source };
|
|
11078
|
+
}
|
|
11079
|
+
if (secure.status === "error") {
|
|
11080
|
+
return { value: "", source: "default", error: `${key}: ${secure.message}` };
|
|
10904
11081
|
}
|
|
10905
11082
|
return { value: "", source: "default" };
|
|
10906
11083
|
}
|
|
11084
|
+
function clearSecretFields(file, key) {
|
|
11085
|
+
if (key === "forgeAuthToken") {
|
|
11086
|
+
return {
|
|
11087
|
+
...file,
|
|
11088
|
+
forgeAuthToken: void 0,
|
|
11089
|
+
TESTOPS_FORGE_AUTH_TOKEN: void 0,
|
|
11090
|
+
forgeAuthTokenKeychainAccount: void 0,
|
|
11091
|
+
forgeAuthTokenProtected: void 0
|
|
11092
|
+
};
|
|
11093
|
+
}
|
|
11094
|
+
return {
|
|
11095
|
+
...file,
|
|
11096
|
+
jiraApiToken: void 0,
|
|
11097
|
+
JIRA_API_TOKEN: void 0,
|
|
11098
|
+
jiraApiTokenKeychainAccount: void 0,
|
|
11099
|
+
jiraApiTokenProtected: void 0
|
|
11100
|
+
};
|
|
11101
|
+
}
|
|
10907
11102
|
function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
10908
11103
|
const { flags, unknownFlags } = parseFlags(args);
|
|
10909
11104
|
const configPathFlag = flags["--config"] ? import_node_path.default.resolve(cwd, flags["--config"]) : "";
|
|
@@ -10942,11 +11137,12 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
|
10942
11137
|
toStringValue(env.JIRA_EMAIL),
|
|
10943
11138
|
toStringValue(file.jiraEmail ?? file.JIRA_EMAIL)
|
|
10944
11139
|
);
|
|
10945
|
-
const jiraApiTokenResolved =
|
|
11140
|
+
const jiraApiTokenResolved = resolveSecretValue(
|
|
10946
11141
|
"jiraApiToken",
|
|
10947
11142
|
toStringValue(flags["--jira-api-token"]),
|
|
10948
11143
|
toStringValue(env.JIRA_API_TOKEN),
|
|
10949
|
-
|
|
11144
|
+
firstStringValue(file.jiraApiToken, file.JIRA_API_TOKEN),
|
|
11145
|
+
file
|
|
10950
11146
|
);
|
|
10951
11147
|
const forgeEndpointResolved = valueFromPrecedence(
|
|
10952
11148
|
"forgeEndpoint",
|
|
@@ -10960,11 +11156,13 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
|
10960
11156
|
"",
|
|
10961
11157
|
toStringValue(file.forgeAuthTokenKeychainAccount)
|
|
10962
11158
|
);
|
|
10963
|
-
const
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
11159
|
+
const forgeAuthTokenResolved = resolveSecretValue(
|
|
11160
|
+
"forgeAuthToken",
|
|
11161
|
+
"",
|
|
11162
|
+
toStringValue(env.TESTOPS_FORGE_AUTH_TOKEN),
|
|
11163
|
+
firstStringValue(file.forgeAuthToken, file.TESTOPS_FORGE_AUTH_TOKEN),
|
|
11164
|
+
file
|
|
11165
|
+
);
|
|
10968
11166
|
const config = {
|
|
10969
11167
|
baseUrl: normalizeBaseUrl(baseUrlResolved.value),
|
|
10970
11168
|
projectKey: projectKeyResolved.value,
|
|
@@ -10990,13 +11188,16 @@ function resolveCliConfig(args, env = process.env, cwd = process.cwd()) {
|
|
|
10990
11188
|
configPath: configPathSource
|
|
10991
11189
|
};
|
|
10992
11190
|
const warnings = [];
|
|
11191
|
+
const errors = [jiraApiTokenResolved.error, forgeAuthTokenResolved.error].filter(
|
|
11192
|
+
(value) => Boolean(value)
|
|
11193
|
+
);
|
|
10993
11194
|
if (unknownFlags.length > 0) {
|
|
10994
11195
|
warnings.push(`Ignored unknown config flags: ${unknownFlags.join(", ")}`);
|
|
10995
11196
|
}
|
|
10996
11197
|
if (!(0, import_node_fs.existsSync)(configPath) && sources.configPath !== "default") {
|
|
10997
11198
|
warnings.push(`Config file not found at ${configPath}; using env/flags/defaults.`);
|
|
10998
11199
|
}
|
|
10999
|
-
return { values: config, sources, warnings };
|
|
11200
|
+
return { values: config, sources, warnings, errors };
|
|
11000
11201
|
}
|
|
11001
11202
|
function maskSecret(value) {
|
|
11002
11203
|
if (!value) {
|
|
@@ -11009,7 +11210,7 @@ function maskSecret(value) {
|
|
|
11009
11210
|
}
|
|
11010
11211
|
function validateResolvedConfig(resolution) {
|
|
11011
11212
|
const { values } = resolution;
|
|
11012
|
-
const errors = [];
|
|
11213
|
+
const errors = [...resolution.errors];
|
|
11013
11214
|
const warnings = [...resolution.warnings];
|
|
11014
11215
|
if (!values.baseUrl) {
|
|
11015
11216
|
errors.push("Missing required setting: JIRA_BASE_URL (flag/env/file).");
|
|
@@ -11024,10 +11225,8 @@ function validateResolvedConfig(resolution) {
|
|
|
11024
11225
|
if (!values.jiraApiToken) {
|
|
11025
11226
|
errors.push("Auth mode api-token requires JIRA_API_TOKEN.");
|
|
11026
11227
|
}
|
|
11027
|
-
} else {
|
|
11028
|
-
|
|
11029
|
-
warnings.push("JIRA_EMAIL/JIRA_API_TOKEN were provided but auth mode is 'none'.");
|
|
11030
|
-
}
|
|
11228
|
+
} else if (values.jiraApiToken || values.jiraEmail) {
|
|
11229
|
+
warnings.push("JIRA_EMAIL/JIRA_API_TOKEN were provided but auth mode is 'none'.");
|
|
11031
11230
|
}
|
|
11032
11231
|
return { ok: errors.length === 0, errors, warnings };
|
|
11033
11232
|
}
|
|
@@ -11090,53 +11289,33 @@ function applyConfigSet(configPath, rawKey, value) {
|
|
|
11090
11289
|
]
|
|
11091
11290
|
};
|
|
11092
11291
|
}
|
|
11093
|
-
const
|
|
11094
|
-
if (
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
stdout: ["Config updated: forgeAuthToken protected with Windows DPAPI for the current user."]
|
|
11108
|
-
};
|
|
11109
|
-
} catch (error) {
|
|
11110
|
-
return {
|
|
11111
|
-
exitCode: ExitCode.ValidationError,
|
|
11112
|
-
stderr: [
|
|
11113
|
-
`Unable to store forgeAuthToken securely: ${error instanceof Error ? error.message : "unknown secure-storage error"}`
|
|
11114
|
-
]
|
|
11115
|
-
};
|
|
11116
|
-
}
|
|
11117
|
-
}
|
|
11118
|
-
if (process.platform !== "darwin") {
|
|
11292
|
+
const mutable = readConfigFileForMutation(configPath);
|
|
11293
|
+
if (mutable.error) {
|
|
11294
|
+
return { exitCode: ExitCode.ValidationError, stderr: [mutable.error] };
|
|
11295
|
+
}
|
|
11296
|
+
const file = mutable.file;
|
|
11297
|
+
if (key === "forgeAuthToken" || key === "jiraApiToken") {
|
|
11298
|
+
const secretKey = key;
|
|
11299
|
+
const account = secureAccountForKey(secretKey, file, configPath);
|
|
11300
|
+
try {
|
|
11301
|
+
const stored = writeSecureSecret(account, value);
|
|
11302
|
+
const cleared = clearSecretFields(file, secretKey);
|
|
11303
|
+
const nextFile2 = stored.source === "keychain" ? secretKey === "forgeAuthToken" ? { ...cleared, forgeAuthTokenKeychainAccount: stored.account } : { ...cleared, jiraApiTokenKeychainAccount: stored.account } : secretKey === "forgeAuthToken" ? { ...cleared, forgeAuthTokenProtected: stored.protectedValue } : { ...cleared, jiraApiTokenProtected: stored.protectedValue };
|
|
11304
|
+
writeConfigFile(configPath, nextFile2);
|
|
11305
|
+
const backend = stored.source === "keychain" ? "macOS Keychain" : "Windows DPAPI for the current user";
|
|
11119
11306
|
return {
|
|
11120
|
-
exitCode: ExitCode.
|
|
11307
|
+
exitCode: ExitCode.Success,
|
|
11308
|
+
stdout: [`Config updated: ${secretKey} stored securely with ${backend}.`]
|
|
11309
|
+
};
|
|
11310
|
+
} catch (error) {
|
|
11311
|
+
const unsupported = process.platform !== "darwin" && process.platform !== "win32";
|
|
11312
|
+
return {
|
|
11313
|
+
exitCode: unsupported ? ExitCode.UsageError : ExitCode.ValidationError,
|
|
11121
11314
|
stderr: [
|
|
11122
|
-
|
|
11315
|
+
`Unable to store ${secretKey} securely: ${error instanceof Error ? error.message : "unknown secure-storage error"} Use ${secretEnvironmentName(secretKey)} on Linux/CI.`
|
|
11123
11316
|
]
|
|
11124
11317
|
};
|
|
11125
11318
|
}
|
|
11126
|
-
const account = toStringValue(file.forgeAuthTokenKeychainAccount) || defaultKeychainAccount(configPath);
|
|
11127
|
-
setKeychainSecret(account, value);
|
|
11128
|
-
const nextFile2 = {
|
|
11129
|
-
...file,
|
|
11130
|
-
forgeAuthToken: void 0,
|
|
11131
|
-
TESTOPS_FORGE_AUTH_TOKEN: void 0,
|
|
11132
|
-
forgeAuthTokenProtected: void 0,
|
|
11133
|
-
forgeAuthTokenKeychainAccount: account
|
|
11134
|
-
};
|
|
11135
|
-
writeConfigFile(configPath, nextFile2);
|
|
11136
|
-
return {
|
|
11137
|
-
exitCode: ExitCode.Success,
|
|
11138
|
-
stdout: [`Config updated: forgeAuthToken stored in macOS Keychain (${account}).`]
|
|
11139
|
-
};
|
|
11140
11319
|
}
|
|
11141
11320
|
const nextFile = {
|
|
11142
11321
|
...file,
|
|
@@ -11148,6 +11327,101 @@ function applyConfigSet(configPath, rawKey, value) {
|
|
|
11148
11327
|
stdout: [`Config updated: ${rawKey}.`]
|
|
11149
11328
|
};
|
|
11150
11329
|
}
|
|
11330
|
+
function configFileKeysFor(key) {
|
|
11331
|
+
switch (key) {
|
|
11332
|
+
case "baseUrl":
|
|
11333
|
+
return ["baseUrl", "JIRA_BASE_URL"];
|
|
11334
|
+
case "projectKey":
|
|
11335
|
+
return ["projectKey", "JIRA_PROJECT_KEY"];
|
|
11336
|
+
case "issueKey":
|
|
11337
|
+
return ["issueKey", "JIRA_ISSUE_KEY"];
|
|
11338
|
+
case "authMode":
|
|
11339
|
+
return ["authMode", "TESTOPS_AUTH_MODE"];
|
|
11340
|
+
case "jiraEmail":
|
|
11341
|
+
return ["jiraEmail", "JIRA_EMAIL"];
|
|
11342
|
+
case "forgeEndpoint":
|
|
11343
|
+
return ["forgeEndpoint", "TESTOPS_FORGE_ENDPOINT"];
|
|
11344
|
+
default:
|
|
11345
|
+
return [key];
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11348
|
+
function applyConfigUnset(configPath, rawKey) {
|
|
11349
|
+
const key = normalizeConfigSetKey(rawKey);
|
|
11350
|
+
if (!key) {
|
|
11351
|
+
return {
|
|
11352
|
+
exitCode: ExitCode.UsageError,
|
|
11353
|
+
stderr: ["Usage: automatify testops config unset <key>"]
|
|
11354
|
+
};
|
|
11355
|
+
}
|
|
11356
|
+
const mutable = readConfigFileForMutation(configPath);
|
|
11357
|
+
if (mutable.error) {
|
|
11358
|
+
return { exitCode: ExitCode.ValidationError, stderr: [mutable.error] };
|
|
11359
|
+
}
|
|
11360
|
+
const file = mutable.file;
|
|
11361
|
+
if (key === "forgeAuthToken" || key === "jiraApiToken") {
|
|
11362
|
+
const secretKey = key;
|
|
11363
|
+
const deleted = deleteSecureSecret(secureLocatorForKey(secretKey, file));
|
|
11364
|
+
if (deleted.status === "error") {
|
|
11365
|
+
return { exitCode: ExitCode.ValidationError, stderr: [deleted.message] };
|
|
11366
|
+
}
|
|
11367
|
+
writeConfigFile(configPath, clearSecretFields(file, secretKey));
|
|
11368
|
+
return { exitCode: ExitCode.Success, stdout: [`Config removed: ${secretKey}.`] };
|
|
11369
|
+
}
|
|
11370
|
+
const next = { ...file };
|
|
11371
|
+
for (const fileKey of configFileKeysFor(key)) {
|
|
11372
|
+
delete next[fileKey];
|
|
11373
|
+
}
|
|
11374
|
+
writeConfigFile(configPath, next);
|
|
11375
|
+
return { exitCode: ExitCode.Success, stdout: [`Config removed: ${rawKey}.`] };
|
|
11376
|
+
}
|
|
11377
|
+
function applyConfigMigrateSecrets(configPath) {
|
|
11378
|
+
const mutable = readConfigFileForMutation(configPath);
|
|
11379
|
+
if (mutable.error) {
|
|
11380
|
+
return { exitCode: ExitCode.ValidationError, stderr: [mutable.error] };
|
|
11381
|
+
}
|
|
11382
|
+
const file = mutable.file;
|
|
11383
|
+
const pending = [];
|
|
11384
|
+
const jiraApiToken = firstStringValue(file.jiraApiToken, file.JIRA_API_TOKEN);
|
|
11385
|
+
const forgeAuthToken = firstStringValue(file.forgeAuthToken, file.TESTOPS_FORGE_AUTH_TOKEN);
|
|
11386
|
+
if (jiraApiToken) {
|
|
11387
|
+
pending.push({ key: "jiraApiToken", value: jiraApiToken });
|
|
11388
|
+
}
|
|
11389
|
+
if (forgeAuthToken) {
|
|
11390
|
+
pending.push({ key: "forgeAuthToken", value: forgeAuthToken });
|
|
11391
|
+
}
|
|
11392
|
+
if (pending.length === 0) {
|
|
11393
|
+
return { exitCode: ExitCode.Success, stdout: ["No plaintext local secrets require migration."] };
|
|
11394
|
+
}
|
|
11395
|
+
const migrated = [];
|
|
11396
|
+
for (const item of pending) {
|
|
11397
|
+
const response = applyConfigSet(configPath, item.key, item.value);
|
|
11398
|
+
if (response.exitCode !== ExitCode.Success) {
|
|
11399
|
+
return {
|
|
11400
|
+
exitCode: response.exitCode,
|
|
11401
|
+
stdout: migrated.length > 0 ? [`Migrated before failure: ${migrated.join(", ")}.`] : void 0,
|
|
11402
|
+
stderr: response.stderr
|
|
11403
|
+
};
|
|
11404
|
+
}
|
|
11405
|
+
migrated.push(item.key);
|
|
11406
|
+
}
|
|
11407
|
+
return {
|
|
11408
|
+
exitCode: ExitCode.Success,
|
|
11409
|
+
stdout: [`Migrated plaintext secrets to secure storage: ${migrated.join(", ")}.`]
|
|
11410
|
+
};
|
|
11411
|
+
}
|
|
11412
|
+
function stripConfigPathArgs(args) {
|
|
11413
|
+
const operands = [];
|
|
11414
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
11415
|
+
if (args[i] === "--config") {
|
|
11416
|
+
if (args[i + 1] && !args[i + 1].startsWith("--")) {
|
|
11417
|
+
i += 1;
|
|
11418
|
+
}
|
|
11419
|
+
continue;
|
|
11420
|
+
}
|
|
11421
|
+
operands.push(args[i]);
|
|
11422
|
+
}
|
|
11423
|
+
return operands;
|
|
11424
|
+
}
|
|
11151
11425
|
function createConfigHandler(env = process.env, cwd = process.cwd()) {
|
|
11152
11426
|
return (request) => {
|
|
11153
11427
|
const [subcommand, ...subArgs] = request.args;
|
|
@@ -11156,7 +11430,11 @@ function createConfigHandler(env = process.env, cwd = process.cwd()) {
|
|
|
11156
11430
|
if (subcommand === "show") {
|
|
11157
11431
|
return {
|
|
11158
11432
|
exitCode: ExitCode.Success,
|
|
11159
|
-
stdout: [
|
|
11433
|
+
stdout: [
|
|
11434
|
+
...toDisplayLines(resolution),
|
|
11435
|
+
...validation.warnings.map((line) => `WARN: ${line}`),
|
|
11436
|
+
...resolution.errors.map((line) => `ERROR: ${line}`)
|
|
11437
|
+
]
|
|
11160
11438
|
};
|
|
11161
11439
|
}
|
|
11162
11440
|
if (subcommand === "validate") {
|
|
@@ -11177,17 +11455,25 @@ function createConfigHandler(env = process.env, cwd = process.cwd()) {
|
|
|
11177
11455
|
};
|
|
11178
11456
|
}
|
|
11179
11457
|
if (subcommand === "set") {
|
|
11180
|
-
const [key, ...valueParts] = subArgs;
|
|
11181
|
-
const
|
|
11458
|
+
const [key, ...valueParts] = stripConfigPathArgs(subArgs);
|
|
11459
|
+
const normalizedKey = normalizeConfigSetKey(key ?? "");
|
|
11460
|
+
const readValueFromStdin = (normalizedKey === "forgeAuthToken" || normalizedKey === "jiraApiToken") && valueParts.length === 1 && valueParts[0] === "--stdin";
|
|
11182
11461
|
const value = readValueFromStdin ? (0, import_node_fs.readFileSync)(0, "utf8").trim() : valueParts.join(" ").trim();
|
|
11183
11462
|
if (!key || !value) {
|
|
11184
11463
|
return {
|
|
11185
11464
|
exitCode: ExitCode.UsageError,
|
|
11186
|
-
stderr: ["Usage: automatify testops config set <key> <value>"]
|
|
11465
|
+
stderr: ["Usage: automatify testops config set <key> <value|--stdin>"]
|
|
11187
11466
|
};
|
|
11188
11467
|
}
|
|
11189
11468
|
return applyConfigSet(resolution.values.configPath, key, value);
|
|
11190
11469
|
}
|
|
11470
|
+
if (subcommand === "unset") {
|
|
11471
|
+
const [key] = stripConfigPathArgs(subArgs);
|
|
11472
|
+
return applyConfigUnset(resolution.values.configPath, key ?? "");
|
|
11473
|
+
}
|
|
11474
|
+
if (subcommand === "migrate-secrets") {
|
|
11475
|
+
return applyConfigMigrateSecrets(resolution.values.configPath);
|
|
11476
|
+
}
|
|
11191
11477
|
return {
|
|
11192
11478
|
exitCode: ExitCode.UsageError,
|
|
11193
11479
|
stderr: [`Unsupported config subcommand: ${subcommand}`]
|
|
@@ -13627,33 +13913,280 @@ function createAllureHandler(deps = {}) {
|
|
|
13627
13913
|
};
|
|
13628
13914
|
}
|
|
13629
13915
|
|
|
13630
|
-
// src/
|
|
13916
|
+
// src/auth.ts
|
|
13631
13917
|
var import_node_fs10 = require("node:fs");
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13918
|
+
function parseAuthArgs(args, allowLoginFlags) {
|
|
13919
|
+
const configArgs = [];
|
|
13920
|
+
const unknown = [];
|
|
13921
|
+
let baseUrl = "";
|
|
13922
|
+
let jiraEmail = "";
|
|
13923
|
+
let tokenStdin = false;
|
|
13924
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
13925
|
+
const token = args[i];
|
|
13926
|
+
if (token === "--config") {
|
|
13927
|
+
const value = args[i + 1];
|
|
13928
|
+
if (!value || value.startsWith("--")) {
|
|
13929
|
+
unknown.push(token);
|
|
13930
|
+
continue;
|
|
13931
|
+
}
|
|
13932
|
+
configArgs.push(token, value);
|
|
13933
|
+
i += 1;
|
|
13934
|
+
continue;
|
|
13935
|
+
}
|
|
13936
|
+
if (allowLoginFlags && token === "--base-url") {
|
|
13937
|
+
const value = args[i + 1];
|
|
13938
|
+
if (!value || value.startsWith("--")) {
|
|
13939
|
+
unknown.push(token);
|
|
13940
|
+
continue;
|
|
13941
|
+
}
|
|
13942
|
+
baseUrl = value.trim();
|
|
13943
|
+
i += 1;
|
|
13944
|
+
continue;
|
|
13945
|
+
}
|
|
13946
|
+
if (allowLoginFlags && token === "--jira-email") {
|
|
13947
|
+
const value = args[i + 1];
|
|
13948
|
+
if (!value || value.startsWith("--")) {
|
|
13949
|
+
unknown.push(token);
|
|
13950
|
+
continue;
|
|
13951
|
+
}
|
|
13952
|
+
jiraEmail = value.trim();
|
|
13953
|
+
i += 1;
|
|
13954
|
+
continue;
|
|
13955
|
+
}
|
|
13956
|
+
if (allowLoginFlags && token === "--token-stdin") {
|
|
13957
|
+
tokenStdin = true;
|
|
13958
|
+
continue;
|
|
13959
|
+
}
|
|
13960
|
+
unknown.push(token);
|
|
13646
13961
|
}
|
|
13647
|
-
};
|
|
13648
|
-
function isObject(value) {
|
|
13649
|
-
return Boolean(value) && typeof value === "object";
|
|
13962
|
+
return { configArgs, baseUrl, jiraEmail, tokenStdin, unknown };
|
|
13650
13963
|
}
|
|
13651
|
-
function
|
|
13652
|
-
|
|
13653
|
-
|
|
13964
|
+
function runConfigMutation(handler, args, context) {
|
|
13965
|
+
const response = handler(
|
|
13966
|
+
{
|
|
13967
|
+
group: "config",
|
|
13968
|
+
args,
|
|
13969
|
+
rawArgs: ["config", ...args]
|
|
13970
|
+
},
|
|
13971
|
+
context
|
|
13972
|
+
);
|
|
13973
|
+
if (response instanceof Promise) {
|
|
13974
|
+
throw new Error("Config mutations must remain synchronous.");
|
|
13975
|
+
}
|
|
13976
|
+
return response;
|
|
13977
|
+
}
|
|
13978
|
+
function sourceLabel(source) {
|
|
13979
|
+
switch (source) {
|
|
13980
|
+
case "keychain":
|
|
13981
|
+
return "macOS Keychain";
|
|
13982
|
+
case "secure-store":
|
|
13983
|
+
return "Windows DPAPI";
|
|
13984
|
+
case "env":
|
|
13985
|
+
return "environment";
|
|
13986
|
+
case "file":
|
|
13987
|
+
return "legacy plaintext config";
|
|
13988
|
+
case "flag":
|
|
13989
|
+
return "command flag";
|
|
13990
|
+
default:
|
|
13991
|
+
return "not configured";
|
|
13654
13992
|
}
|
|
13655
|
-
|
|
13656
|
-
|
|
13993
|
+
}
|
|
13994
|
+
function authState(parts) {
|
|
13995
|
+
const populated = parts.filter(Boolean).length;
|
|
13996
|
+
if (populated === 0) {
|
|
13997
|
+
return "not configured";
|
|
13998
|
+
}
|
|
13999
|
+
return populated === parts.length ? "configured" : "incomplete";
|
|
14000
|
+
}
|
|
14001
|
+
function createStatusResponse(args, env, cwd) {
|
|
14002
|
+
const parsed = parseAuthArgs(args, false);
|
|
14003
|
+
if (parsed.unknown.length > 0) {
|
|
14004
|
+
return {
|
|
14005
|
+
exitCode: ExitCode.UsageError,
|
|
14006
|
+
stderr: ["Unsupported auth status arguments. Only --config <path> is supported."]
|
|
14007
|
+
};
|
|
14008
|
+
}
|
|
14009
|
+
const resolution = resolveCliConfig(parsed.configArgs, env, cwd);
|
|
14010
|
+
const { values, sources } = resolution;
|
|
14011
|
+
const jiraState = authState([values.baseUrl, values.jiraEmail, values.jiraApiToken]);
|
|
14012
|
+
const forgeState = authState([values.forgeEndpoint, values.forgeAuthToken]);
|
|
14013
|
+
const stdout = [
|
|
14014
|
+
"Auth status (secret-safe):",
|
|
14015
|
+
` Jira: ${jiraState}`,
|
|
14016
|
+
` baseUrl: ${values.baseUrl || "<unset>"}`,
|
|
14017
|
+
` email: ${values.jiraEmail || "<unset>"}`,
|
|
14018
|
+
` apiToken: ${values.jiraApiToken ? `configured (${sourceLabel(sources.jiraApiToken)})` : "<unset>"}`,
|
|
14019
|
+
` Forge transport: ${forgeState}`,
|
|
14020
|
+
` endpoint: ${values.forgeEndpoint || "<unset>"}`,
|
|
14021
|
+
` authToken: ${values.forgeAuthToken ? `configured (${sourceLabel(sources.forgeAuthToken)})` : "<unset>"}`,
|
|
14022
|
+
` configPath: ${values.configPath}`
|
|
14023
|
+
];
|
|
14024
|
+
if (resolution.errors.length > 0) {
|
|
14025
|
+
return {
|
|
14026
|
+
exitCode: ExitCode.ValidationError,
|
|
14027
|
+
stdout,
|
|
14028
|
+
stderr: resolution.errors.map((error) => `ERROR: ${error}`)
|
|
14029
|
+
};
|
|
14030
|
+
}
|
|
14031
|
+
return { exitCode: ExitCode.Success, stdout };
|
|
14032
|
+
}
|
|
14033
|
+
function createLoginResponse(args, env, cwd, readStdin, platform, context) {
|
|
14034
|
+
const parsed = parseAuthArgs(args, true);
|
|
14035
|
+
if (parsed.unknown.length > 0) {
|
|
14036
|
+
return {
|
|
14037
|
+
exitCode: ExitCode.UsageError,
|
|
14038
|
+
stderr: [
|
|
14039
|
+
"Unsupported auth login arguments. Use --base-url, --jira-email, --token-stdin, and optional --config only.",
|
|
14040
|
+
"Secrets must be provided through --token-stdin or environment variables, never as command-line values."
|
|
14041
|
+
]
|
|
14042
|
+
};
|
|
14043
|
+
}
|
|
14044
|
+
const current = resolveCliConfig(parsed.configArgs, env, cwd);
|
|
14045
|
+
const baseUrl = parsed.baseUrl || env.JIRA_BASE_URL?.trim() || current.values.baseUrl;
|
|
14046
|
+
const jiraEmail = parsed.jiraEmail || env.JIRA_EMAIL?.trim() || current.values.jiraEmail;
|
|
14047
|
+
const stdinToken = parsed.tokenStdin ? readStdin().trim() : "";
|
|
14048
|
+
const effectiveToken = stdinToken || env.JIRA_API_TOKEN?.trim() || current.values.jiraApiToken;
|
|
14049
|
+
const missing = [];
|
|
14050
|
+
if (!baseUrl) missing.push("--base-url or JIRA_BASE_URL");
|
|
14051
|
+
if (!jiraEmail) missing.push("--jira-email or JIRA_EMAIL");
|
|
14052
|
+
if (!effectiveToken) missing.push("--token-stdin, JIRA_API_TOKEN, or an existing stored token");
|
|
14053
|
+
if (missing.length > 0) {
|
|
14054
|
+
return {
|
|
14055
|
+
exitCode: ExitCode.UsageError,
|
|
14056
|
+
stderr: [
|
|
14057
|
+
`Jira login is incomplete. Provide: ${missing.join(", ")}.`,
|
|
14058
|
+
"Secrets are never accepted as normal command-line values."
|
|
14059
|
+
]
|
|
14060
|
+
};
|
|
14061
|
+
}
|
|
14062
|
+
if (parsed.tokenStdin && platform !== "darwin" && platform !== "win32") {
|
|
14063
|
+
return {
|
|
14064
|
+
exitCode: ExitCode.UsageError,
|
|
14065
|
+
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."
|
|
14068
|
+
]
|
|
14069
|
+
};
|
|
14070
|
+
}
|
|
14071
|
+
const configHandler = createConfigHandler(env, cwd);
|
|
14072
|
+
const configSuffix = parsed.configArgs;
|
|
14073
|
+
const steps = [];
|
|
14074
|
+
if (stdinToken) {
|
|
14075
|
+
steps.push({ label: "Jira API token", args: ["set", "jiraApiToken", stdinToken, ...configSuffix] });
|
|
14076
|
+
}
|
|
14077
|
+
steps.push(
|
|
14078
|
+
{ label: "Jira base URL", args: ["set", "baseUrl", baseUrl, ...configSuffix] },
|
|
14079
|
+
{ label: "Jira email", args: ["set", "jiraEmail", jiraEmail, ...configSuffix] },
|
|
14080
|
+
{ label: "auth mode", args: ["set", "authMode", "api-token", ...configSuffix] }
|
|
14081
|
+
);
|
|
14082
|
+
const completed = [];
|
|
14083
|
+
for (const step of steps) {
|
|
14084
|
+
const response = runConfigMutation(configHandler, step.args, context);
|
|
14085
|
+
if (response.exitCode !== ExitCode.Success) {
|
|
14086
|
+
return {
|
|
14087
|
+
exitCode: response.exitCode,
|
|
14088
|
+
stdout: completed.length > 0 ? [`Configured before failure: ${completed.join(", ")}.`] : void 0,
|
|
14089
|
+
stderr: response.stderr
|
|
14090
|
+
};
|
|
14091
|
+
}
|
|
14092
|
+
completed.push(step.label);
|
|
14093
|
+
}
|
|
14094
|
+
const tokenSource = stdinToken ? platform === "darwin" ? "macOS Keychain" : "Windows DPAPI" : sourceLabel(
|
|
14095
|
+
current.sources.jiraApiToken === "default" && env.JIRA_API_TOKEN ? "env" : current.sources.jiraApiToken
|
|
14096
|
+
);
|
|
14097
|
+
return {
|
|
14098
|
+
exitCode: ExitCode.Success,
|
|
14099
|
+
stdout: [
|
|
14100
|
+
"Jira login configured.",
|
|
14101
|
+
` baseUrl: ${baseUrl.replace(/\/$/, "")}`,
|
|
14102
|
+
` email: ${jiraEmail}`,
|
|
14103
|
+
` apiToken: configured (${tokenSource})`,
|
|
14104
|
+
"Run `automatify testops auth status` to verify the resolved credentials."
|
|
14105
|
+
]
|
|
14106
|
+
};
|
|
14107
|
+
}
|
|
14108
|
+
function createLogoutResponse(args, env, cwd, context) {
|
|
14109
|
+
const parsed = parseAuthArgs(args, false);
|
|
14110
|
+
if (parsed.unknown.length > 0) {
|
|
14111
|
+
return {
|
|
14112
|
+
exitCode: ExitCode.UsageError,
|
|
14113
|
+
stderr: ["Unsupported auth logout arguments. Only --config <path> is supported."]
|
|
14114
|
+
};
|
|
14115
|
+
}
|
|
14116
|
+
const configHandler = createConfigHandler(env, cwd);
|
|
14117
|
+
const failures = [];
|
|
14118
|
+
const removed = [];
|
|
14119
|
+
for (const key of ["jiraApiToken", "forgeAuthToken"]) {
|
|
14120
|
+
const response = runConfigMutation(configHandler, ["unset", key, ...parsed.configArgs], context);
|
|
14121
|
+
if (response.exitCode === ExitCode.Success) {
|
|
14122
|
+
removed.push(key);
|
|
14123
|
+
} else {
|
|
14124
|
+
failures.push(...response.stderr ?? [`Unable to remove ${key}.`]);
|
|
14125
|
+
}
|
|
14126
|
+
}
|
|
14127
|
+
if (failures.length > 0) {
|
|
14128
|
+
return {
|
|
14129
|
+
exitCode: ExitCode.ValidationError,
|
|
14130
|
+
stdout: removed.length > 0 ? [`Removed before failure: ${removed.join(", ")}.`] : void 0,
|
|
14131
|
+
stderr: failures
|
|
14132
|
+
};
|
|
14133
|
+
}
|
|
14134
|
+
const stdout = ["Local Jira and Forge credentials removed.", "Non-secret URL/email settings were preserved."];
|
|
14135
|
+
if (env.JIRA_API_TOKEN || env.TESTOPS_FORGE_AUTH_TOKEN) {
|
|
14136
|
+
stdout.push("Environment-provided credentials are unchanged and may still resolve as configured.");
|
|
14137
|
+
}
|
|
14138
|
+
return { exitCode: ExitCode.Success, stdout };
|
|
14139
|
+
}
|
|
14140
|
+
function createAuthHandler(deps = {}) {
|
|
14141
|
+
const env = deps.env ?? process.env;
|
|
14142
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
14143
|
+
const readStdin = deps.readStdin ?? (() => (0, import_node_fs10.readFileSync)(0, "utf8"));
|
|
14144
|
+
const platform = deps.platform ?? process.platform;
|
|
14145
|
+
return (request, context) => {
|
|
14146
|
+
const [subcommand, ...args] = request.args;
|
|
14147
|
+
if (subcommand === "status") {
|
|
14148
|
+
return createStatusResponse(args, env, cwd);
|
|
14149
|
+
}
|
|
14150
|
+
if (subcommand === "login") {
|
|
14151
|
+
return createLoginResponse(args, env, cwd, readStdin, platform, context);
|
|
14152
|
+
}
|
|
14153
|
+
if (subcommand === "logout") {
|
|
14154
|
+
return createLogoutResponse(args, env, cwd, context);
|
|
14155
|
+
}
|
|
14156
|
+
return {
|
|
14157
|
+
exitCode: ExitCode.UsageError,
|
|
14158
|
+
stderr: ["Unsupported auth subcommand. Use login, status, or logout."]
|
|
14159
|
+
};
|
|
14160
|
+
};
|
|
14161
|
+
}
|
|
14162
|
+
|
|
14163
|
+
// src/bdd.ts
|
|
14164
|
+
var import_node_fs11 = require("node:fs");
|
|
14165
|
+
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
14166
|
+
var import_yazl = __toESM(require_yazl(), 1);
|
|
14167
|
+
|
|
14168
|
+
// src/forgeClient.ts
|
|
14169
|
+
var ForgeClientError = class extends Error {
|
|
14170
|
+
code;
|
|
14171
|
+
status;
|
|
14172
|
+
retryable;
|
|
14173
|
+
constructor(input) {
|
|
14174
|
+
super(input.message);
|
|
14175
|
+
this.name = "ForgeClientError";
|
|
14176
|
+
this.code = input.code;
|
|
14177
|
+
this.status = input.status;
|
|
14178
|
+
this.retryable = Boolean(input.retryable);
|
|
14179
|
+
}
|
|
14180
|
+
};
|
|
14181
|
+
function isObject(value) {
|
|
14182
|
+
return Boolean(value) && typeof value === "object";
|
|
14183
|
+
}
|
|
14184
|
+
function isServiceError(value) {
|
|
14185
|
+
if (!isObject(value)) {
|
|
14186
|
+
return false;
|
|
14187
|
+
}
|
|
14188
|
+
const code = value.code;
|
|
14189
|
+
const message = value.message;
|
|
13657
14190
|
return typeof code === "string" && typeof message === "string";
|
|
13658
14191
|
}
|
|
13659
14192
|
function normalizeServiceError(error) {
|
|
@@ -14053,7 +14586,7 @@ function toJsonExportManifestItems(items) {
|
|
|
14053
14586
|
async function defaultCreateZipArchive(archivePath, entries) {
|
|
14054
14587
|
await new Promise((resolve, reject) => {
|
|
14055
14588
|
const zip = new import_yazl.ZipFile();
|
|
14056
|
-
const output = zip.outputStream.pipe((0,
|
|
14589
|
+
const output = zip.outputStream.pipe((0, import_node_fs11.createWriteStream)(archivePath));
|
|
14057
14590
|
output.on("close", () => resolve());
|
|
14058
14591
|
output.on("error", reject);
|
|
14059
14592
|
zip.outputStream.on("error", reject);
|
|
@@ -14083,10 +14616,10 @@ function missingProjectResponse() {
|
|
|
14083
14616
|
function createBddHandler(deps = {}) {
|
|
14084
14617
|
const cwd = deps.cwd ?? process.cwd();
|
|
14085
14618
|
const env = deps.env ?? process.env;
|
|
14086
|
-
const mkdir = deps.mkdir ?? ((targetPath, options) => (0,
|
|
14087
|
-
const readFile = deps.readFile ?? ((filePath) => (0,
|
|
14088
|
-
const readDir = deps.readDir ?? ((dirPath) => (0,
|
|
14089
|
-
const writeFile = deps.writeFile ?? ((filePath, content) => (0,
|
|
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"));
|
|
14090
14623
|
const createZipArchive = deps.createZipArchive ?? defaultCreateZipArchive;
|
|
14091
14624
|
return async (request, context) => {
|
|
14092
14625
|
const [subcommand, ...restArgs] = request.args;
|
|
@@ -15179,9 +15712,10 @@ function resolveAllureJiraConfig(parsed, env, cwd) {
|
|
|
15179
15712
|
const resolution = resolveCliConfig(pickConfigArgs5(parsed), env, cwd);
|
|
15180
15713
|
const site = normalizeSite3(parsed.flags["--site"] ?? env.JIRA_SITE ?? resolution.values.baseUrl);
|
|
15181
15714
|
const email = (parsed.flags["--email"] ?? resolution.values.jiraEmail).trim();
|
|
15182
|
-
const
|
|
15715
|
+
const directApiToken = (parsed.flags["--api-token"] ?? "").trim();
|
|
15716
|
+
const apiToken = (directApiToken || resolution.values.jiraApiToken).trim();
|
|
15183
15717
|
const issueKey = (parsed.flags["--issue-key"] ?? "").trim();
|
|
15184
|
-
const errors = [];
|
|
15718
|
+
const errors = directApiToken ? [] : resolution.errors.filter((line) => line.startsWith("jiraApiToken:"));
|
|
15185
15719
|
if (!hasValue(site)) {
|
|
15186
15720
|
errors.push("Missing Jira site. Pass --site, set JIRA_SITE/JIRA_BASE_URL, or configure baseUrl.");
|
|
15187
15721
|
}
|
|
@@ -15420,7 +15954,7 @@ function createDoctorHandler(deps = {}) {
|
|
|
15420
15954
|
}
|
|
15421
15955
|
|
|
15422
15956
|
// src/ingestFeature.ts
|
|
15423
|
-
var
|
|
15957
|
+
var import_node_fs12 = require("node:fs");
|
|
15424
15958
|
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
15425
15959
|
var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
|
|
15426
15960
|
"--config",
|
|
@@ -15507,8 +16041,8 @@ function normalizeError5(error) {
|
|
|
15507
16041
|
return "Unknown ingest error.";
|
|
15508
16042
|
}
|
|
15509
16043
|
function createIngestFeatureHandler(deps = {}) {
|
|
15510
|
-
const readFile = deps.readFile ?? ((filePath) => (0,
|
|
15511
|
-
const readStdin = deps.readStdin ?? (() => (0,
|
|
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"));
|
|
15512
16046
|
const cwd = deps.cwd ?? process.cwd();
|
|
15513
16047
|
const env = deps.env ?? process.env;
|
|
15514
16048
|
return async (request, context) => {
|
|
@@ -15571,75 +16105,573 @@ function createIngestFeatureHandler(deps = {}) {
|
|
|
15571
16105
|
name,
|
|
15572
16106
|
gherkin
|
|
15573
16107
|
}
|
|
15574
|
-
};
|
|
15575
|
-
try {
|
|
15576
|
-
const result = await context.invokeForgeContract("ingestBddFeature", payload);
|
|
15577
|
-
if (!result.ok) {
|
|
16108
|
+
};
|
|
16109
|
+
try {
|
|
16110
|
+
const result = await context.invokeForgeContract("ingestBddFeature", payload);
|
|
16111
|
+
if (!result.ok) {
|
|
16112
|
+
if (useJson) {
|
|
16113
|
+
return {
|
|
16114
|
+
exitCode: ExitCode.RemoteError,
|
|
16115
|
+
stdout: toJsonLine({
|
|
16116
|
+
action: "ingest-feature",
|
|
16117
|
+
status: "failed",
|
|
16118
|
+
featureName: name,
|
|
16119
|
+
source: useStdin ? "stdin" : sourceFile,
|
|
16120
|
+
errorCode: result.error.code,
|
|
16121
|
+
errorMessage: result.error.message
|
|
16122
|
+
})
|
|
16123
|
+
};
|
|
16124
|
+
}
|
|
16125
|
+
return {
|
|
16126
|
+
exitCode: ExitCode.RemoteError,
|
|
16127
|
+
stdout: ["Feature ingestion: FAILED", `Feature: ${name}`, `Source: ${useStdin ? "stdin" : sourceFile}`],
|
|
16128
|
+
stderr: [`ERROR: ${result.error.code}: ${result.error.message}`]
|
|
16129
|
+
};
|
|
16130
|
+
}
|
|
16131
|
+
if (useJson) {
|
|
16132
|
+
const details = summarizeSuccess(result);
|
|
16133
|
+
return {
|
|
16134
|
+
exitCode: ExitCode.Success,
|
|
16135
|
+
stdout: toJsonLine({
|
|
16136
|
+
action: "ingest-feature",
|
|
16137
|
+
status: "success",
|
|
16138
|
+
featureName: name,
|
|
16139
|
+
source: useStdin ? "stdin" : sourceFile,
|
|
16140
|
+
details
|
|
16141
|
+
})
|
|
16142
|
+
};
|
|
16143
|
+
}
|
|
16144
|
+
return {
|
|
16145
|
+
exitCode: ExitCode.Success,
|
|
16146
|
+
stdout: [
|
|
16147
|
+
"Feature ingestion: SUCCESS",
|
|
16148
|
+
`Feature: ${name}`,
|
|
16149
|
+
`Source: ${useStdin ? "stdin" : sourceFile}`,
|
|
16150
|
+
...summarizeSuccess(result)
|
|
16151
|
+
]
|
|
16152
|
+
};
|
|
16153
|
+
} catch (error) {
|
|
16154
|
+
if (useJson) {
|
|
16155
|
+
return {
|
|
16156
|
+
exitCode: ExitCode.TransportError,
|
|
16157
|
+
stdout: toJsonLine({
|
|
16158
|
+
action: "ingest-feature",
|
|
16159
|
+
status: "failed",
|
|
16160
|
+
featureName: name,
|
|
16161
|
+
source: useStdin ? "stdin" : sourceFile,
|
|
16162
|
+
errorMessage: normalizeError5(error)
|
|
16163
|
+
})
|
|
16164
|
+
};
|
|
16165
|
+
}
|
|
16166
|
+
return {
|
|
16167
|
+
exitCode: ExitCode.TransportError,
|
|
16168
|
+
stdout: ["Feature ingestion: FAILED", `Feature: ${name}`, `Source: ${useStdin ? "stdin" : sourceFile}`],
|
|
16169
|
+
stderr: [`ERROR: ${normalizeError5(error)}`]
|
|
16170
|
+
};
|
|
16171
|
+
}
|
|
16172
|
+
};
|
|
16173
|
+
}
|
|
16174
|
+
|
|
16175
|
+
// src/profiles.ts
|
|
16176
|
+
var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
|
|
16177
|
+
"--config",
|
|
16178
|
+
"--base-url",
|
|
16179
|
+
"--project-key",
|
|
16180
|
+
"--issue-key",
|
|
16181
|
+
"--auth-mode",
|
|
16182
|
+
"--jira-email",
|
|
16183
|
+
"--jira-api-token"
|
|
16184
|
+
]);
|
|
16185
|
+
function parseArgs8(args) {
|
|
16186
|
+
const flags = {};
|
|
16187
|
+
const boolFlags = /* @__PURE__ */ new Set();
|
|
16188
|
+
const unknownFlags = [];
|
|
16189
|
+
const supportedValueFlags = /* @__PURE__ */ new Set(["--id", ...CONFIG_FLAGS7]);
|
|
16190
|
+
const supportedBoolFlags = /* @__PURE__ */ new Set(["--json", "--dry-run", "--confirm", "--detach"]);
|
|
16191
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
16192
|
+
const token = args[index];
|
|
16193
|
+
if (!token.startsWith("--")) {
|
|
16194
|
+
unknownFlags.push(token);
|
|
16195
|
+
continue;
|
|
16196
|
+
}
|
|
16197
|
+
if (supportedBoolFlags.has(token)) {
|
|
16198
|
+
boolFlags.add(token);
|
|
16199
|
+
continue;
|
|
16200
|
+
}
|
|
16201
|
+
if (!supportedValueFlags.has(token)) {
|
|
16202
|
+
unknownFlags.push(token);
|
|
16203
|
+
continue;
|
|
16204
|
+
}
|
|
16205
|
+
const value = args[index + 1];
|
|
16206
|
+
if (!value || value.startsWith("--")) {
|
|
16207
|
+
unknownFlags.push(token);
|
|
16208
|
+
continue;
|
|
16209
|
+
}
|
|
16210
|
+
flags[token] = value;
|
|
16211
|
+
index += 1;
|
|
16212
|
+
}
|
|
16213
|
+
return { flags, boolFlags, unknownFlags };
|
|
16214
|
+
}
|
|
16215
|
+
function pickConfigArgs7(parsed) {
|
|
16216
|
+
const args = [];
|
|
16217
|
+
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
16218
|
+
if (CONFIG_FLAGS7.has(flag)) {
|
|
16219
|
+
args.push(flag, value);
|
|
16220
|
+
}
|
|
16221
|
+
}
|
|
16222
|
+
return args;
|
|
16223
|
+
}
|
|
16224
|
+
function normalizeError6(error) {
|
|
16225
|
+
if (error instanceof ForgeClientError) {
|
|
16226
|
+
return `${error.code}: ${error.message}`;
|
|
16227
|
+
}
|
|
16228
|
+
if (error instanceof Error) {
|
|
16229
|
+
return error.message;
|
|
16230
|
+
}
|
|
16231
|
+
return "Unknown profiles command error.";
|
|
16232
|
+
}
|
|
16233
|
+
function missingProjectResponse3() {
|
|
16234
|
+
return {
|
|
16235
|
+
exitCode: ExitCode.ValidationError,
|
|
16236
|
+
stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
|
|
16237
|
+
};
|
|
16238
|
+
}
|
|
16239
|
+
function confirmationRequiredResponse(command) {
|
|
16240
|
+
return {
|
|
16241
|
+
exitCode: ExitCode.ValidationError,
|
|
16242
|
+
stderr: [`ERROR: ${command} is destructive and requires --confirm. Use --dry-run first to preview the cleanup.`]
|
|
16243
|
+
};
|
|
16244
|
+
}
|
|
16245
|
+
function remoteErrorResponse(code, message, stdout = []) {
|
|
16246
|
+
return {
|
|
16247
|
+
exitCode: ExitCode.RemoteError,
|
|
16248
|
+
stdout,
|
|
16249
|
+
stderr: [`ERROR: ${code}: ${message}`]
|
|
16250
|
+
};
|
|
16251
|
+
}
|
|
16252
|
+
function toJsonProfile(profile) {
|
|
16253
|
+
return {
|
|
16254
|
+
id: profile.id,
|
|
16255
|
+
label: profile.label,
|
|
16256
|
+
provider: profile.provider,
|
|
16257
|
+
endpointSummary: profile.endpointSummary,
|
|
16258
|
+
enabled: profile.enabled,
|
|
16259
|
+
isProjectDefault: profile.isProjectDefault ?? false,
|
|
16260
|
+
hasSecret: profile.hasSecret,
|
|
16261
|
+
createdAt: profile.createdAt,
|
|
16262
|
+
updatedAt: profile.updatedAt,
|
|
16263
|
+
lastUsedAt: profile.lastUsedAt ?? null
|
|
16264
|
+
};
|
|
16265
|
+
}
|
|
16266
|
+
function toJsonBinding(automation) {
|
|
16267
|
+
return {
|
|
16268
|
+
id: automation.id,
|
|
16269
|
+
scenarioId: automation.scenarioId,
|
|
16270
|
+
label: automation.label,
|
|
16271
|
+
profileId: automation.profileId ?? null,
|
|
16272
|
+
profileLabel: automation.profileLabel ?? null,
|
|
16273
|
+
materializedFromProjectDefault: automation.materializedFromProjectDefault ?? false,
|
|
16274
|
+
enabled: automation.enabled
|
|
16275
|
+
};
|
|
16276
|
+
}
|
|
16277
|
+
function profileLines(profiles) {
|
|
16278
|
+
if (profiles.length === 0) {
|
|
16279
|
+
return ["Automation profiles: 0", "No automation profiles found for the selected project."];
|
|
16280
|
+
}
|
|
16281
|
+
return [
|
|
16282
|
+
`Automation profiles: ${profiles.length}`,
|
|
16283
|
+
...profiles.map((profile) => {
|
|
16284
|
+
const flags = [profile.isProjectDefault ? "default" : "", profile.enabled ? "enabled" : "disabled"].filter(
|
|
16285
|
+
Boolean
|
|
16286
|
+
);
|
|
16287
|
+
return `- ${profile.id} [${flags.join(", ")}] ${profile.label} (${profile.provider}) ${profile.endpointSummary}`;
|
|
16288
|
+
})
|
|
16289
|
+
];
|
|
16290
|
+
}
|
|
16291
|
+
function bindingLines(bindings) {
|
|
16292
|
+
return bindings.map(
|
|
16293
|
+
(binding) => `- ${binding.id} scenario=${binding.scenarioId} ${binding.label}` + (binding.materializedFromProjectDefault ? " [project-default materialized]" : "")
|
|
16294
|
+
);
|
|
16295
|
+
}
|
|
16296
|
+
function profileBindings(automations, profileId) {
|
|
16297
|
+
return automations.filter((automation) => automation.mode === "profile" && automation.profileId === profileId);
|
|
16298
|
+
}
|
|
16299
|
+
function allProfileBindings(automations) {
|
|
16300
|
+
return automations.filter((automation) => automation.mode === "profile");
|
|
16301
|
+
}
|
|
16302
|
+
function createProfilesHandler(deps = {}) {
|
|
16303
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
16304
|
+
const env = deps.env ?? process.env;
|
|
16305
|
+
return async (request, context) => {
|
|
16306
|
+
const [subcommand, ...restArgs] = request.args;
|
|
16307
|
+
const parsed = parseArgs8(restArgs);
|
|
16308
|
+
const useJson = parsed.boolFlags.has("--json");
|
|
16309
|
+
const dryRun = parsed.boolFlags.has("--dry-run");
|
|
16310
|
+
const confirmed = parsed.boolFlags.has("--confirm");
|
|
16311
|
+
const detach = parsed.boolFlags.has("--detach");
|
|
16312
|
+
const profileId = parsed.flags["--id"]?.trim() ?? "";
|
|
16313
|
+
const config = resolveCliConfig(pickConfigArgs7(parsed), env, cwd);
|
|
16314
|
+
const projectKey = config.values.projectKey;
|
|
16315
|
+
const issueKey = config.values.issueKey;
|
|
16316
|
+
const forgeContext = {
|
|
16317
|
+
projectKey: projectKey || void 0,
|
|
16318
|
+
issueKey: issueKey || void 0
|
|
16319
|
+
};
|
|
16320
|
+
if (parsed.unknownFlags.length > 0) {
|
|
16321
|
+
return {
|
|
16322
|
+
exitCode: ExitCode.UsageError,
|
|
16323
|
+
stderr: [`ERROR: Unknown or invalid arguments: ${parsed.unknownFlags.join(", ")}`]
|
|
16324
|
+
};
|
|
16325
|
+
}
|
|
16326
|
+
if (!projectKey) {
|
|
16327
|
+
return missingProjectResponse3();
|
|
16328
|
+
}
|
|
16329
|
+
if (dryRun && confirmed) {
|
|
16330
|
+
return {
|
|
16331
|
+
exitCode: ExitCode.UsageError,
|
|
16332
|
+
stderr: ["ERROR: Use either --dry-run or --confirm, not both."]
|
|
16333
|
+
};
|
|
16334
|
+
}
|
|
16335
|
+
if (subcommand === "list") {
|
|
16336
|
+
if (dryRun || confirmed || detach || profileId) {
|
|
16337
|
+
return {
|
|
16338
|
+
exitCode: ExitCode.UsageError,
|
|
16339
|
+
stderr: ["ERROR: profiles list accepts only configuration flags and optional --json."]
|
|
16340
|
+
};
|
|
16341
|
+
}
|
|
16342
|
+
try {
|
|
16343
|
+
const result = await context.invokeForgeContract("listScenarioAutomationProfiles", {
|
|
16344
|
+
context: forgeContext
|
|
16345
|
+
});
|
|
16346
|
+
if (!result.ok) {
|
|
16347
|
+
return remoteErrorResponse(result.error.code, result.error.message);
|
|
16348
|
+
}
|
|
16349
|
+
if (useJson) {
|
|
16350
|
+
return {
|
|
16351
|
+
exitCode: ExitCode.Success,
|
|
16352
|
+
stdout: toJsonLine({
|
|
16353
|
+
action: "profiles-list",
|
|
16354
|
+
projectKey,
|
|
16355
|
+
count: result.data.length,
|
|
16356
|
+
items: result.data.map(toJsonProfile)
|
|
16357
|
+
})
|
|
16358
|
+
};
|
|
16359
|
+
}
|
|
16360
|
+
return {
|
|
16361
|
+
exitCode: ExitCode.Success,
|
|
16362
|
+
stdout: profileLines(result.data)
|
|
16363
|
+
};
|
|
16364
|
+
} catch (error) {
|
|
16365
|
+
return {
|
|
16366
|
+
exitCode: ExitCode.TransportError,
|
|
16367
|
+
stderr: [`ERROR: ${normalizeError6(error)}`]
|
|
16368
|
+
};
|
|
16369
|
+
}
|
|
16370
|
+
}
|
|
16371
|
+
if (subcommand === "detach") {
|
|
16372
|
+
if (!profileId) {
|
|
16373
|
+
return {
|
|
16374
|
+
exitCode: ExitCode.ValidationError,
|
|
16375
|
+
stderr: ["ERROR: Missing required --id <PROFILE_ID> for profiles detach."]
|
|
16376
|
+
};
|
|
16377
|
+
}
|
|
16378
|
+
if (detach) {
|
|
16379
|
+
return {
|
|
16380
|
+
exitCode: ExitCode.UsageError,
|
|
16381
|
+
stderr: [
|
|
16382
|
+
"ERROR: --detach is only valid with profiles delete; profiles detach already performs that operation."
|
|
16383
|
+
]
|
|
16384
|
+
};
|
|
16385
|
+
}
|
|
16386
|
+
if (!dryRun && !confirmed) {
|
|
16387
|
+
return confirmationRequiredResponse("profiles detach");
|
|
16388
|
+
}
|
|
16389
|
+
try {
|
|
16390
|
+
const result = await context.invokeForgeContract("listScenarioAutomations", {
|
|
16391
|
+
context: forgeContext
|
|
16392
|
+
});
|
|
16393
|
+
if (!result.ok) {
|
|
16394
|
+
return remoteErrorResponse(result.error.code, result.error.message);
|
|
16395
|
+
}
|
|
16396
|
+
const bindings = profileBindings(result.data, profileId);
|
|
16397
|
+
if (dryRun) {
|
|
16398
|
+
if (useJson) {
|
|
16399
|
+
return {
|
|
16400
|
+
exitCode: ExitCode.Success,
|
|
16401
|
+
stdout: toJsonLine({
|
|
16402
|
+
action: "profiles-detach",
|
|
16403
|
+
dryRun: true,
|
|
16404
|
+
projectKey,
|
|
16405
|
+
profileId,
|
|
16406
|
+
bindingCount: bindings.length,
|
|
16407
|
+
bindings: bindings.map(toJsonBinding)
|
|
16408
|
+
})
|
|
16409
|
+
};
|
|
16410
|
+
}
|
|
16411
|
+
return {
|
|
16412
|
+
exitCode: ExitCode.Success,
|
|
16413
|
+
stdout: [
|
|
16414
|
+
`Dry run: profile ${profileId} has ${bindings.length} automation binding(s) to detach.`,
|
|
16415
|
+
...bindingLines(bindings),
|
|
16416
|
+
"No changes made."
|
|
16417
|
+
]
|
|
16418
|
+
};
|
|
16419
|
+
}
|
|
16420
|
+
const deletedIds = [];
|
|
16421
|
+
for (const binding of bindings) {
|
|
16422
|
+
const deleted = await context.invokeForgeContract("deleteScenarioAutomation", {
|
|
16423
|
+
context: forgeContext,
|
|
16424
|
+
automationId: binding.id
|
|
16425
|
+
});
|
|
16426
|
+
if (!deleted.ok) {
|
|
16427
|
+
return remoteErrorResponse(deleted.error.code, deleted.error.message, [
|
|
16428
|
+
`Detached ${deletedIds.length} of ${bindings.length} automation binding(s) before the failure.`
|
|
16429
|
+
]);
|
|
16430
|
+
}
|
|
16431
|
+
deletedIds.push(binding.id);
|
|
16432
|
+
}
|
|
15578
16433
|
if (useJson) {
|
|
15579
16434
|
return {
|
|
15580
|
-
exitCode: ExitCode.
|
|
16435
|
+
exitCode: ExitCode.Success,
|
|
15581
16436
|
stdout: toJsonLine({
|
|
15582
|
-
action: "
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
16437
|
+
action: "profiles-detach",
|
|
16438
|
+
dryRun: false,
|
|
16439
|
+
projectKey,
|
|
16440
|
+
profileId,
|
|
16441
|
+
detachedCount: deletedIds.length,
|
|
16442
|
+
detachedAutomationIds: deletedIds
|
|
15588
16443
|
})
|
|
15589
16444
|
};
|
|
15590
16445
|
}
|
|
15591
16446
|
return {
|
|
15592
|
-
exitCode: ExitCode.
|
|
15593
|
-
stdout: [
|
|
15594
|
-
|
|
16447
|
+
exitCode: ExitCode.Success,
|
|
16448
|
+
stdout: [`Detached ${deletedIds.length} automation binding(s) from profile ${profileId}.`]
|
|
16449
|
+
};
|
|
16450
|
+
} catch (error) {
|
|
16451
|
+
return {
|
|
16452
|
+
exitCode: ExitCode.TransportError,
|
|
16453
|
+
stderr: [`ERROR: ${normalizeError6(error)}`]
|
|
15595
16454
|
};
|
|
15596
16455
|
}
|
|
15597
|
-
|
|
15598
|
-
|
|
16456
|
+
}
|
|
16457
|
+
if (subcommand === "delete") {
|
|
16458
|
+
if (!profileId) {
|
|
15599
16459
|
return {
|
|
15600
|
-
exitCode: ExitCode.
|
|
15601
|
-
|
|
15602
|
-
action: "ingest-feature",
|
|
15603
|
-
status: "success",
|
|
15604
|
-
featureName: name,
|
|
15605
|
-
source: useStdin ? "stdin" : sourceFile,
|
|
15606
|
-
details
|
|
15607
|
-
})
|
|
16460
|
+
exitCode: ExitCode.ValidationError,
|
|
16461
|
+
stderr: ["ERROR: Missing required --id <PROFILE_ID> for profiles delete."]
|
|
15608
16462
|
};
|
|
15609
16463
|
}
|
|
15610
|
-
|
|
15611
|
-
|
|
15612
|
-
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15618
|
-
|
|
15619
|
-
|
|
15620
|
-
|
|
16464
|
+
if (!dryRun && !confirmed) {
|
|
16465
|
+
return confirmationRequiredResponse("profiles delete");
|
|
16466
|
+
}
|
|
16467
|
+
try {
|
|
16468
|
+
let bindings = [];
|
|
16469
|
+
if (detach || dryRun) {
|
|
16470
|
+
const automations = await context.invokeForgeContract("listScenarioAutomations", {
|
|
16471
|
+
context: forgeContext
|
|
16472
|
+
});
|
|
16473
|
+
if (!automations.ok) {
|
|
16474
|
+
return remoteErrorResponse(automations.error.code, automations.error.message);
|
|
16475
|
+
}
|
|
16476
|
+
bindings = profileBindings(automations.data, profileId);
|
|
16477
|
+
}
|
|
16478
|
+
if (dryRun) {
|
|
16479
|
+
if (useJson) {
|
|
16480
|
+
return {
|
|
16481
|
+
exitCode: ExitCode.Success,
|
|
16482
|
+
stdout: toJsonLine({
|
|
16483
|
+
action: "profiles-delete",
|
|
16484
|
+
dryRun: true,
|
|
16485
|
+
projectKey,
|
|
16486
|
+
profileId,
|
|
16487
|
+
detach,
|
|
16488
|
+
bindingCount: bindings.length,
|
|
16489
|
+
bindings: bindings.map(toJsonBinding)
|
|
16490
|
+
})
|
|
16491
|
+
};
|
|
16492
|
+
}
|
|
16493
|
+
return {
|
|
16494
|
+
exitCode: ExitCode.Success,
|
|
16495
|
+
stdout: [
|
|
16496
|
+
`Dry run: profile ${profileId} would be deleted.`,
|
|
16497
|
+
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.`,
|
|
16498
|
+
...bindingLines(bindings),
|
|
16499
|
+
"No changes made."
|
|
16500
|
+
]
|
|
16501
|
+
};
|
|
16502
|
+
}
|
|
16503
|
+
const detachedIds = [];
|
|
16504
|
+
if (detach) {
|
|
16505
|
+
for (const binding of bindings) {
|
|
16506
|
+
const deletedBinding = await context.invokeForgeContract("deleteScenarioAutomation", {
|
|
16507
|
+
context: forgeContext,
|
|
16508
|
+
automationId: binding.id
|
|
16509
|
+
});
|
|
16510
|
+
if (!deletedBinding.ok) {
|
|
16511
|
+
return remoteErrorResponse(deletedBinding.error.code, deletedBinding.error.message, [
|
|
16512
|
+
`Detached ${detachedIds.length} of ${bindings.length} automation binding(s); profile ${profileId} was not deleted.`
|
|
16513
|
+
]);
|
|
16514
|
+
}
|
|
16515
|
+
detachedIds.push(binding.id);
|
|
16516
|
+
}
|
|
16517
|
+
}
|
|
16518
|
+
const deletedProfile = await context.invokeForgeContract("deleteScenarioAutomationProfile", {
|
|
16519
|
+
context: forgeContext,
|
|
16520
|
+
profileId
|
|
16521
|
+
});
|
|
16522
|
+
if (!deletedProfile.ok) {
|
|
16523
|
+
return remoteErrorResponse(deletedProfile.error.code, deletedProfile.error.message, [
|
|
16524
|
+
`Detached ${detachedIds.length} automation binding(s) before profile deletion was rejected.`
|
|
16525
|
+
]);
|
|
16526
|
+
}
|
|
16527
|
+
if (useJson) {
|
|
16528
|
+
return {
|
|
16529
|
+
exitCode: ExitCode.Success,
|
|
16530
|
+
stdout: toJsonLine({
|
|
16531
|
+
action: "profiles-delete",
|
|
16532
|
+
dryRun: false,
|
|
16533
|
+
projectKey,
|
|
16534
|
+
profileId,
|
|
16535
|
+
detachedCount: detachedIds.length,
|
|
16536
|
+
detachedAutomationIds: detachedIds,
|
|
16537
|
+
deleted: true
|
|
16538
|
+
})
|
|
16539
|
+
};
|
|
16540
|
+
}
|
|
16541
|
+
return {
|
|
16542
|
+
exitCode: ExitCode.Success,
|
|
16543
|
+
stdout: [
|
|
16544
|
+
`Deleted automation profile ${profileId}.`,
|
|
16545
|
+
detach ? `Detached ${detachedIds.length} automation binding(s) first.` : "No automatic detach was requested."
|
|
16546
|
+
]
|
|
16547
|
+
};
|
|
16548
|
+
} catch (error) {
|
|
15621
16549
|
return {
|
|
15622
16550
|
exitCode: ExitCode.TransportError,
|
|
15623
|
-
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
16551
|
+
stderr: [`ERROR: ${normalizeError6(error)}`]
|
|
16552
|
+
};
|
|
16553
|
+
}
|
|
16554
|
+
}
|
|
16555
|
+
if (subcommand === "clear") {
|
|
16556
|
+
if (profileId || detach) {
|
|
16557
|
+
return {
|
|
16558
|
+
exitCode: ExitCode.UsageError,
|
|
16559
|
+
stderr: [
|
|
16560
|
+
"ERROR: profiles clear clears every profile and profile-based binding; do not pass --id or --detach."
|
|
16561
|
+
]
|
|
16562
|
+
};
|
|
16563
|
+
}
|
|
16564
|
+
if (!dryRun && !confirmed) {
|
|
16565
|
+
return confirmationRequiredResponse("profiles clear");
|
|
16566
|
+
}
|
|
16567
|
+
try {
|
|
16568
|
+
const [profilesResult, automationsResult] = await Promise.all([
|
|
16569
|
+
context.invokeForgeContract("listScenarioAutomationProfiles", {
|
|
16570
|
+
context: forgeContext
|
|
16571
|
+
}),
|
|
16572
|
+
context.invokeForgeContract("listScenarioAutomations", {
|
|
16573
|
+
context: forgeContext
|
|
15629
16574
|
})
|
|
16575
|
+
]);
|
|
16576
|
+
if (!profilesResult.ok) {
|
|
16577
|
+
return remoteErrorResponse(profilesResult.error.code, profilesResult.error.message);
|
|
16578
|
+
}
|
|
16579
|
+
if (!automationsResult.ok) {
|
|
16580
|
+
return remoteErrorResponse(automationsResult.error.code, automationsResult.error.message);
|
|
16581
|
+
}
|
|
16582
|
+
const profiles = profilesResult.data;
|
|
16583
|
+
const bindings = allProfileBindings(automationsResult.data);
|
|
16584
|
+
if (dryRun) {
|
|
16585
|
+
if (useJson) {
|
|
16586
|
+
return {
|
|
16587
|
+
exitCode: ExitCode.Success,
|
|
16588
|
+
stdout: toJsonLine({
|
|
16589
|
+
action: "profiles-clear",
|
|
16590
|
+
dryRun: true,
|
|
16591
|
+
projectKey,
|
|
16592
|
+
profileCount: profiles.length,
|
|
16593
|
+
bindingCount: bindings.length,
|
|
16594
|
+
profiles: profiles.map(toJsonProfile),
|
|
16595
|
+
bindings: bindings.map(toJsonBinding)
|
|
16596
|
+
})
|
|
16597
|
+
};
|
|
16598
|
+
}
|
|
16599
|
+
return {
|
|
16600
|
+
exitCode: ExitCode.Success,
|
|
16601
|
+
stdout: [
|
|
16602
|
+
`Dry run: would detach ${bindings.length} profile-based automation binding(s) and delete ${profiles.length} automation profile(s).`,
|
|
16603
|
+
...bindingLines(bindings),
|
|
16604
|
+
...profiles.map((profile) => `- profile ${profile.id} ${profile.label}`),
|
|
16605
|
+
"Direct (non-profile) scenario automations are preserved.",
|
|
16606
|
+
"No changes made."
|
|
16607
|
+
]
|
|
16608
|
+
};
|
|
16609
|
+
}
|
|
16610
|
+
const detachedIds = [];
|
|
16611
|
+
for (const binding of bindings) {
|
|
16612
|
+
const deletedBinding = await context.invokeForgeContract("deleteScenarioAutomation", {
|
|
16613
|
+
context: forgeContext,
|
|
16614
|
+
automationId: binding.id
|
|
16615
|
+
});
|
|
16616
|
+
if (!deletedBinding.ok) {
|
|
16617
|
+
return remoteErrorResponse(deletedBinding.error.code, deletedBinding.error.message, [
|
|
16618
|
+
`Detached ${detachedIds.length} of ${bindings.length} profile-based automation binding(s). No profiles were deleted after the failure.`
|
|
16619
|
+
]);
|
|
16620
|
+
}
|
|
16621
|
+
detachedIds.push(binding.id);
|
|
16622
|
+
}
|
|
16623
|
+
const deletedProfileIds = [];
|
|
16624
|
+
for (const profile of profiles) {
|
|
16625
|
+
const deletedProfile = await context.invokeForgeContract("deleteScenarioAutomationProfile", {
|
|
16626
|
+
context: forgeContext,
|
|
16627
|
+
profileId: profile.id
|
|
16628
|
+
});
|
|
16629
|
+
if (!deletedProfile.ok) {
|
|
16630
|
+
return remoteErrorResponse(deletedProfile.error.code, deletedProfile.error.message, [
|
|
16631
|
+
`Detached ${detachedIds.length} profile-based automation binding(s).`,
|
|
16632
|
+
`Deleted ${deletedProfileIds.length} of ${profiles.length} automation profile(s) before the failure.`
|
|
16633
|
+
]);
|
|
16634
|
+
}
|
|
16635
|
+
deletedProfileIds.push(profile.id);
|
|
16636
|
+
}
|
|
16637
|
+
if (useJson) {
|
|
16638
|
+
return {
|
|
16639
|
+
exitCode: ExitCode.Success,
|
|
16640
|
+
stdout: toJsonLine({
|
|
16641
|
+
action: "profiles-clear",
|
|
16642
|
+
dryRun: false,
|
|
16643
|
+
projectKey,
|
|
16644
|
+
detachedCount: detachedIds.length,
|
|
16645
|
+
detachedAutomationIds: detachedIds,
|
|
16646
|
+
deletedProfileCount: deletedProfileIds.length,
|
|
16647
|
+
deletedProfileIds
|
|
16648
|
+
})
|
|
16649
|
+
};
|
|
16650
|
+
}
|
|
16651
|
+
return {
|
|
16652
|
+
exitCode: ExitCode.Success,
|
|
16653
|
+
stdout: [
|
|
16654
|
+
`Detached ${detachedIds.length} profile-based automation binding(s).`,
|
|
16655
|
+
`Deleted ${deletedProfileIds.length} automation profile(s).`,
|
|
16656
|
+
"Direct (non-profile) scenario automations were preserved."
|
|
16657
|
+
]
|
|
16658
|
+
};
|
|
16659
|
+
} catch (error) {
|
|
16660
|
+
return {
|
|
16661
|
+
exitCode: ExitCode.TransportError,
|
|
16662
|
+
stderr: [`ERROR: ${normalizeError6(error)}`]
|
|
15630
16663
|
};
|
|
15631
16664
|
}
|
|
15632
|
-
return {
|
|
15633
|
-
exitCode: ExitCode.TransportError,
|
|
15634
|
-
stdout: ["Feature ingestion: FAILED", `Feature: ${name}`, `Source: ${useStdin ? "stdin" : sourceFile}`],
|
|
15635
|
-
stderr: [`ERROR: ${normalizeError5(error)}`]
|
|
15636
|
-
};
|
|
15637
16665
|
}
|
|
16666
|
+
return {
|
|
16667
|
+
exitCode: ExitCode.UsageError,
|
|
16668
|
+
stderr: ["ERROR: Unsupported profiles subcommand. Use: list, detach, delete, or clear."]
|
|
16669
|
+
};
|
|
15638
16670
|
};
|
|
15639
16671
|
}
|
|
15640
16672
|
|
|
15641
16673
|
// src/runUpload.ts
|
|
15642
|
-
var
|
|
16674
|
+
var import_node_fs13 = require("node:fs");
|
|
15643
16675
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
15644
16676
|
var STEP_RESULTS = [
|
|
15645
16677
|
StepResult.Passed,
|
|
@@ -15647,7 +16679,7 @@ var STEP_RESULTS = [
|
|
|
15647
16679
|
StepResult.Skipped,
|
|
15648
16680
|
StepResult.Blocked
|
|
15649
16681
|
];
|
|
15650
|
-
var
|
|
16682
|
+
var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
|
|
15651
16683
|
"--config",
|
|
15652
16684
|
"--base-url",
|
|
15653
16685
|
"--project-key",
|
|
@@ -15656,7 +16688,7 @@ var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
|
|
|
15656
16688
|
"--jira-email",
|
|
15657
16689
|
"--jira-api-token"
|
|
15658
16690
|
]);
|
|
15659
|
-
function
|
|
16691
|
+
function parseArgs9(args) {
|
|
15660
16692
|
const flags = {};
|
|
15661
16693
|
const boolFlags = /* @__PURE__ */ new Set();
|
|
15662
16694
|
const unknownFlags = [];
|
|
@@ -15667,7 +16699,7 @@ function parseArgs8(args) {
|
|
|
15667
16699
|
"--feature-name",
|
|
15668
16700
|
"--scenario-name",
|
|
15669
16701
|
"--executed-at",
|
|
15670
|
-
...
|
|
16702
|
+
...CONFIG_FLAGS8
|
|
15671
16703
|
]);
|
|
15672
16704
|
const supportedBoolFlags = /* @__PURE__ */ new Set(["--stdin", "--json"]);
|
|
15673
16705
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -15693,16 +16725,16 @@ function parseArgs8(args) {
|
|
|
15693
16725
|
}
|
|
15694
16726
|
return { flags, boolFlags, unknownFlags };
|
|
15695
16727
|
}
|
|
15696
|
-
function
|
|
16728
|
+
function pickConfigArgs8(parsed) {
|
|
15697
16729
|
const configArgs = [];
|
|
15698
16730
|
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
15699
|
-
if (
|
|
16731
|
+
if (CONFIG_FLAGS8.has(flag)) {
|
|
15700
16732
|
configArgs.push(flag, value);
|
|
15701
16733
|
}
|
|
15702
16734
|
}
|
|
15703
16735
|
return configArgs;
|
|
15704
16736
|
}
|
|
15705
|
-
function
|
|
16737
|
+
function normalizeError7(error) {
|
|
15706
16738
|
if (error instanceof ForgeClientError) {
|
|
15707
16739
|
return `${error.code}: ${error.message}`;
|
|
15708
16740
|
}
|
|
@@ -15781,14 +16813,14 @@ function summarizeRunResult(result) {
|
|
|
15781
16813
|
return lines;
|
|
15782
16814
|
}
|
|
15783
16815
|
function createRunUploadHandler(deps = {}) {
|
|
15784
|
-
const readFile = deps.readFile ?? ((filePath) => (0,
|
|
15785
|
-
const readStdin = deps.readStdin ?? (() => (0,
|
|
16816
|
+
const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs13.readFileSync)(filePath, "utf8"));
|
|
16817
|
+
const readStdin = deps.readStdin ?? (() => (0, import_node_fs13.readFileSync)(0, "utf8"));
|
|
15786
16818
|
const cwd = deps.cwd ?? process.cwd();
|
|
15787
16819
|
const env = deps.env ?? process.env;
|
|
15788
16820
|
return async (request, context) => {
|
|
15789
16821
|
const [, ...subArgs] = request.args;
|
|
15790
|
-
const parsed =
|
|
15791
|
-
const configArgs =
|
|
16822
|
+
const parsed = parseArgs9(subArgs);
|
|
16823
|
+
const configArgs = pickConfigArgs8(parsed);
|
|
15792
16824
|
const config = resolveCliConfig(configArgs, env, cwd);
|
|
15793
16825
|
const projectKey = config.values.projectKey;
|
|
15794
16826
|
const issueKey = config.values.issueKey;
|
|
@@ -15821,7 +16853,7 @@ function createRunUploadHandler(deps = {}) {
|
|
|
15821
16853
|
} catch (error) {
|
|
15822
16854
|
return {
|
|
15823
16855
|
exitCode: ExitCode.InternalError,
|
|
15824
|
-
stderr: [`ERROR: Failed to read run payload source: ${
|
|
16856
|
+
stderr: [`ERROR: Failed to read run payload source: ${normalizeError7(error)}`]
|
|
15825
16857
|
};
|
|
15826
16858
|
}
|
|
15827
16859
|
const payloadFromSource = parseRunPayload(raw);
|
|
@@ -15851,7 +16883,7 @@ function createRunUploadHandler(deps = {}) {
|
|
|
15851
16883
|
} catch (error) {
|
|
15852
16884
|
return {
|
|
15853
16885
|
exitCode: ExitCode.TransportError,
|
|
15854
|
-
stderr: [`ERROR: ${
|
|
16886
|
+
stderr: [`ERROR: ${normalizeError7(error)}`]
|
|
15855
16887
|
};
|
|
15856
16888
|
}
|
|
15857
16889
|
}
|
|
@@ -15931,7 +16963,7 @@ function createRunUploadHandler(deps = {}) {
|
|
|
15931
16963
|
featureName: runInput.featureName,
|
|
15932
16964
|
scenarioName: runInput.scenarioName,
|
|
15933
16965
|
executedAt: runInput.executedAt,
|
|
15934
|
-
errorMessage:
|
|
16966
|
+
errorMessage: normalizeError7(error)
|
|
15935
16967
|
})
|
|
15936
16968
|
};
|
|
15937
16969
|
}
|
|
@@ -15943,14 +16975,14 @@ function createRunUploadHandler(deps = {}) {
|
|
|
15943
16975
|
`Scenario: ${runInput.scenarioName}`,
|
|
15944
16976
|
`ExecutedAt: ${runInput.executedAt}`
|
|
15945
16977
|
],
|
|
15946
|
-
stderr: [`ERROR: ${
|
|
16978
|
+
stderr: [`ERROR: ${normalizeError7(error)}`]
|
|
15947
16979
|
};
|
|
15948
16980
|
}
|
|
15949
16981
|
};
|
|
15950
16982
|
}
|
|
15951
16983
|
|
|
15952
16984
|
// src/runs.ts
|
|
15953
|
-
var
|
|
16985
|
+
var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
|
|
15954
16986
|
"--config",
|
|
15955
16987
|
"--base-url",
|
|
15956
16988
|
"--project-key",
|
|
@@ -15959,11 +16991,11 @@ var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
|
|
|
15959
16991
|
"--jira-email",
|
|
15960
16992
|
"--jira-api-token"
|
|
15961
16993
|
]);
|
|
15962
|
-
function
|
|
16994
|
+
function parseArgs10(args) {
|
|
15963
16995
|
const flags = {};
|
|
15964
16996
|
const boolFlags = /* @__PURE__ */ new Set();
|
|
15965
16997
|
const unknownFlags = [];
|
|
15966
|
-
const supportedValueFlags = /* @__PURE__ */ new Set(["--id", ...
|
|
16998
|
+
const supportedValueFlags = /* @__PURE__ */ new Set(["--id", ...CONFIG_FLAGS9]);
|
|
15967
16999
|
const supportedBoolFlags = /* @__PURE__ */ new Set(["--json"]);
|
|
15968
17000
|
for (let index = 0; index < args.length; index += 1) {
|
|
15969
17001
|
const token = args[index];
|
|
@@ -15988,16 +17020,16 @@ function parseArgs9(args) {
|
|
|
15988
17020
|
}
|
|
15989
17021
|
return { flags, boolFlags, unknownFlags };
|
|
15990
17022
|
}
|
|
15991
|
-
function
|
|
17023
|
+
function pickConfigArgs9(parsed) {
|
|
15992
17024
|
const args = [];
|
|
15993
17025
|
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
15994
|
-
if (
|
|
17026
|
+
if (CONFIG_FLAGS9.has(flag)) {
|
|
15995
17027
|
args.push(flag, value);
|
|
15996
17028
|
}
|
|
15997
17029
|
}
|
|
15998
17030
|
return args;
|
|
15999
17031
|
}
|
|
16000
|
-
function
|
|
17032
|
+
function normalizeError8(error) {
|
|
16001
17033
|
if (error instanceof ForgeClientError) {
|
|
16002
17034
|
return `${error.code}: ${error.message}`;
|
|
16003
17035
|
}
|
|
@@ -16050,7 +17082,7 @@ function runToLines(run) {
|
|
|
16050
17082
|
}
|
|
16051
17083
|
return lines;
|
|
16052
17084
|
}
|
|
16053
|
-
function
|
|
17085
|
+
function missingProjectResponse4() {
|
|
16054
17086
|
return {
|
|
16055
17087
|
exitCode: ExitCode.ValidationError,
|
|
16056
17088
|
stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
|
|
@@ -16061,9 +17093,9 @@ function createRunsHandler(deps = {}) {
|
|
|
16061
17093
|
const env = deps.env ?? process.env;
|
|
16062
17094
|
return async (request, context) => {
|
|
16063
17095
|
const [subcommand, ...restArgs] = request.args;
|
|
16064
|
-
const parsed =
|
|
17096
|
+
const parsed = parseArgs10(restArgs);
|
|
16065
17097
|
const useJson = parsed.boolFlags.has("--json");
|
|
16066
|
-
const config = resolveCliConfig(
|
|
17098
|
+
const config = resolveCliConfig(pickConfigArgs9(parsed), env, cwd);
|
|
16067
17099
|
const projectKey = config.values.projectKey;
|
|
16068
17100
|
const issueKey = config.values.issueKey;
|
|
16069
17101
|
if (parsed.unknownFlags.length > 0) {
|
|
@@ -16073,7 +17105,7 @@ function createRunsHandler(deps = {}) {
|
|
|
16073
17105
|
};
|
|
16074
17106
|
}
|
|
16075
17107
|
if (!projectKey) {
|
|
16076
|
-
return
|
|
17108
|
+
return missingProjectResponse4();
|
|
16077
17109
|
}
|
|
16078
17110
|
if (subcommand === "show") {
|
|
16079
17111
|
const runId = parsed.flags["--id"]?.trim() ?? "";
|
|
@@ -16115,7 +17147,7 @@ function createRunsHandler(deps = {}) {
|
|
|
16115
17147
|
} catch (error) {
|
|
16116
17148
|
return {
|
|
16117
17149
|
exitCode: ExitCode.TransportError,
|
|
16118
|
-
stderr: [`ERROR: ${
|
|
17150
|
+
stderr: [`ERROR: ${normalizeError8(error)}`]
|
|
16119
17151
|
};
|
|
16120
17152
|
}
|
|
16121
17153
|
}
|
|
@@ -16127,7 +17159,7 @@ function createRunsHandler(deps = {}) {
|
|
|
16127
17159
|
}
|
|
16128
17160
|
|
|
16129
17161
|
// src/setup.ts
|
|
16130
|
-
var
|
|
17162
|
+
var import_node_fs15 = require("node:fs");
|
|
16131
17163
|
var import_node_path15 = __toESM(require("node:path"), 1);
|
|
16132
17164
|
|
|
16133
17165
|
// src/githubSetupProvider.ts
|
|
@@ -16135,7 +17167,7 @@ var import_node_child_process4 = require("node:child_process");
|
|
|
16135
17167
|
|
|
16136
17168
|
// src/setupPlan.ts
|
|
16137
17169
|
var import_node_crypto = require("node:crypto");
|
|
16138
|
-
var
|
|
17170
|
+
var import_node_fs14 = require("node:fs");
|
|
16139
17171
|
var import_node_path14 = __toESM(require("node:path"), 1);
|
|
16140
17172
|
var SETUP_PLAN_SCHEMA_VERSION = "automatify.testops.setup/v1";
|
|
16141
17173
|
var SETUP_PLAN_KIND = "AutomatifyTestOpsSetupPlan";
|
|
@@ -16231,7 +17263,7 @@ function buildGitHubSetupPlan(input) {
|
|
|
16231
17263
|
if (endpointSecretName === tokenSecretName) {
|
|
16232
17264
|
throw new Error("callback endpoint and auth token secret names must be different.");
|
|
16233
17265
|
}
|
|
16234
|
-
const workflowContent = (0,
|
|
17266
|
+
const workflowContent = (0, import_node_fs14.readFileSync)(input.workflowSourcePath, "utf8");
|
|
16235
17267
|
if (!workflowContent.trim()) {
|
|
16236
17268
|
throw new Error("workflow source file is empty.");
|
|
16237
17269
|
}
|
|
@@ -17095,7 +18127,7 @@ function defaultReadStdin() {
|
|
|
17095
18127
|
process.stdin.on("error", reject);
|
|
17096
18128
|
});
|
|
17097
18129
|
}
|
|
17098
|
-
function
|
|
18130
|
+
function parseArgs11(args, valueFlags, boolFlags, allowApplyCollections = false) {
|
|
17099
18131
|
const flags = {};
|
|
17100
18132
|
const enabled = /* @__PURE__ */ new Set();
|
|
17101
18133
|
const approvals = [];
|
|
@@ -17177,7 +18209,7 @@ function loadPlan(planPath, cwd) {
|
|
|
17177
18209
|
const absolute = import_node_path15.default.resolve(cwd, planPath);
|
|
17178
18210
|
let parsed;
|
|
17179
18211
|
try {
|
|
17180
|
-
parsed = JSON.parse((0,
|
|
18212
|
+
parsed = JSON.parse((0, import_node_fs15.readFileSync)(absolute, "utf8"));
|
|
17181
18213
|
} catch (error) {
|
|
17182
18214
|
throw new SetupCommandError(
|
|
17183
18215
|
"PLAN_READ_ERROR",
|
|
@@ -17416,7 +18448,7 @@ function resolveWorkflowTarget(plan, repoRoot) {
|
|
|
17416
18448
|
function applyWorkflowFile(plan, repoRoot, dryRun) {
|
|
17417
18449
|
const action = plan.actions.find((item) => item.scope === "workflow-file");
|
|
17418
18450
|
const target = resolveWorkflowTarget(plan, repoRoot);
|
|
17419
|
-
const sourceContent = (0,
|
|
18451
|
+
const sourceContent = (0, import_node_fs15.readFileSync)(plan.github.workflowFile.sourcePath, "utf8");
|
|
17420
18452
|
if (hashSetupContent(sourceContent) !== plan.github.workflowFile.sha256) {
|
|
17421
18453
|
throw new SetupCommandError(
|
|
17422
18454
|
"WORKFLOW_SOURCE_CHANGED",
|
|
@@ -17424,8 +18456,8 @@ function applyWorkflowFile(plan, repoRoot, dryRun) {
|
|
|
17424
18456
|
ExitCode.ValidationError
|
|
17425
18457
|
);
|
|
17426
18458
|
}
|
|
17427
|
-
const existed = (0,
|
|
17428
|
-
const previousContent = existed ? (0,
|
|
18459
|
+
const existed = (0, import_node_fs15.existsSync)(target);
|
|
18460
|
+
const previousContent = existed ? (0, import_node_fs15.readFileSync)(target, "utf8") : void 0;
|
|
17429
18461
|
const matches = previousContent !== void 0 && hashSetupContent(previousContent) === plan.github.workflowFile.sha256;
|
|
17430
18462
|
if (matches) {
|
|
17431
18463
|
return {
|
|
@@ -17437,8 +18469,8 @@ function applyWorkflowFile(plan, repoRoot, dryRun) {
|
|
|
17437
18469
|
};
|
|
17438
18470
|
}
|
|
17439
18471
|
if (!dryRun) {
|
|
17440
|
-
(0,
|
|
17441
|
-
(0,
|
|
18472
|
+
(0, import_node_fs15.mkdirSync)(import_node_path15.default.dirname(target), { recursive: true });
|
|
18473
|
+
(0, import_node_fs15.writeFileSync)(target, sourceContent, { encoding: "utf8", mode: 420 });
|
|
17442
18474
|
}
|
|
17443
18475
|
return {
|
|
17444
18476
|
id: action.id,
|
|
@@ -17608,14 +18640,14 @@ async function inspectPlan(plan, repoRoot, secrets, context, deps) {
|
|
|
17608
18640
|
const checks = [];
|
|
17609
18641
|
let forgeTransportFailure = false;
|
|
17610
18642
|
const target = resolveWorkflowTarget(plan, repoRoot);
|
|
17611
|
-
if (!(0,
|
|
18643
|
+
if (!(0, import_node_fs15.existsSync)(target)) {
|
|
17612
18644
|
checks.push({
|
|
17613
18645
|
id: "workflow-local",
|
|
17614
18646
|
status: "fail",
|
|
17615
18647
|
message: `Local workflow file ${plan.github.workflowFile.path} does not exist.`
|
|
17616
18648
|
});
|
|
17617
18649
|
} else {
|
|
17618
|
-
const localHash = hashSetupContent((0,
|
|
18650
|
+
const localHash = hashSetupContent((0, import_node_fs15.readFileSync)(target, "utf8"));
|
|
17619
18651
|
checks.push({
|
|
17620
18652
|
id: "workflow-local",
|
|
17621
18653
|
status: localHash === plan.github.workflowFile.sha256 ? "pass" : "fail",
|
|
@@ -17705,7 +18737,7 @@ async function inspectPlan(plan, repoRoot, secrets, context, deps) {
|
|
|
17705
18737
|
};
|
|
17706
18738
|
}
|
|
17707
18739
|
function planCommand(args, deps) {
|
|
17708
|
-
const parsed =
|
|
18740
|
+
const parsed = parseArgs11(args, PLAN_VALUE_FLAGS, /* @__PURE__ */ new Set(["--set-default", "--disabled"]));
|
|
17709
18741
|
if (parsed.errors.length > 0) {
|
|
17710
18742
|
return jsonResponse(
|
|
17711
18743
|
errorPayload("USAGE_ERROR", "Invalid setup plan arguments.", parsed.errors),
|
|
@@ -17764,7 +18796,7 @@ function planCommand(args, deps) {
|
|
|
17764
18796
|
}
|
|
17765
18797
|
}
|
|
17766
18798
|
async function applyCommand(args, context, deps) {
|
|
17767
|
-
const parsed =
|
|
18799
|
+
const parsed = parseArgs11(args, APPLY_VALUE_FLAGS, APPLY_BOOL_FLAGS, true);
|
|
17768
18800
|
if (parsed.errors.length > 0) {
|
|
17769
18801
|
return jsonResponse(
|
|
17770
18802
|
errorPayload("USAGE_ERROR", "Invalid setup apply arguments.", parsed.errors),
|
|
@@ -18007,7 +19039,7 @@ async function applyAzureCommand(plan, parsed, context, deps, secrets) {
|
|
|
18007
19039
|
});
|
|
18008
19040
|
}
|
|
18009
19041
|
async function doctorCommand(args, context, deps) {
|
|
18010
|
-
const parsed =
|
|
19042
|
+
const parsed = parseArgs11(args, APPLY_VALUE_FLAGS, /* @__PURE__ */ new Set(["--secrets-stdin"]), true);
|
|
18011
19043
|
if (parsed.approvals.length > 0) {
|
|
18012
19044
|
return jsonResponse(errorPayload("USAGE_ERROR", "setup doctor does not accept --approve."), ExitCode.UsageError);
|
|
18013
19045
|
}
|
|
@@ -18120,7 +19152,7 @@ function createSetupHandler(overrides = {}) {
|
|
|
18120
19152
|
}
|
|
18121
19153
|
|
|
18122
19154
|
// src/suites.ts
|
|
18123
|
-
var
|
|
19155
|
+
var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
|
|
18124
19156
|
"--config",
|
|
18125
19157
|
"--base-url",
|
|
18126
19158
|
"--project-key",
|
|
@@ -18129,11 +19161,11 @@ var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
|
|
|
18129
19161
|
"--jira-email",
|
|
18130
19162
|
"--jira-api-token"
|
|
18131
19163
|
]);
|
|
18132
|
-
function
|
|
19164
|
+
function parseArgs12(args) {
|
|
18133
19165
|
const flags = {};
|
|
18134
19166
|
const boolFlags = /* @__PURE__ */ new Set();
|
|
18135
19167
|
const unknownFlags = [];
|
|
18136
|
-
const supportedValueFlags = /* @__PURE__ */ new Set(["--key", ...
|
|
19168
|
+
const supportedValueFlags = /* @__PURE__ */ new Set(["--key", ...CONFIG_FLAGS10]);
|
|
18137
19169
|
const supportedBoolFlags = /* @__PURE__ */ new Set(["--json"]);
|
|
18138
19170
|
for (let index = 0; index < args.length; index += 1) {
|
|
18139
19171
|
const token = args[index];
|
|
@@ -18158,16 +19190,16 @@ function parseArgs11(args) {
|
|
|
18158
19190
|
}
|
|
18159
19191
|
return { flags, boolFlags, unknownFlags };
|
|
18160
19192
|
}
|
|
18161
|
-
function
|
|
19193
|
+
function pickConfigArgs10(parsed) {
|
|
18162
19194
|
const args = [];
|
|
18163
19195
|
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
18164
|
-
if (
|
|
19196
|
+
if (CONFIG_FLAGS10.has(flag)) {
|
|
18165
19197
|
args.push(flag, value);
|
|
18166
19198
|
}
|
|
18167
19199
|
}
|
|
18168
19200
|
return args;
|
|
18169
19201
|
}
|
|
18170
|
-
function
|
|
19202
|
+
function normalizeError9(error) {
|
|
18171
19203
|
if (error instanceof ForgeClientError) {
|
|
18172
19204
|
return `${error.code}: ${error.message}`;
|
|
18173
19205
|
}
|
|
@@ -18229,7 +19261,7 @@ function suiteCasesToLines(suite, items) {
|
|
|
18229
19261
|
...items.map((item) => `- ${item.key} [${item.status}] ${item.title}`)
|
|
18230
19262
|
];
|
|
18231
19263
|
}
|
|
18232
|
-
function
|
|
19264
|
+
function missingProjectResponse5() {
|
|
18233
19265
|
return {
|
|
18234
19266
|
exitCode: ExitCode.ValidationError,
|
|
18235
19267
|
stderr: ["ERROR: Missing project context. Set JIRA_PROJECT_KEY or pass --project-key."]
|
|
@@ -18246,9 +19278,9 @@ function createSuitesHandler(deps = {}) {
|
|
|
18246
19278
|
const env = deps.env ?? process.env;
|
|
18247
19279
|
return async (request, context) => {
|
|
18248
19280
|
const [subcommand, ...restArgs] = request.args;
|
|
18249
|
-
const parsed =
|
|
19281
|
+
const parsed = parseArgs12(restArgs);
|
|
18250
19282
|
const useJson = parsed.boolFlags.has("--json");
|
|
18251
|
-
const config = resolveCliConfig(
|
|
19283
|
+
const config = resolveCliConfig(pickConfigArgs10(parsed), env, cwd);
|
|
18252
19284
|
const projectKey = config.values.projectKey;
|
|
18253
19285
|
const issueKey = config.values.issueKey;
|
|
18254
19286
|
if (parsed.unknownFlags.length > 0) {
|
|
@@ -18258,7 +19290,7 @@ function createSuitesHandler(deps = {}) {
|
|
|
18258
19290
|
};
|
|
18259
19291
|
}
|
|
18260
19292
|
if (!projectKey) {
|
|
18261
|
-
return
|
|
19293
|
+
return missingProjectResponse5();
|
|
18262
19294
|
}
|
|
18263
19295
|
if (subcommand === "list") {
|
|
18264
19296
|
try {
|
|
@@ -18293,7 +19325,7 @@ function createSuitesHandler(deps = {}) {
|
|
|
18293
19325
|
} catch (error) {
|
|
18294
19326
|
return {
|
|
18295
19327
|
exitCode: ExitCode.TransportError,
|
|
18296
|
-
stderr: [`ERROR: ${
|
|
19328
|
+
stderr: [`ERROR: ${normalizeError9(error)}`]
|
|
18297
19329
|
};
|
|
18298
19330
|
}
|
|
18299
19331
|
}
|
|
@@ -18334,7 +19366,7 @@ function createSuitesHandler(deps = {}) {
|
|
|
18334
19366
|
} catch (error) {
|
|
18335
19367
|
return {
|
|
18336
19368
|
exitCode: ExitCode.TransportError,
|
|
18337
|
-
stderr: [`ERROR: ${
|
|
19369
|
+
stderr: [`ERROR: ${normalizeError9(error)}`]
|
|
18338
19370
|
};
|
|
18339
19371
|
}
|
|
18340
19372
|
}
|
|
@@ -18398,7 +19430,7 @@ function createSuitesHandler(deps = {}) {
|
|
|
18398
19430
|
} catch (error) {
|
|
18399
19431
|
return {
|
|
18400
19432
|
exitCode: ExitCode.TransportError,
|
|
18401
|
-
stderr: [`ERROR: ${
|
|
19433
|
+
stderr: [`ERROR: ${normalizeError9(error)}`]
|
|
18402
19434
|
};
|
|
18403
19435
|
}
|
|
18404
19436
|
}
|
|
@@ -18410,7 +19442,7 @@ function createSuitesHandler(deps = {}) {
|
|
|
18410
19442
|
}
|
|
18411
19443
|
|
|
18412
19444
|
// src/sync.ts
|
|
18413
|
-
var
|
|
19445
|
+
var CONFIG_FLAGS11 = /* @__PURE__ */ new Set([
|
|
18414
19446
|
"--config",
|
|
18415
19447
|
"--base-url",
|
|
18416
19448
|
"--project-key",
|
|
@@ -18420,11 +19452,11 @@ var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
|
|
|
18420
19452
|
"--jira-api-token"
|
|
18421
19453
|
]);
|
|
18422
19454
|
var RECONCILE_CONFIRM_TOKEN = "RECONCILE";
|
|
18423
|
-
function
|
|
19455
|
+
function parseArgs13(args) {
|
|
18424
19456
|
const flags = {};
|
|
18425
19457
|
const boolFlags = /* @__PURE__ */ new Set();
|
|
18426
19458
|
const unknownFlags = [];
|
|
18427
|
-
const supportedValueFlags = /* @__PURE__ */ new Set(["--confirm", ...
|
|
19459
|
+
const supportedValueFlags = /* @__PURE__ */ new Set(["--confirm", ...CONFIG_FLAGS11]);
|
|
18428
19460
|
const supportedBoolFlags = /* @__PURE__ */ new Set(["--json"]);
|
|
18429
19461
|
for (let i = 0; i < args.length; i += 1) {
|
|
18430
19462
|
const token = args[i];
|
|
@@ -18449,16 +19481,16 @@ function parseArgs12(args) {
|
|
|
18449
19481
|
}
|
|
18450
19482
|
return { flags, boolFlags, unknownFlags };
|
|
18451
19483
|
}
|
|
18452
|
-
function
|
|
19484
|
+
function pickConfigArgs11(parsed) {
|
|
18453
19485
|
const args = [];
|
|
18454
19486
|
for (const [flag, value] of Object.entries(parsed.flags)) {
|
|
18455
|
-
if (
|
|
19487
|
+
if (CONFIG_FLAGS11.has(flag)) {
|
|
18456
19488
|
args.push(flag, value);
|
|
18457
19489
|
}
|
|
18458
19490
|
}
|
|
18459
19491
|
return args;
|
|
18460
19492
|
}
|
|
18461
|
-
function
|
|
19493
|
+
function normalizeError10(error) {
|
|
18462
19494
|
if (error instanceof ForgeClientError) {
|
|
18463
19495
|
return `${error.code}: ${error.message}`;
|
|
18464
19496
|
}
|
|
@@ -18509,9 +19541,9 @@ function createSyncHandler(deps = {}) {
|
|
|
18509
19541
|
const env = deps.env ?? process.env;
|
|
18510
19542
|
return async (request, context) => {
|
|
18511
19543
|
const [subcommand, ...restArgs] = request.args;
|
|
18512
|
-
const parsed =
|
|
19544
|
+
const parsed = parseArgs13(restArgs);
|
|
18513
19545
|
const useJson = parsed.boolFlags.has("--json");
|
|
18514
|
-
const config = resolveCliConfig(
|
|
19546
|
+
const config = resolveCliConfig(pickConfigArgs11(parsed), env, cwd);
|
|
18515
19547
|
const projectKey = config.values.projectKey;
|
|
18516
19548
|
const issueKey = config.values.issueKey;
|
|
18517
19549
|
if (parsed.unknownFlags.length > 0) {
|
|
@@ -18561,7 +19593,7 @@ function createSyncHandler(deps = {}) {
|
|
|
18561
19593
|
} catch (error) {
|
|
18562
19594
|
return {
|
|
18563
19595
|
exitCode: ExitCode.TransportError,
|
|
18564
|
-
stderr: [`ERROR: ${
|
|
19596
|
+
stderr: [`ERROR: ${normalizeError10(error)}`]
|
|
18565
19597
|
};
|
|
18566
19598
|
}
|
|
18567
19599
|
}
|
|
@@ -18607,7 +19639,7 @@ function createSyncHandler(deps = {}) {
|
|
|
18607
19639
|
} catch (error) {
|
|
18608
19640
|
return {
|
|
18609
19641
|
exitCode: ExitCode.TransportError,
|
|
18610
|
-
stderr: [`ERROR: ${
|
|
19642
|
+
stderr: [`ERROR: ${normalizeError10(error)}`]
|
|
18611
19643
|
};
|
|
18612
19644
|
}
|
|
18613
19645
|
}
|
|
@@ -18626,6 +19658,12 @@ var COMMAND_REGISTRY = [
|
|
|
18626
19658
|
subcommands: ["upload", "download", "open"],
|
|
18627
19659
|
handler: createAllureHandler()
|
|
18628
19660
|
},
|
|
19661
|
+
{
|
|
19662
|
+
name: "auth",
|
|
19663
|
+
description: "Secret-safe Jira authentication login, status, and logout commands",
|
|
19664
|
+
subcommands: ["login", "status", "logout"],
|
|
19665
|
+
handler: createAuthHandler()
|
|
19666
|
+
},
|
|
18629
19667
|
{
|
|
18630
19668
|
name: "auto",
|
|
18631
19669
|
description: "Smart auto-ingestion command contract (MVP staged)",
|
|
@@ -18648,7 +19686,7 @@ var COMMAND_REGISTRY = [
|
|
|
18648
19686
|
{
|
|
18649
19687
|
name: "config",
|
|
18650
19688
|
description: "Configuration and auth bootstrap commands",
|
|
18651
|
-
subcommands: ["show", "validate", "set"],
|
|
19689
|
+
subcommands: ["show", "validate", "set", "unset", "migrate-secrets"],
|
|
18652
19690
|
handler: createConfigHandler()
|
|
18653
19691
|
},
|
|
18654
19692
|
{
|
|
@@ -18657,6 +19695,12 @@ var COMMAND_REGISTRY = [
|
|
|
18657
19695
|
subcommands: ["feature"],
|
|
18658
19696
|
handler: createIngestFeatureHandler()
|
|
18659
19697
|
},
|
|
19698
|
+
{
|
|
19699
|
+
name: "profiles",
|
|
19700
|
+
description: "Automation profile inspection, detach, delete, and demo cleanup commands",
|
|
19701
|
+
subcommands: ["list", "detach", "delete", "clear"],
|
|
19702
|
+
handler: createProfilesHandler()
|
|
19703
|
+
},
|
|
18660
19704
|
{
|
|
18661
19705
|
name: "run",
|
|
18662
19706
|
description: "Execution result upload commands",
|