@automatify-au/cli 0.1.10 → 0.1.11

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.
@@ -2894,8 +2894,8 @@ var require_utils = __commonJS({
2894
2894
  var result = transform[inputType][outputType](input);
2895
2895
  return result;
2896
2896
  };
2897
- exports2.resolve = function(path12) {
2898
- var parts = path12.split("/");
2897
+ exports2.resolve = function(path14) {
2898
+ var parts = path14.split("/");
2899
2899
  var result = [];
2900
2900
  for (var index = 0; index < parts.length; index++) {
2901
2901
  var part = parts[index];
@@ -8748,18 +8748,18 @@ var require_object = __commonJS({
8748
8748
  var object = new ZipObject(name, zipObjectContent, o);
8749
8749
  this.files[name] = object;
8750
8750
  };
8751
- var parentFolder = function(path12) {
8752
- if (path12.slice(-1) === "/") {
8753
- path12 = path12.substring(0, path12.length - 1);
8751
+ var parentFolder = function(path14) {
8752
+ if (path14.slice(-1) === "/") {
8753
+ path14 = path14.substring(0, path14.length - 1);
8754
8754
  }
8755
- var lastSlash = path12.lastIndexOf("/");
8756
- return lastSlash > 0 ? path12.substring(0, lastSlash) : "";
8755
+ var lastSlash = path14.lastIndexOf("/");
8756
+ return lastSlash > 0 ? path14.substring(0, lastSlash) : "";
8757
8757
  };
8758
- var forceTrailingSlash = function(path12) {
8759
- if (path12.slice(-1) !== "/") {
8760
- path12 += "/";
8758
+ var forceTrailingSlash = function(path14) {
8759
+ if (path14.slice(-1) !== "/") {
8760
+ path14 += "/";
8761
8761
  }
8762
- return path12;
8762
+ return path14;
8763
8763
  };
8764
8764
  var folderAdd = function(name, createFolders) {
8765
8765
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -9725,9 +9725,9 @@ var require_load = __commonJS({
9725
9725
  var require_lib3 = __commonJS({
9726
9726
  "../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/index.js"(exports2, module2) {
9727
9727
  "use strict";
9728
- function JSZip2() {
9729
- if (!(this instanceof JSZip2)) {
9730
- return new JSZip2();
9728
+ function JSZip3() {
9729
+ if (!(this instanceof JSZip3)) {
9730
+ return new JSZip3();
9731
9731
  }
9732
9732
  if (arguments.length) {
9733
9733
  throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
@@ -9736,7 +9736,7 @@ var require_lib3 = __commonJS({
9736
9736
  this.comment = null;
9737
9737
  this.root = "";
9738
9738
  this.clone = function() {
9739
- var newObj = new JSZip2();
9739
+ var newObj = new JSZip3();
9740
9740
  for (var i in this) {
9741
9741
  if (typeof this[i] !== "function") {
9742
9742
  newObj[i] = this[i];
@@ -9745,16 +9745,16 @@ var require_lib3 = __commonJS({
9745
9745
  return newObj;
9746
9746
  };
9747
9747
  }
9748
- JSZip2.prototype = require_object();
9749
- JSZip2.prototype.loadAsync = require_load();
9750
- JSZip2.support = require_support();
9751
- JSZip2.defaults = require_defaults();
9752
- JSZip2.version = "3.10.1";
9753
- JSZip2.loadAsync = function(content, options) {
9754
- return new JSZip2().loadAsync(content, options);
9748
+ JSZip3.prototype = require_object();
9749
+ JSZip3.prototype.loadAsync = require_load();
9750
+ JSZip3.support = require_support();
9751
+ JSZip3.defaults = require_defaults();
9752
+ JSZip3.version = "3.10.1";
9753
+ JSZip3.loadAsync = function(content, options) {
9754
+ return new JSZip3().loadAsync(content, options);
9755
9755
  };
9756
- JSZip2.external = require_external();
9757
- module2.exports = JSZip2;
9756
+ JSZip3.external = require_external();
9757
+ module2.exports = JSZip3;
9758
9758
  }
9759
9759
  });
9760
9760
 
@@ -12447,8 +12447,8 @@ function createAutoHandler(deps = {}) {
12447
12447
  }
12448
12448
 
12449
12449
  // src/allure.ts
12450
- var import_node_path8 = __toESM(require("node:path"), 1);
12451
- var import_node_fs7 = require("node:fs");
12450
+ var import_node_path10 = __toESM(require("node:path"), 1);
12451
+ var import_node_fs9 = require("node:fs");
12452
12452
 
12453
12453
  // ../../packages/jira-client/src/adf.ts
12454
12454
  function paragraph(text = "") {
@@ -12681,6 +12681,438 @@ async function parseAllureReportZip(zipPath) {
12681
12681
  };
12682
12682
  }
12683
12683
 
12684
+ // ../../packages/allure-core/src/extractAllureZip.ts
12685
+ var import_node_fs7 = require("node:fs");
12686
+ var import_node_path8 = __toESM(require("node:path"), 1);
12687
+ var import_jszip2 = __toESM(require_lib3(), 1);
12688
+ var INDEX_HTML = "index.html";
12689
+ var MISSING_INDEX_ERROR = "Could not find index.html. Please provide a generated Allure HTML report ZIP, not raw allure-results.";
12690
+ function findIndexEntry(paths) {
12691
+ return paths.find((entryPath) => entryPath === INDEX_HTML) ?? paths.find((entryPath) => entryPath === `allure-report/${INDEX_HTML}`) ?? paths.find((entryPath) => entryPath.endsWith(`/${INDEX_HTML}`));
12692
+ }
12693
+ function ensureInsideRoot(rootDir, candidate) {
12694
+ const resolved = import_node_path8.default.resolve(rootDir, candidate);
12695
+ const rootWithSep = rootDir.endsWith(import_node_path8.default.sep) ? rootDir : rootDir + import_node_path8.default.sep;
12696
+ if (resolved !== rootDir && !resolved.startsWith(rootWithSep)) {
12697
+ throw new Error(`Refusing to write zip entry outside extract directory: ${candidate}`);
12698
+ }
12699
+ return resolved;
12700
+ }
12701
+ async function extractAllureReportZip(zipPath, extractDir) {
12702
+ if (!(0, import_node_fs7.existsSync)(zipPath)) {
12703
+ throw new Error(`Allure report ZIP not found: ${zipPath}`);
12704
+ }
12705
+ if (import_node_path8.default.extname(zipPath).toLowerCase() !== ".zip") {
12706
+ throw new Error("Allure report must be a .zip file.");
12707
+ }
12708
+ const zip = await import_jszip2.default.loadAsync((0, import_node_fs7.readFileSync)(zipPath));
12709
+ const entries = Object.entries(zip.files);
12710
+ const filePaths = entries.filter(([, file]) => !file.dir).map(([entryPath]) => entryPath);
12711
+ const indexEntry = findIndexEntry(filePaths);
12712
+ if (!indexEntry) {
12713
+ throw new Error(MISSING_INDEX_ERROR);
12714
+ }
12715
+ const resolvedExtract = import_node_path8.default.resolve(extractDir);
12716
+ (0, import_node_fs7.mkdirSync)(resolvedExtract, { recursive: true });
12717
+ for (const [entryPath, file] of entries) {
12718
+ const target = ensureInsideRoot(resolvedExtract, entryPath);
12719
+ if (file.dir) {
12720
+ (0, import_node_fs7.mkdirSync)(target, { recursive: true });
12721
+ continue;
12722
+ }
12723
+ (0, import_node_fs7.mkdirSync)(import_node_path8.default.dirname(target), { recursive: true });
12724
+ const buffer = await file.async("nodebuffer");
12725
+ (0, import_node_fs7.writeFileSync)(target, buffer);
12726
+ }
12727
+ const indexAbsolute = import_node_path8.default.join(resolvedExtract, indexEntry);
12728
+ const rootDir = import_node_path8.default.dirname(indexAbsolute);
12729
+ return {
12730
+ zipPath,
12731
+ extractDir: resolvedExtract,
12732
+ rootDir,
12733
+ indexPath: indexAbsolute
12734
+ };
12735
+ }
12736
+
12737
+ // src/allureOpen.ts
12738
+ var import_node_fs8 = require("node:fs");
12739
+ var import_promises = require("node:fs/promises");
12740
+ var import_node_http = __toESM(require("node:http"), 1);
12741
+ var import_node_os = require("node:os");
12742
+ var import_node_path9 = __toESM(require("node:path"), 1);
12743
+ var import_node_child_process2 = require("node:child_process");
12744
+ function parseArgs2(args) {
12745
+ const positionals = [];
12746
+ const flags = {};
12747
+ const boolFlags = /* @__PURE__ */ new Set();
12748
+ const unknownFlags = [];
12749
+ const valuedFlags = /* @__PURE__ */ new Set(["--port", "--host", "--extract-to"]);
12750
+ const supportedBoolFlags = /* @__PURE__ */ new Set(["--keep", "--no-open", "--json", "--dry-run", "--verbose"]);
12751
+ for (let i = 0; i < args.length; i += 1) {
12752
+ const token = args[i];
12753
+ if (!token.startsWith("--")) {
12754
+ positionals.push(token);
12755
+ continue;
12756
+ }
12757
+ if (supportedBoolFlags.has(token)) {
12758
+ boolFlags.add(token);
12759
+ continue;
12760
+ }
12761
+ if (!valuedFlags.has(token)) {
12762
+ unknownFlags.push(token);
12763
+ continue;
12764
+ }
12765
+ const value = args[i + 1];
12766
+ if (!value || value.startsWith("--")) {
12767
+ unknownFlags.push(token);
12768
+ continue;
12769
+ }
12770
+ flags[token] = value;
12771
+ i += 1;
12772
+ }
12773
+ return { positionals, flags, boolFlags, unknownFlags };
12774
+ }
12775
+ function parsePort(raw) {
12776
+ if (raw === void 0) {
12777
+ return { value: 0 };
12778
+ }
12779
+ const parsed = Number(raw);
12780
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
12781
+ return { value: 0, error: `Invalid --port value: ${raw}. Must be an integer between 0 and 65535.` };
12782
+ }
12783
+ return { value: parsed };
12784
+ }
12785
+ function normalizeError(error) {
12786
+ return error instanceof Error ? error.message : "Unknown Allure open error.";
12787
+ }
12788
+ var MIME_TYPES = {
12789
+ ".html": "text/html; charset=utf-8",
12790
+ ".htm": "text/html; charset=utf-8",
12791
+ ".js": "application/javascript; charset=utf-8",
12792
+ ".mjs": "application/javascript; charset=utf-8",
12793
+ ".css": "text/css; charset=utf-8",
12794
+ ".json": "application/json; charset=utf-8",
12795
+ ".svg": "image/svg+xml",
12796
+ ".png": "image/png",
12797
+ ".jpg": "image/jpeg",
12798
+ ".jpeg": "image/jpeg",
12799
+ ".gif": "image/gif",
12800
+ ".webp": "image/webp",
12801
+ ".ico": "image/x-icon",
12802
+ ".woff": "font/woff",
12803
+ ".woff2": "font/woff2",
12804
+ ".ttf": "font/ttf",
12805
+ ".otf": "font/otf",
12806
+ ".eot": "application/vnd.ms-fontobject",
12807
+ ".map": "application/json; charset=utf-8",
12808
+ ".txt": "text/plain; charset=utf-8"
12809
+ };
12810
+ function contentTypeFor(filePath) {
12811
+ return MIME_TYPES[import_node_path9.default.extname(filePath).toLowerCase()] ?? "application/octet-stream";
12812
+ }
12813
+ function resolveRequestedPath(rootDir, urlPath) {
12814
+ let decoded;
12815
+ try {
12816
+ decoded = decodeURIComponent(urlPath.split("?")[0].split("#")[0]);
12817
+ } catch {
12818
+ return void 0;
12819
+ }
12820
+ const normalized = import_node_path9.default.normalize(decoded);
12821
+ const trimmed = normalized.replace(/^[/\\]+/, "");
12822
+ const candidate = import_node_path9.default.resolve(rootDir, trimmed);
12823
+ const rootWithSep = rootDir.endsWith(import_node_path9.default.sep) ? rootDir : rootDir + import_node_path9.default.sep;
12824
+ if (candidate !== rootDir && !candidate.startsWith(rootWithSep)) {
12825
+ return void 0;
12826
+ }
12827
+ return candidate;
12828
+ }
12829
+ function createStaticServerStarter() {
12830
+ return async ({ rootDir, host, port }) => {
12831
+ const server = import_node_http.default.createServer((req, res) => {
12832
+ if (!req.url || req.method !== "GET" && req.method !== "HEAD") {
12833
+ res.statusCode = 405;
12834
+ res.end("Method Not Allowed");
12835
+ return;
12836
+ }
12837
+ const resolved = resolveRequestedPath(rootDir, req.url);
12838
+ if (!resolved) {
12839
+ res.statusCode = 400;
12840
+ res.end("Bad Request");
12841
+ return;
12842
+ }
12843
+ let target = resolved;
12844
+ try {
12845
+ const stat = (0, import_node_fs8.statSync)(target);
12846
+ if (stat.isDirectory()) {
12847
+ target = import_node_path9.default.join(target, "index.html");
12848
+ }
12849
+ } catch {
12850
+ res.statusCode = 404;
12851
+ res.end("Not Found");
12852
+ return;
12853
+ }
12854
+ if (!(0, import_node_fs8.existsSync)(target)) {
12855
+ res.statusCode = 404;
12856
+ res.end("Not Found");
12857
+ return;
12858
+ }
12859
+ res.setHeader("Content-Type", contentTypeFor(target));
12860
+ if (req.method === "HEAD") {
12861
+ res.statusCode = 200;
12862
+ res.end();
12863
+ return;
12864
+ }
12865
+ const stream = (0, import_node_fs8.createReadStream)(target);
12866
+ stream.on("error", () => {
12867
+ res.statusCode = 500;
12868
+ res.end("Internal Server Error");
12869
+ });
12870
+ stream.pipe(res);
12871
+ });
12872
+ await new Promise((resolve, reject) => {
12873
+ const onError = (err) => {
12874
+ server.off("listening", onListening);
12875
+ reject(err);
12876
+ };
12877
+ const onListening = () => {
12878
+ server.off("error", onError);
12879
+ resolve();
12880
+ };
12881
+ server.once("error", onError);
12882
+ server.once("listening", onListening);
12883
+ server.listen(port, host);
12884
+ });
12885
+ const address = server.address();
12886
+ const actualPort = typeof address === "object" && address ? address.port : port;
12887
+ const displayHost = host === "0.0.0.0" ? "127.0.0.1" : host;
12888
+ const url = `http://${displayHost}:${actualPort}/`;
12889
+ return {
12890
+ url,
12891
+ port: actualPort,
12892
+ host,
12893
+ close: () => new Promise((resolve, reject) => {
12894
+ server.close((err) => err ? reject(err) : resolve());
12895
+ })
12896
+ };
12897
+ };
12898
+ }
12899
+ function createOsBrowserOpener() {
12900
+ return async (url) => {
12901
+ const platform = process.platform;
12902
+ let command;
12903
+ let args;
12904
+ if (platform === "darwin") {
12905
+ command = "open";
12906
+ args = [url];
12907
+ } else if (platform === "win32") {
12908
+ command = "cmd";
12909
+ args = ["/c", "start", "", url];
12910
+ } else {
12911
+ command = "xdg-open";
12912
+ args = [url];
12913
+ }
12914
+ try {
12915
+ const child = (0, import_node_child_process2.spawn)(command, args, { stdio: "ignore", detached: true });
12916
+ child.on("error", () => void 0);
12917
+ child.unref();
12918
+ } catch {
12919
+ }
12920
+ };
12921
+ }
12922
+ function waitForProcessSignals() {
12923
+ return new Promise((resolve) => {
12924
+ const cleanup = () => {
12925
+ process.off("SIGINT", onSignal);
12926
+ process.off("SIGTERM", onSignal);
12927
+ resolve();
12928
+ };
12929
+ const onSignal = () => {
12930
+ cleanup();
12931
+ };
12932
+ process.once("SIGINT", onSignal);
12933
+ process.once("SIGTERM", onSignal);
12934
+ });
12935
+ }
12936
+ function buildJsonOutput(params) {
12937
+ const output = {
12938
+ zipPath: params.zipPath,
12939
+ dryRun: params.dryRun,
12940
+ served: params.served,
12941
+ opened: params.opened,
12942
+ keep: params.keep,
12943
+ extractDir: params.extractDir,
12944
+ rootDir: params.rootDir,
12945
+ indexPath: params.indexPath,
12946
+ summary: {
12947
+ total: params.summary.counts.total,
12948
+ passed: params.summary.counts.passed,
12949
+ failed: params.summary.counts.failed,
12950
+ broken: params.summary.counts.broken,
12951
+ skipped: params.summary.counts.skipped,
12952
+ unknown: params.summary.counts.unknown
12953
+ }
12954
+ };
12955
+ if (params.url) {
12956
+ output.url = params.url;
12957
+ }
12958
+ if (params.port !== void 0) {
12959
+ output.port = params.port;
12960
+ }
12961
+ if (params.host !== void 0) {
12962
+ output.host = params.host;
12963
+ }
12964
+ return output;
12965
+ }
12966
+ function createAllureOpenHandler(deps = {}) {
12967
+ const cwd = deps.cwd ?? process.cwd();
12968
+ const parseZip = deps.parseAllureReportZip ?? parseAllureReportZip;
12969
+ const extractZip = deps.extractAllureReportZip ?? extractAllureReportZip;
12970
+ const createTempDir = deps.createTempDir ?? (() => (0, import_promises.mkdtemp)(import_node_path9.default.join((0, import_node_os.tmpdir)(), "automatify-allure-open-")));
12971
+ const startServer = deps.startServer ?? createStaticServerStarter();
12972
+ const openInBrowser = deps.openInBrowser ?? createOsBrowserOpener();
12973
+ const waitForShutdown = deps.waitForShutdown ?? waitForProcessSignals;
12974
+ const removeDir = deps.removeDir ?? ((dir) => (0, import_promises.rm)(dir, { recursive: true, force: true }));
12975
+ const logger = deps.logger ?? console;
12976
+ return async (request) => {
12977
+ const parsed = parseArgs2(request.args);
12978
+ const useJson = parsed.boolFlags.has("--json");
12979
+ const dryRun = parsed.boolFlags.has("--dry-run");
12980
+ const verbose = parsed.boolFlags.has("--verbose");
12981
+ const keep = parsed.boolFlags.has("--keep");
12982
+ const noOpen = parsed.boolFlags.has("--no-open");
12983
+ const userExtractDir = parsed.flags["--extract-to"];
12984
+ if (parsed.unknownFlags.length > 0) {
12985
+ return {
12986
+ exitCode: ExitCode.UsageError,
12987
+ stderr: [`ERROR: Unknown or invalid flags: ${parsed.unknownFlags.join(", ")}`]
12988
+ };
12989
+ }
12990
+ const [zipPathInput] = parsed.positionals;
12991
+ if (!zipPathInput || parsed.positionals.length > 1) {
12992
+ return {
12993
+ exitCode: ExitCode.UsageError,
12994
+ stderr: ["Usage: automatify testops allure open <zipPath> [options]"]
12995
+ };
12996
+ }
12997
+ const portResult = parsePort(parsed.flags["--port"]);
12998
+ if (portResult.error) {
12999
+ return {
13000
+ exitCode: ExitCode.UsageError,
13001
+ stderr: [`ERROR: ${portResult.error}`]
13002
+ };
13003
+ }
13004
+ const host = parsed.flags["--host"] ?? "127.0.0.1";
13005
+ const zipPath = import_node_path9.default.resolve(cwd, zipPathInput);
13006
+ let summary;
13007
+ try {
13008
+ summary = await parseZip(zipPath);
13009
+ } catch (error) {
13010
+ return {
13011
+ exitCode: ExitCode.ValidationError,
13012
+ stderr: [`ERROR: ${normalizeError(error)}`]
13013
+ };
13014
+ }
13015
+ const extractDirRaw = userExtractDir ? import_node_path9.default.resolve(cwd, userExtractDir) : await createTempDir();
13016
+ if (dryRun) {
13017
+ const indexPath = import_node_path9.default.join(extractDirRaw, "index.html");
13018
+ if (useJson) {
13019
+ return {
13020
+ exitCode: ExitCode.Success,
13021
+ stdout: toJsonLine(buildJsonOutput({
13022
+ zipPath,
13023
+ dryRun: true,
13024
+ served: false,
13025
+ opened: false,
13026
+ keep,
13027
+ extractDir: extractDirRaw,
13028
+ rootDir: extractDirRaw,
13029
+ indexPath,
13030
+ summary
13031
+ }))
13032
+ };
13033
+ }
13034
+ return {
13035
+ exitCode: ExitCode.Success,
13036
+ stdout: [
13037
+ `Allure report ZIP: ${zipPath}`,
13038
+ `Total: ${summary.counts.total} (passed ${summary.counts.passed}, failed ${summary.counts.failed}, broken ${summary.counts.broken}, skipped ${summary.counts.skipped}, unknown ${summary.counts.unknown})`,
13039
+ `Dry run: would extract to ${extractDirRaw} and serve on http://${host}:${portResult.value === 0 ? "<auto>" : portResult.value}/`
13040
+ ]
13041
+ };
13042
+ }
13043
+ let extractResult;
13044
+ try {
13045
+ extractResult = await extractZip(zipPath, extractDirRaw);
13046
+ } catch (error) {
13047
+ if (!userExtractDir) {
13048
+ await removeDir(extractDirRaw).catch(() => void 0);
13049
+ }
13050
+ return {
13051
+ exitCode: ExitCode.ValidationError,
13052
+ stderr: [`ERROR: ${normalizeError(error)}`]
13053
+ };
13054
+ }
13055
+ let serverHandle;
13056
+ try {
13057
+ serverHandle = await startServer({
13058
+ rootDir: extractResult.rootDir,
13059
+ host,
13060
+ port: portResult.value
13061
+ });
13062
+ } catch (error) {
13063
+ if (!keep && !userExtractDir) {
13064
+ await removeDir(extractResult.extractDir).catch(() => void 0);
13065
+ }
13066
+ return {
13067
+ exitCode: ExitCode.TransportError,
13068
+ stderr: [`ERROR: Could not start static server: ${normalizeError(error)}`]
13069
+ };
13070
+ }
13071
+ let opened = false;
13072
+ if (!noOpen) {
13073
+ try {
13074
+ await openInBrowser(serverHandle.url);
13075
+ opened = true;
13076
+ } catch {
13077
+ opened = false;
13078
+ }
13079
+ }
13080
+ if (useJson) {
13081
+ logger.log(JSON.stringify(buildJsonOutput({
13082
+ zipPath,
13083
+ dryRun: false,
13084
+ served: true,
13085
+ opened,
13086
+ keep,
13087
+ extractDir: extractResult.extractDir,
13088
+ rootDir: extractResult.rootDir,
13089
+ indexPath: extractResult.indexPath,
13090
+ url: serverHandle.url,
13091
+ port: serverHandle.port,
13092
+ host: serverHandle.host,
13093
+ summary
13094
+ })));
13095
+ } else {
13096
+ logger.log(`Allure report extracted to ${extractResult.extractDir}.`);
13097
+ if (verbose) {
13098
+ logger.log(`Index: ${extractResult.indexPath}`);
13099
+ }
13100
+ logger.log(`Serving at ${serverHandle.url}`);
13101
+ logger.log(opened ? "Opened in your default browser." : "Browser open skipped.");
13102
+ logger.log("Press Ctrl+C to stop the server.");
13103
+ }
13104
+ await waitForShutdown();
13105
+ try {
13106
+ await serverHandle.close();
13107
+ } catch {
13108
+ }
13109
+ if (!keep && !userExtractDir) {
13110
+ await removeDir(extractResult.extractDir).catch(() => void 0);
13111
+ }
13112
+ return { exitCode: ExitCode.Success };
13113
+ };
13114
+ }
13115
+
12684
13116
  // src/allure.ts
12685
13117
  var CONFIG_FLAGS2 = /* @__PURE__ */ new Set([
12686
13118
  "--config",
@@ -12691,7 +13123,7 @@ var CONFIG_FLAGS2 = /* @__PURE__ */ new Set([
12691
13123
  "--jira-email",
12692
13124
  "--jira-api-token"
12693
13125
  ]);
12694
- function parseArgs2(args) {
13126
+ function parseArgs3(args) {
12695
13127
  const positionals = [];
12696
13128
  const flags = {};
12697
13129
  const boolFlags = /* @__PURE__ */ new Set();
@@ -12742,7 +13174,7 @@ function pickConfigArgs2(parsed) {
12742
13174
  function normalizeSite2(value) {
12743
13175
  return value.trim().replace(/\/+$/, "");
12744
13176
  }
12745
- function normalizeError(error) {
13177
+ function normalizeError2(error) {
12746
13178
  return error instanceof Error ? error.message : "Unknown Allure upload error.";
12747
13179
  }
12748
13180
  function hasCredential(value) {
@@ -12847,7 +13279,7 @@ function buildDownloadJsonOutput(params) {
12847
13279
  outputPath: params.outputPath
12848
13280
  };
12849
13281
  }
12850
- function buildJsonOutput(params) {
13282
+ function buildJsonOutput2(params) {
12851
13283
  const attachment = {
12852
13284
  filename: params.attachment.filename
12853
13285
  };
@@ -12887,19 +13319,24 @@ function createAllureHandler(deps = {}) {
12887
13319
  const createComment = deps.addIssueComment ?? addIssueComment;
12888
13320
  const listAttachments = deps.listIssueAttachments ?? listIssueAttachments;
12889
13321
  const downloadAttachment = deps.downloadIssueAttachment ?? downloadIssueAttachment;
12890
- return async (request) => {
13322
+ const openHandler = createAllureOpenHandler({ cwd });
13323
+ return async (request, context) => {
12891
13324
  const [subcommand, ...subArgs] = request.args;
13325
+ if (subcommand === "open") {
13326
+ return openHandler({ ...request, args: subArgs }, context);
13327
+ }
12892
13328
  if (subcommand !== "upload" && subcommand !== "download") {
12893
13329
  return {
12894
13330
  exitCode: ExitCode.UsageError,
12895
13331
  stderr: [
12896
13332
  "Usage:",
12897
13333
  " automatify testops allure upload <issueKey> <zipPath> [options]",
12898
- " automatify testops allure download <issueKey> [options]"
13334
+ " automatify testops allure download <issueKey> [options]",
13335
+ " automatify testops allure open <zipPath> [options]"
12899
13336
  ]
12900
13337
  };
12901
13338
  }
12902
- const parsed = parseArgs2(subArgs);
13339
+ const parsed = parseArgs3(subArgs);
12903
13340
  const useJson = parsed.boolFlags.has("--json");
12904
13341
  const dryRun = parsed.boolFlags.has("--dry-run");
12905
13342
  const verbose = parsed.boolFlags.has("--verbose");
@@ -12936,8 +13373,8 @@ function createAllureHandler(deps = {}) {
12936
13373
  attachmentId: parsed.flags["--attachment-id"],
12937
13374
  filename: parsed.flags["--filename"]
12938
13375
  });
12939
- const outputDir = import_node_path8.default.resolve(cwd, parsed.flags["--output-dir"] ?? ".");
12940
- const outputPath = import_node_path8.default.join(outputDir, selected.filename);
13376
+ const outputDir = import_node_path10.default.resolve(cwd, parsed.flags["--output-dir"] ?? ".");
13377
+ const outputPath = import_node_path10.default.join(outputDir, selected.filename);
12941
13378
  if (dryRun) {
12942
13379
  if (useJson) {
12943
13380
  return {
@@ -12960,8 +13397,8 @@ function createAllureHandler(deps = {}) {
12960
13397
  ]
12961
13398
  };
12962
13399
  }
12963
- (0, import_node_fs7.mkdirSync)(outputDir, { recursive: true });
12964
- if ((0, import_node_fs7.existsSync)(outputPath) && !force) {
13400
+ (0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
13401
+ if ((0, import_node_fs9.existsSync)(outputPath) && !force) {
12965
13402
  return {
12966
13403
  exitCode: ExitCode.ValidationError,
12967
13404
  stderr: [`ERROR: Output file already exists: ${outputPath}. Pass --force to overwrite.`]
@@ -12996,7 +13433,7 @@ function createAllureHandler(deps = {}) {
12996
13433
  } catch (error) {
12997
13434
  return {
12998
13435
  exitCode: ExitCode.RemoteError,
12999
- stderr: [`ERROR: ${normalizeError(error)}`]
13436
+ stderr: [`ERROR: ${normalizeError2(error)}`]
13000
13437
  };
13001
13438
  }
13002
13439
  }
@@ -13008,8 +13445,8 @@ function createAllureHandler(deps = {}) {
13008
13445
  };
13009
13446
  }
13010
13447
  const resolved = resolveJiraConfig(parsed, env, cwd);
13011
- const zipPath = import_node_path8.default.resolve(cwd, zipPathInput);
13012
- const attachmentFilename = import_node_path8.default.basename(zipPath);
13448
+ const zipPath = import_node_path10.default.resolve(cwd, zipPathInput);
13449
+ const attachmentFilename = import_node_path10.default.basename(zipPath);
13013
13450
  if (!resolved.config) {
13014
13451
  return {
13015
13452
  exitCode: ExitCode.ValidationError,
@@ -13022,14 +13459,14 @@ function createAllureHandler(deps = {}) {
13022
13459
  } catch (error) {
13023
13460
  return {
13024
13461
  exitCode: ExitCode.ValidationError,
13025
- stderr: [`ERROR: ${normalizeError(error)}`]
13462
+ stderr: [`ERROR: ${normalizeError2(error)}`]
13026
13463
  };
13027
13464
  }
13028
13465
  if (dryRun) {
13029
13466
  if (useJson) {
13030
13467
  return {
13031
13468
  exitCode: ExitCode.Success,
13032
- stdout: toJsonLine(buildJsonOutput({
13469
+ stdout: toJsonLine(buildJsonOutput2({
13033
13470
  issueKey,
13034
13471
  uploaded: false,
13035
13472
  commentAdded: false,
@@ -13072,7 +13509,7 @@ function createAllureHandler(deps = {}) {
13072
13509
  if (useJson) {
13073
13510
  return {
13074
13511
  exitCode: ExitCode.Success,
13075
- stdout: toJsonLine(buildJsonOutput({
13512
+ stdout: toJsonLine(buildJsonOutput2({
13076
13513
  issueKey,
13077
13514
  uploaded: true,
13078
13515
  commentAdded,
@@ -13096,15 +13533,15 @@ function createAllureHandler(deps = {}) {
13096
13533
  } catch (error) {
13097
13534
  return {
13098
13535
  exitCode: ExitCode.RemoteError,
13099
- stderr: [`ERROR: ${normalizeError(error)}`]
13536
+ stderr: [`ERROR: ${normalizeError2(error)}`]
13100
13537
  };
13101
13538
  }
13102
13539
  };
13103
13540
  }
13104
13541
 
13105
13542
  // src/bdd.ts
13106
- var import_node_fs8 = require("node:fs");
13107
- var import_node_path9 = __toESM(require("node:path"), 1);
13543
+ var import_node_fs10 = require("node:fs");
13544
+ var import_node_path11 = __toESM(require("node:path"), 1);
13108
13545
  var import_yazl = __toESM(require_yazl(), 1);
13109
13546
 
13110
13547
  // src/forgeClient.ts
@@ -13269,7 +13706,7 @@ var CONFIG_FLAGS3 = /* @__PURE__ */ new Set([
13269
13706
  "--jira-email",
13270
13707
  "--jira-api-token"
13271
13708
  ]);
13272
- function parseArgs3(args) {
13709
+ function parseArgs4(args) {
13273
13710
  const flags = {};
13274
13711
  const boolFlags = /* @__PURE__ */ new Set();
13275
13712
  const unknownFlags = [];
@@ -13316,7 +13753,7 @@ function pickConfigArgs3(parsed) {
13316
13753
  }
13317
13754
  return args;
13318
13755
  }
13319
- function normalizeError2(error) {
13756
+ function normalizeError3(error) {
13320
13757
  if (error instanceof ForgeClientError) {
13321
13758
  return `${error.code}: ${error.message}`;
13322
13759
  }
@@ -13408,7 +13845,7 @@ function parseScenarioFeatureFile(raw, filePath) {
13408
13845
  }
13409
13846
  const scenarioTag = tags.map((tag) => tag.match(SCENARIO_METADATA_PATTERN)).find((match) => Boolean(match));
13410
13847
  const scenarioKeyFromTag = scenarioTag?.[1]?.trim().toUpperCase() ?? "";
13411
- const fileKeyMatch = import_node_path9.default.basename(filePath ?? "").match(/^(SC-\d+)\.feature$/i);
13848
+ const fileKeyMatch = import_node_path11.default.basename(filePath ?? "").match(/^(SC-\d+)\.feature$/i);
13412
13849
  const scenarioKeyFromFile = fileKeyMatch?.[1]?.trim().toUpperCase() ?? "";
13413
13850
  const scenarioKey = scenarioKeyFromTag || scenarioKeyFromFile;
13414
13851
  if (scenarioKeyFromTag && scenarioKeyFromFile && scenarioKeyFromTag !== scenarioKeyFromFile) {
@@ -13526,7 +13963,7 @@ function toJsonExportManifestItems(items) {
13526
13963
  async function defaultCreateZipArchive(archivePath, entries) {
13527
13964
  await new Promise((resolve, reject) => {
13528
13965
  const zip = new import_yazl.ZipFile();
13529
- const output = zip.outputStream.pipe((0, import_node_fs8.createWriteStream)(archivePath));
13966
+ const output = zip.outputStream.pipe((0, import_node_fs10.createWriteStream)(archivePath));
13530
13967
  output.on("close", () => resolve());
13531
13968
  output.on("error", reject);
13532
13969
  zip.outputStream.on("error", reject);
@@ -13556,14 +13993,14 @@ function missingProjectResponse() {
13556
13993
  function createBddHandler(deps = {}) {
13557
13994
  const cwd = deps.cwd ?? process.cwd();
13558
13995
  const env = deps.env ?? process.env;
13559
- const mkdir = deps.mkdir ?? ((targetPath, options) => (0, import_node_fs8.mkdirSync)(targetPath, options));
13560
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs8.readFileSync)(filePath, "utf8"));
13561
- const readDir = deps.readDir ?? ((dirPath) => (0, import_node_fs8.readdirSync)(dirPath));
13562
- const writeFile = deps.writeFile ?? ((filePath, content) => (0, import_node_fs8.writeFileSync)(filePath, content, "utf8"));
13996
+ const mkdir = deps.mkdir ?? ((targetPath, options) => (0, import_node_fs10.mkdirSync)(targetPath, options));
13997
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs10.readFileSync)(filePath, "utf8"));
13998
+ const readDir = deps.readDir ?? ((dirPath) => (0, import_node_fs10.readdirSync)(dirPath));
13999
+ const writeFile = deps.writeFile ?? ((filePath, content) => (0, import_node_fs10.writeFileSync)(filePath, content, "utf8"));
13563
14000
  const createZipArchive = deps.createZipArchive ?? defaultCreateZipArchive;
13564
14001
  return async (request, context) => {
13565
14002
  const [subcommand, ...restArgs] = request.args;
13566
- const parsed = parseArgs3(restArgs);
14003
+ const parsed = parseArgs4(restArgs);
13567
14004
  const requestedFormat = (parsed.flags["--format"] ?? "").trim().toLowerCase();
13568
14005
  const outputFormat = parsed.boolFlags.has("--json") ? "json" : parsed.boolFlags.has("--feature") ? "feature" : requestedFormat || "text";
13569
14006
  const config = resolveCliConfig(pickConfigArgs3(parsed), env, cwd);
@@ -13603,7 +14040,7 @@ function createBddHandler(deps = {}) {
13603
14040
  stderr: ["ERROR: Missing required --output-dir for testops bdd features export."]
13604
14041
  };
13605
14042
  }
13606
- const outputDir = import_node_path9.default.resolve(cwd, outputDirFlag);
14043
+ const outputDir = import_node_path11.default.resolve(cwd, outputDirFlag);
13607
14044
  try {
13608
14045
  const result = await context.invokeForgeContract("listBddFeatures", {
13609
14046
  context: {
@@ -13639,7 +14076,7 @@ function createBddHandler(deps = {}) {
13639
14076
  `
13640
14077
  }));
13641
14078
  for (let index = 0; index < features.length; index += 1) {
13642
- writeFile(import_node_path9.default.join(outputDir, fileNames[index]), exportEntries[index].content);
14079
+ writeFile(import_node_path11.default.join(outputDir, fileNames[index]), exportEntries[index].content);
13643
14080
  }
13644
14081
  const manifest = {
13645
14082
  action: "bdd-features-export",
@@ -13652,7 +14089,7 @@ function createBddHandler(deps = {}) {
13652
14089
  };
13653
14090
  const manifestContent = `${JSON.stringify(manifest, null, 2)}
13654
14091
  `;
13655
- writeFile(import_node_path9.default.join(outputDir, "manifest.json"), manifestContent);
14092
+ writeFile(import_node_path11.default.join(outputDir, "manifest.json"), manifestContent);
13656
14093
  const archivePath = `${outputDir}.zip`;
13657
14094
  if (zipRequested) {
13658
14095
  await createZipArchive(archivePath, [
@@ -13691,7 +14128,7 @@ function createBddHandler(deps = {}) {
13691
14128
  } catch (error) {
13692
14129
  return {
13693
14130
  exitCode: ExitCode.TransportError,
13694
- stderr: [`ERROR: ${normalizeError2(error)}`]
14131
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13695
14132
  };
13696
14133
  }
13697
14134
  }
@@ -13722,7 +14159,7 @@ function createBddHandler(deps = {}) {
13722
14159
  stderr: ["ERROR: Missing required --output-dir for testops bdd scenarios export."]
13723
14160
  };
13724
14161
  }
13725
- const outputDir = import_node_path9.default.resolve(cwd, outputDirFlag);
14162
+ const outputDir = import_node_path11.default.resolve(cwd, outputDirFlag);
13726
14163
  const zipRequested = parsed.boolFlags.has("--zip");
13727
14164
  try {
13728
14165
  const scenariosResult = await context.invokeForgeContract("listBddScenarios", {
@@ -13757,7 +14194,7 @@ function createBddHandler(deps = {}) {
13757
14194
  const fileName = scenarioToExportFileName(scenario);
13758
14195
  const fileContent = `${scenarioToFeatureLines(scenario).join("\n")}
13759
14196
  `;
13760
- writeFile(import_node_path9.default.join(outputDir, fileName), fileContent);
14197
+ writeFile(import_node_path11.default.join(outputDir, fileName), fileContent);
13761
14198
  return {
13762
14199
  scenario,
13763
14200
  fileName,
@@ -13783,7 +14220,7 @@ function createBddHandler(deps = {}) {
13783
14220
  };
13784
14221
  const manifestContent = `${JSON.stringify(manifest, null, 2)}
13785
14222
  `;
13786
- writeFile(import_node_path9.default.join(outputDir, "manifest.json"), manifestContent);
14223
+ writeFile(import_node_path11.default.join(outputDir, "manifest.json"), manifestContent);
13787
14224
  const archivePath = `${outputDir}.zip`;
13788
14225
  if (zipRequested) {
13789
14226
  await createZipArchive(archivePath, [
@@ -13826,13 +14263,13 @@ function createBddHandler(deps = {}) {
13826
14263
  } catch (error) {
13827
14264
  return {
13828
14265
  exitCode: ExitCode.TransportError,
13829
- stderr: [`ERROR: ${normalizeError2(error)}`]
14266
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13830
14267
  };
13831
14268
  }
13832
14269
  }
13833
14270
  if (nested === "import") {
13834
- const sourceFile = parsed.flags["--file"] ? import_node_path9.default.resolve(cwd, parsed.flags["--file"]) : "";
13835
- const inputDir = parsed.flags["--input-dir"] ? import_node_path9.default.resolve(cwd, parsed.flags["--input-dir"]) : "";
14271
+ const sourceFile = parsed.flags["--file"] ? import_node_path11.default.resolve(cwd, parsed.flags["--file"]) : "";
14272
+ const inputDir = parsed.flags["--input-dir"] ? import_node_path11.default.resolve(cwd, parsed.flags["--input-dir"]) : "";
13836
14273
  const dryRun = parsed.boolFlags.has("--dry-run");
13837
14274
  if (!sourceFile && !inputDir) {
13838
14275
  return {
@@ -13841,7 +14278,7 @@ function createBddHandler(deps = {}) {
13841
14278
  };
13842
14279
  }
13843
14280
  try {
13844
- const scenarioFiles = sourceFile ? [sourceFile] : readDir(inputDir).filter((entry) => entry.toLowerCase().endsWith(".feature")).map((entry) => import_node_path9.default.join(inputDir, entry)).sort((left, right) => left.localeCompare(right));
14281
+ const scenarioFiles = sourceFile ? [sourceFile] : readDir(inputDir).filter((entry) => entry.toLowerCase().endsWith(".feature")).map((entry) => import_node_path11.default.join(inputDir, entry)).sort((left, right) => left.localeCompare(right));
13845
14282
  if (scenarioFiles.length === 0) {
13846
14283
  return {
13847
14284
  exitCode: ExitCode.ValidationError,
@@ -13980,7 +14417,7 @@ function createBddHandler(deps = {}) {
13980
14417
  } catch (error) {
13981
14418
  return {
13982
14419
  exitCode: ExitCode.TransportError,
13983
- stderr: [`ERROR: ${normalizeError2(error)}`]
14420
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13984
14421
  };
13985
14422
  }
13986
14423
  }
@@ -14083,7 +14520,7 @@ function createBddHandler(deps = {}) {
14083
14520
  } catch (error) {
14084
14521
  return {
14085
14522
  exitCode: ExitCode.TransportError,
14086
- stderr: [`ERROR: ${normalizeError2(error)}`]
14523
+ stderr: [`ERROR: ${normalizeError3(error)}`]
14087
14524
  };
14088
14525
  }
14089
14526
  }
@@ -14136,7 +14573,7 @@ function createBddHandler(deps = {}) {
14136
14573
  } catch (error) {
14137
14574
  return {
14138
14575
  exitCode: ExitCode.TransportError,
14139
- stderr: [`ERROR: ${normalizeError2(error)}`]
14576
+ stderr: [`ERROR: ${normalizeError3(error)}`]
14140
14577
  };
14141
14578
  }
14142
14579
  }
@@ -14157,7 +14594,7 @@ var CONFIG_FLAGS4 = /* @__PURE__ */ new Set([
14157
14594
  "--jira-email",
14158
14595
  "--jira-api-token"
14159
14596
  ]);
14160
- function parseArgs4(args) {
14597
+ function parseArgs5(args) {
14161
14598
  const flags = {};
14162
14599
  const boolFlags = /* @__PURE__ */ new Set();
14163
14600
  const unknownFlags = [];
@@ -14195,7 +14632,7 @@ function pickConfigArgs4(parsed) {
14195
14632
  }
14196
14633
  return args;
14197
14634
  }
14198
- function normalizeError3(error) {
14635
+ function normalizeError4(error) {
14199
14636
  if (error instanceof ForgeClientError) {
14200
14637
  return `${error.code}: ${error.message}`;
14201
14638
  }
@@ -14329,7 +14766,7 @@ function createCasesHandler(deps = {}) {
14329
14766
  const env = deps.env ?? process.env;
14330
14767
  return async (request, context) => {
14331
14768
  const [subcommand, ...restArgs] = request.args;
14332
- const parsed = parseArgs4(restArgs);
14769
+ const parsed = parseArgs5(restArgs);
14333
14770
  const useJson = parsed.boolFlags.has("--json");
14334
14771
  const config = resolveCliConfig(pickConfigArgs4(parsed), env, cwd);
14335
14772
  const projectKey = config.values.projectKey;
@@ -14376,7 +14813,7 @@ function createCasesHandler(deps = {}) {
14376
14813
  } catch (error) {
14377
14814
  return {
14378
14815
  exitCode: ExitCode.TransportError,
14379
- stderr: [`ERROR: ${normalizeError3(error)}`]
14816
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14380
14817
  };
14381
14818
  }
14382
14819
  }
@@ -14418,7 +14855,7 @@ function createCasesHandler(deps = {}) {
14418
14855
  } catch (error) {
14419
14856
  return {
14420
14857
  exitCode: ExitCode.TransportError,
14421
- stderr: [`ERROR: ${normalizeError3(error)}`]
14858
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14422
14859
  };
14423
14860
  }
14424
14861
  }
@@ -14481,7 +14918,7 @@ function createCasesHandler(deps = {}) {
14481
14918
  } catch (error) {
14482
14919
  return {
14483
14920
  exitCode: ExitCode.TransportError,
14484
- stderr: [`ERROR: ${normalizeError3(error)}`]
14921
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14485
14922
  };
14486
14923
  }
14487
14924
  }
@@ -14544,7 +14981,7 @@ function createCasesHandler(deps = {}) {
14544
14981
  } catch (error) {
14545
14982
  return {
14546
14983
  exitCode: ExitCode.TransportError,
14547
- stderr: [`ERROR: ${normalizeError3(error)}`]
14984
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14548
14985
  };
14549
14986
  }
14550
14987
  }
@@ -14556,6 +14993,7 @@ function createCasesHandler(deps = {}) {
14556
14993
  }
14557
14994
 
14558
14995
  // src/doctor.ts
14996
+ var import_node_child_process3 = require("node:child_process");
14559
14997
  var CONFIG_FLAGS5 = /* @__PURE__ */ new Set([
14560
14998
  "--config",
14561
14999
  "--base-url",
@@ -14565,11 +15003,16 @@ var CONFIG_FLAGS5 = /* @__PURE__ */ new Set([
14565
15003
  "--jira-email",
14566
15004
  "--jira-api-token"
14567
15005
  ]);
14568
- function parseArgs5(args) {
15006
+ var DIRECT_JIRA_FLAGS = /* @__PURE__ */ new Set([
15007
+ "--site",
15008
+ "--email",
15009
+ "--api-token"
15010
+ ]);
15011
+ function parseArgs6(args) {
14569
15012
  const flags = {};
14570
15013
  const boolFlags = /* @__PURE__ */ new Set();
14571
15014
  const unknownFlags = [];
14572
- const valuedFlags = /* @__PURE__ */ new Set([...CONFIG_FLAGS5]);
15015
+ const valuedFlags = /* @__PURE__ */ new Set([...CONFIG_FLAGS5, ...DIRECT_JIRA_FLAGS]);
14573
15016
  const supportedBoolFlags = /* @__PURE__ */ new Set(["--json", "--check-context"]);
14574
15017
  for (let i = 0; i < args.length; i += 1) {
14575
15018
  const token = args[i];
@@ -14635,6 +15078,49 @@ function checkFromServiceResult(result) {
14635
15078
  message: `Forge endpoint reachable (service returned ${result.error.code}).`
14636
15079
  };
14637
15080
  }
15081
+ function normalizeSite3(value) {
15082
+ return value.trim().replace(/\/+$/, "");
15083
+ }
15084
+ function hasValue(value) {
15085
+ return value.trim().length > 0;
15086
+ }
15087
+ function resolveAllureJiraConfig(parsed, env, cwd) {
15088
+ const resolution = resolveCliConfig(pickConfigArgs5(parsed), env, cwd);
15089
+ const site = normalizeSite3(parsed.flags["--site"] ?? env.JIRA_SITE ?? resolution.values.baseUrl);
15090
+ const email = (parsed.flags["--email"] ?? resolution.values.jiraEmail).trim();
15091
+ const apiToken = (parsed.flags["--api-token"] ?? resolution.values.jiraApiToken).trim();
15092
+ const issueKey = (parsed.flags["--issue-key"] ?? "").trim();
15093
+ const errors = [];
15094
+ if (!hasValue(site)) {
15095
+ errors.push("Missing Jira site. Pass --site, set JIRA_SITE/JIRA_BASE_URL, or configure baseUrl.");
15096
+ }
15097
+ if (!hasValue(email)) {
15098
+ errors.push("Missing Jira email. Pass --email or set JIRA_EMAIL.");
15099
+ }
15100
+ if (!hasValue(apiToken)) {
15101
+ errors.push("Missing Jira API token. Pass --api-token or set JIRA_API_TOKEN.");
15102
+ }
15103
+ return { site, email, apiToken, issueKey, errors };
15104
+ }
15105
+ function commandCheck(deps, command, args, passMessage, warnMessage) {
15106
+ try {
15107
+ deps.execFileSync(command, [...args], {
15108
+ encoding: "utf8",
15109
+ stdio: ["ignore", "pipe", "pipe"]
15110
+ });
15111
+ return {
15112
+ name: command,
15113
+ status: "pass",
15114
+ message: passMessage
15115
+ };
15116
+ } catch {
15117
+ return {
15118
+ name: command,
15119
+ status: "warn",
15120
+ message: warnMessage
15121
+ };
15122
+ }
15123
+ }
14638
15124
  function toJsonPayload(summary) {
14639
15125
  return {
14640
15126
  status: summary.status,
@@ -14667,9 +15153,11 @@ function toHumanLines2(summary) {
14667
15153
  function createDoctorHandler(deps = {}) {
14668
15154
  const cwd = deps.cwd ?? process.cwd();
14669
15155
  const env = deps.env ?? process.env;
15156
+ const runCommand = deps.execFileSync ?? import_node_child_process3.execFileSync;
15157
+ const listAttachments = deps.listIssueAttachments ?? listIssueAttachments;
14670
15158
  return async (request, context) => {
14671
- const [, ...subArgs] = request.args;
14672
- const parsed = parseArgs5(subArgs);
15159
+ const [subcommand, ...subArgs] = request.args;
15160
+ const parsed = parseArgs6(subArgs);
14673
15161
  const useJson = parsed.boolFlags.has("--json");
14674
15162
  const enforceContextCheck = parsed.boolFlags.has("--check-context");
14675
15163
  const checks = [];
@@ -14685,6 +15173,70 @@ function createDoctorHandler(deps = {}) {
14685
15173
  stdout: useJson ? toJsonLine(toJsonPayload(summary2)) : toHumanLines2(summary2)
14686
15174
  };
14687
15175
  }
15176
+ if (subcommand === "allure") {
15177
+ const config = resolveAllureJiraConfig(parsed, env, cwd);
15178
+ checks.push({
15179
+ name: "allure_jira_credentials",
15180
+ status: config.errors.length === 0 ? "pass" : "fail",
15181
+ message: config.errors.length === 0 ? "Jira site, email, and API token are configured for Allure upload/download." : config.errors.join(" ")
15182
+ });
15183
+ if (config.issueKey && config.errors.length === 0) {
15184
+ try {
15185
+ const attachments = await listAttachments({
15186
+ site: config.site,
15187
+ email: config.email,
15188
+ apiToken: config.apiToken,
15189
+ issueKey: config.issueKey
15190
+ });
15191
+ checks.push({
15192
+ name: "allure_jira_issue_access",
15193
+ status: "pass",
15194
+ message: `Jira issue ${config.issueKey} is reachable; ${attachments.length} attachment(s) visible.`
15195
+ });
15196
+ } catch (error) {
15197
+ checks.push({
15198
+ name: "allure_jira_issue_access",
15199
+ status: "fail",
15200
+ message: normalizeConnectivityError(error)
15201
+ });
15202
+ }
15203
+ } else if (!config.issueKey) {
15204
+ checks.push({
15205
+ name: "allure_jira_issue_access",
15206
+ status: "skip",
15207
+ message: "Issue access check skipped. Pass --issue-key to verify Jira read/download access for a specific issue."
15208
+ });
15209
+ }
15210
+ checks.push(commandCheck(
15211
+ { execFileSync: runCommand },
15212
+ "unzip",
15213
+ ["-v"],
15214
+ "unzip is available for manual ZIP inspection.",
15215
+ "unzip was not found. The future built-in opener can avoid this, but manual inspection will be less convenient."
15216
+ ));
15217
+ checks.push(commandCheck(
15218
+ { execFileSync: runCommand },
15219
+ "java",
15220
+ ["-version"],
15221
+ "Java is available for optional raw allure-results workflows.",
15222
+ "Java was not found. This does not block generated Allure HTML ZIP upload/download/open; it only matters for raw allure-results generation."
15223
+ ));
15224
+ checks.push(commandCheck(
15225
+ { execFileSync: runCommand },
15226
+ "allure",
15227
+ ["--version"],
15228
+ "Allure CLI is available for optional raw allure-results workflows.",
15229
+ "Allure CLI was not found. This does not block generated Allure HTML ZIP upload/download/open."
15230
+ ));
15231
+ const summary2 = {
15232
+ status: computeOverallStatus(checks),
15233
+ checks
15234
+ };
15235
+ return {
15236
+ exitCode: pickDoctorExitCode(summary2),
15237
+ stdout: useJson ? toJsonLine(toJsonPayload(summary2)) : toHumanLines2(summary2)
15238
+ };
15239
+ }
14688
15240
  const configArgs = pickConfigArgs5(parsed);
14689
15241
  const resolution = resolveCliConfig(configArgs, env, cwd);
14690
15242
  const configValidation = validateCliConfigResolution(resolution);
@@ -14773,8 +15325,8 @@ function createDoctorHandler(deps = {}) {
14773
15325
  }
14774
15326
 
14775
15327
  // src/ingestFeature.ts
14776
- var import_node_fs9 = require("node:fs");
14777
- var import_node_path10 = __toESM(require("node:path"), 1);
15328
+ var import_node_fs11 = require("node:fs");
15329
+ var import_node_path12 = __toESM(require("node:path"), 1);
14778
15330
  var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
14779
15331
  "--config",
14780
15332
  "--base-url",
@@ -14784,7 +15336,7 @@ var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
14784
15336
  "--jira-email",
14785
15337
  "--jira-api-token"
14786
15338
  ]);
14787
- function parseArgs6(args) {
15339
+ function parseArgs7(args) {
14788
15340
  const flags = {};
14789
15341
  const boolFlags = /* @__PURE__ */ new Set();
14790
15342
  const unknownFlags = [];
@@ -14850,7 +15402,7 @@ function summarizeSuccess(result) {
14850
15402
  }
14851
15403
  return lines;
14852
15404
  }
14853
- function normalizeError4(error) {
15405
+ function normalizeError5(error) {
14854
15406
  if (error instanceof ForgeClientError) {
14855
15407
  return `${error.code}: ${error.message}`;
14856
15408
  }
@@ -14860,18 +15412,18 @@ function normalizeError4(error) {
14860
15412
  return "Unknown ingest error.";
14861
15413
  }
14862
15414
  function createIngestFeatureHandler(deps = {}) {
14863
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs9.readFileSync)(filePath, "utf8"));
14864
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs9.readFileSync)(0, "utf8"));
15415
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs11.readFileSync)(filePath, "utf8"));
15416
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs11.readFileSync)(0, "utf8"));
14865
15417
  const cwd = deps.cwd ?? process.cwd();
14866
15418
  const env = deps.env ?? process.env;
14867
15419
  return async (request, context) => {
14868
15420
  const [, ...subArgs] = request.args;
14869
- const parsed = parseArgs6(subArgs);
15421
+ const parsed = parseArgs7(subArgs);
14870
15422
  const configArgs = pickConfigArgs6(parsed);
14871
15423
  const config = resolveCliConfig(configArgs, env, cwd);
14872
15424
  const projectKey = config.values.projectKey;
14873
15425
  const issueKey = config.values.issueKey;
14874
- const sourceFile = parsed.flags["--file"] ? import_node_path10.default.resolve(cwd, parsed.flags["--file"]) : "";
15426
+ const sourceFile = parsed.flags["--file"] ? import_node_path12.default.resolve(cwd, parsed.flags["--file"]) : "";
14875
15427
  const useStdin = parsed.boolFlags.has("--stdin");
14876
15428
  const useJson = parsed.boolFlags.has("--json");
14877
15429
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -14887,7 +15439,7 @@ function createIngestFeatureHandler(deps = {}) {
14887
15439
  } catch (error) {
14888
15440
  return {
14889
15441
  exitCode: ExitCode.InternalError,
14890
- stderr: [`ERROR: Failed to read feature source: ${normalizeError4(error)}`]
15442
+ stderr: [`ERROR: Failed to read feature source: ${normalizeError5(error)}`]
14891
15443
  };
14892
15444
  }
14893
15445
  const name = (parsed.flags["--name"] ?? deriveNameFromGherkin(gherkin)).trim();
@@ -14982,7 +15534,7 @@ function createIngestFeatureHandler(deps = {}) {
14982
15534
  status: "failed",
14983
15535
  featureName: name,
14984
15536
  source: useStdin ? "stdin" : sourceFile,
14985
- errorMessage: normalizeError4(error)
15537
+ errorMessage: normalizeError5(error)
14986
15538
  })
14987
15539
  };
14988
15540
  }
@@ -14993,15 +15545,15 @@ function createIngestFeatureHandler(deps = {}) {
14993
15545
  `Feature: ${name}`,
14994
15546
  `Source: ${useStdin ? "stdin" : sourceFile}`
14995
15547
  ],
14996
- stderr: [`ERROR: ${normalizeError4(error)}`]
15548
+ stderr: [`ERROR: ${normalizeError5(error)}`]
14997
15549
  };
14998
15550
  }
14999
15551
  };
15000
15552
  }
15001
15553
 
15002
15554
  // src/runUpload.ts
15003
- var import_node_fs10 = require("node:fs");
15004
- var import_node_path11 = __toESM(require("node:path"), 1);
15555
+ var import_node_fs12 = require("node:fs");
15556
+ var import_node_path13 = __toESM(require("node:path"), 1);
15005
15557
  var STEP_RESULTS = [
15006
15558
  StepResult.Passed,
15007
15559
  StepResult.Failed,
@@ -15017,7 +15569,7 @@ var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
15017
15569
  "--jira-email",
15018
15570
  "--jira-api-token"
15019
15571
  ]);
15020
- function parseArgs7(args) {
15572
+ function parseArgs8(args) {
15021
15573
  const flags = {};
15022
15574
  const boolFlags = /* @__PURE__ */ new Set();
15023
15575
  const unknownFlags = [];
@@ -15063,7 +15615,7 @@ function pickConfigArgs7(parsed) {
15063
15615
  }
15064
15616
  return configArgs;
15065
15617
  }
15066
- function normalizeError5(error) {
15618
+ function normalizeError6(error) {
15067
15619
  if (error instanceof ForgeClientError) {
15068
15620
  return `${error.code}: ${error.message}`;
15069
15621
  }
@@ -15142,19 +15694,19 @@ function summarizeRunResult(result) {
15142
15694
  return lines;
15143
15695
  }
15144
15696
  function createRunUploadHandler(deps = {}) {
15145
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs10.readFileSync)(filePath, "utf8"));
15146
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs10.readFileSync)(0, "utf8"));
15697
+ const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs12.readFileSync)(filePath, "utf8"));
15698
+ const readStdin = deps.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8"));
15147
15699
  const cwd = deps.cwd ?? process.cwd();
15148
15700
  const env = deps.env ?? process.env;
15149
15701
  return async (request, context) => {
15150
15702
  const [, ...subArgs] = request.args;
15151
- const parsed = parseArgs7(subArgs);
15703
+ const parsed = parseArgs8(subArgs);
15152
15704
  const configArgs = pickConfigArgs7(parsed);
15153
15705
  const config = resolveCliConfig(configArgs, env, cwd);
15154
15706
  const projectKey = config.values.projectKey;
15155
15707
  const issueKey = config.values.issueKey;
15156
15708
  const testCaseKey = parsed.flags["--test-case-key"]?.trim().toUpperCase() ?? "";
15157
- const sourceFile = parsed.flags["--file"] ? import_node_path11.default.resolve(cwd, parsed.flags["--file"]) : "";
15709
+ const sourceFile = parsed.flags["--file"] ? import_node_path13.default.resolve(cwd, parsed.flags["--file"]) : "";
15158
15710
  const useStdin = parsed.boolFlags.has("--stdin");
15159
15711
  const useJson = parsed.boolFlags.has("--json");
15160
15712
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -15182,7 +15734,7 @@ function createRunUploadHandler(deps = {}) {
15182
15734
  } catch (error) {
15183
15735
  return {
15184
15736
  exitCode: ExitCode.InternalError,
15185
- stderr: [`ERROR: Failed to read run payload source: ${normalizeError5(error)}`]
15737
+ stderr: [`ERROR: Failed to read run payload source: ${normalizeError6(error)}`]
15186
15738
  };
15187
15739
  }
15188
15740
  const payloadFromSource = parseRunPayload(raw);
@@ -15212,7 +15764,7 @@ function createRunUploadHandler(deps = {}) {
15212
15764
  } catch (error) {
15213
15765
  return {
15214
15766
  exitCode: ExitCode.TransportError,
15215
- stderr: [`ERROR: ${normalizeError5(error)}`]
15767
+ stderr: [`ERROR: ${normalizeError6(error)}`]
15216
15768
  };
15217
15769
  }
15218
15770
  }
@@ -15292,7 +15844,7 @@ function createRunUploadHandler(deps = {}) {
15292
15844
  featureName: runInput.featureName,
15293
15845
  scenarioName: runInput.scenarioName,
15294
15846
  executedAt: runInput.executedAt,
15295
- errorMessage: normalizeError5(error)
15847
+ errorMessage: normalizeError6(error)
15296
15848
  })
15297
15849
  };
15298
15850
  }
@@ -15304,7 +15856,7 @@ function createRunUploadHandler(deps = {}) {
15304
15856
  `Scenario: ${runInput.scenarioName}`,
15305
15857
  `ExecutedAt: ${runInput.executedAt}`
15306
15858
  ],
15307
- stderr: [`ERROR: ${normalizeError5(error)}`]
15859
+ stderr: [`ERROR: ${normalizeError6(error)}`]
15308
15860
  };
15309
15861
  }
15310
15862
  };
@@ -15320,7 +15872,7 @@ var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
15320
15872
  "--jira-email",
15321
15873
  "--jira-api-token"
15322
15874
  ]);
15323
- function parseArgs8(args) {
15875
+ function parseArgs9(args) {
15324
15876
  const flags = {};
15325
15877
  const boolFlags = /* @__PURE__ */ new Set();
15326
15878
  const unknownFlags = [];
@@ -15358,7 +15910,7 @@ function pickConfigArgs8(parsed) {
15358
15910
  }
15359
15911
  return args;
15360
15912
  }
15361
- function normalizeError6(error) {
15913
+ function normalizeError7(error) {
15362
15914
  if (error instanceof ForgeClientError) {
15363
15915
  return `${error.code}: ${error.message}`;
15364
15916
  }
@@ -15422,7 +15974,7 @@ function createRunsHandler(deps = {}) {
15422
15974
  const env = deps.env ?? process.env;
15423
15975
  return async (request, context) => {
15424
15976
  const [subcommand, ...restArgs] = request.args;
15425
- const parsed = parseArgs8(restArgs);
15977
+ const parsed = parseArgs9(restArgs);
15426
15978
  const useJson = parsed.boolFlags.has("--json");
15427
15979
  const config = resolveCliConfig(pickConfigArgs8(parsed), env, cwd);
15428
15980
  const projectKey = config.values.projectKey;
@@ -15476,7 +16028,7 @@ function createRunsHandler(deps = {}) {
15476
16028
  } catch (error) {
15477
16029
  return {
15478
16030
  exitCode: ExitCode.TransportError,
15479
- stderr: [`ERROR: ${normalizeError6(error)}`]
16031
+ stderr: [`ERROR: ${normalizeError7(error)}`]
15480
16032
  };
15481
16033
  }
15482
16034
  }
@@ -15497,7 +16049,7 @@ var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
15497
16049
  "--jira-email",
15498
16050
  "--jira-api-token"
15499
16051
  ]);
15500
- function parseArgs9(args) {
16052
+ function parseArgs10(args) {
15501
16053
  const flags = {};
15502
16054
  const boolFlags = /* @__PURE__ */ new Set();
15503
16055
  const unknownFlags = [];
@@ -15535,7 +16087,7 @@ function pickConfigArgs9(parsed) {
15535
16087
  }
15536
16088
  return args;
15537
16089
  }
15538
- function normalizeError7(error) {
16090
+ function normalizeError8(error) {
15539
16091
  if (error instanceof ForgeClientError) {
15540
16092
  return `${error.code}: ${error.message}`;
15541
16093
  }
@@ -15617,7 +16169,7 @@ function createSuitesHandler(deps = {}) {
15617
16169
  const env = deps.env ?? process.env;
15618
16170
  return async (request, context) => {
15619
16171
  const [subcommand, ...restArgs] = request.args;
15620
- const parsed = parseArgs9(restArgs);
16172
+ const parsed = parseArgs10(restArgs);
15621
16173
  const useJson = parsed.boolFlags.has("--json");
15622
16174
  const config = resolveCliConfig(pickConfigArgs9(parsed), env, cwd);
15623
16175
  const projectKey = config.values.projectKey;
@@ -15664,7 +16216,7 @@ function createSuitesHandler(deps = {}) {
15664
16216
  } catch (error) {
15665
16217
  return {
15666
16218
  exitCode: ExitCode.TransportError,
15667
- stderr: [`ERROR: ${normalizeError7(error)}`]
16219
+ stderr: [`ERROR: ${normalizeError8(error)}`]
15668
16220
  };
15669
16221
  }
15670
16222
  }
@@ -15705,7 +16257,7 @@ function createSuitesHandler(deps = {}) {
15705
16257
  } catch (error) {
15706
16258
  return {
15707
16259
  exitCode: ExitCode.TransportError,
15708
- stderr: [`ERROR: ${normalizeError7(error)}`]
16260
+ stderr: [`ERROR: ${normalizeError8(error)}`]
15709
16261
  };
15710
16262
  }
15711
16263
  }
@@ -15769,7 +16321,7 @@ function createSuitesHandler(deps = {}) {
15769
16321
  } catch (error) {
15770
16322
  return {
15771
16323
  exitCode: ExitCode.TransportError,
15772
- stderr: [`ERROR: ${normalizeError7(error)}`]
16324
+ stderr: [`ERROR: ${normalizeError8(error)}`]
15773
16325
  };
15774
16326
  }
15775
16327
  }
@@ -15791,7 +16343,7 @@ var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
15791
16343
  "--jira-api-token"
15792
16344
  ]);
15793
16345
  var RECONCILE_CONFIRM_TOKEN = "RECONCILE";
15794
- function parseArgs10(args) {
16346
+ function parseArgs11(args) {
15795
16347
  const flags = {};
15796
16348
  const boolFlags = /* @__PURE__ */ new Set();
15797
16349
  const unknownFlags = [];
@@ -15829,7 +16381,7 @@ function pickConfigArgs10(parsed) {
15829
16381
  }
15830
16382
  return args;
15831
16383
  }
15832
- function normalizeError8(error) {
16384
+ function normalizeError9(error) {
15833
16385
  if (error instanceof ForgeClientError) {
15834
16386
  return `${error.code}: ${error.message}`;
15835
16387
  }
@@ -15880,7 +16432,7 @@ function createSyncHandler(deps = {}) {
15880
16432
  const env = deps.env ?? process.env;
15881
16433
  return async (request, context) => {
15882
16434
  const [subcommand, ...restArgs] = request.args;
15883
- const parsed = parseArgs10(restArgs);
16435
+ const parsed = parseArgs11(restArgs);
15884
16436
  const useJson = parsed.boolFlags.has("--json");
15885
16437
  const config = resolveCliConfig(pickConfigArgs10(parsed), env, cwd);
15886
16438
  const projectKey = config.values.projectKey;
@@ -15932,7 +16484,7 @@ function createSyncHandler(deps = {}) {
15932
16484
  } catch (error) {
15933
16485
  return {
15934
16486
  exitCode: ExitCode.TransportError,
15935
- stderr: [`ERROR: ${normalizeError8(error)}`]
16487
+ stderr: [`ERROR: ${normalizeError9(error)}`]
15936
16488
  };
15937
16489
  }
15938
16490
  }
@@ -15978,7 +16530,7 @@ function createSyncHandler(deps = {}) {
15978
16530
  } catch (error) {
15979
16531
  return {
15980
16532
  exitCode: ExitCode.TransportError,
15981
- stderr: [`ERROR: ${normalizeError8(error)}`]
16533
+ stderr: [`ERROR: ${normalizeError9(error)}`]
15982
16534
  };
15983
16535
  }
15984
16536
  }
@@ -15993,8 +16545,8 @@ function createSyncHandler(deps = {}) {
15993
16545
  var COMMAND_REGISTRY = [
15994
16546
  {
15995
16547
  name: "allure",
15996
- description: "Allure evidence upload/download commands for Jira issues",
15997
- subcommands: ["upload", "download"],
16548
+ description: "Allure evidence upload/download/open commands for Jira issues",
16549
+ subcommands: ["upload", "download", "open"],
15998
16550
  handler: createAllureHandler()
15999
16551
  },
16000
16552
  {
@@ -16049,7 +16601,7 @@ var COMMAND_REGISTRY = [
16049
16601
  {
16050
16602
  name: "doctor",
16051
16603
  description: "Preflight and health checks",
16052
- subcommands: ["check"],
16604
+ subcommands: ["check", "allure"],
16053
16605
  handler: createDoctorHandler()
16054
16606
  },
16055
16607
  {
@@ -16234,6 +16786,7 @@ function showTopLevelHelp() {
16234
16786
  "",
16235
16787
  "Available products:",
16236
16788
  " testops TestOps \u2014 test management, BDD, execution, reporting",
16789
+ " allure Allure evidence diagnostics and local report workflows (doctor, open)",
16237
16790
  "",
16238
16791
  "Boundary:",
16239
16792
  " Thin client over Forge contracts or Jira Cloud APIs.",
@@ -16243,6 +16796,29 @@ function showTopLevelHelp() {
16243
16796
  console.log(line);
16244
16797
  }
16245
16798
  }
16799
+ function showAllureHelp() {
16800
+ const lines = [
16801
+ "Automatify Allure",
16802
+ "",
16803
+ "Usage:",
16804
+ " automatify allure doctor [options]",
16805
+ " automatify allure open <zipPath> [options]",
16806
+ "",
16807
+ "Commands:",
16808
+ " doctor Check local Allure/Jira readiness",
16809
+ " open Extract a generated Allure HTML report ZIP and serve it locally",
16810
+ "",
16811
+ "Examples:",
16812
+ " automatify allure doctor",
16813
+ " automatify allure doctor --json",
16814
+ " automatify allure doctor --issue-key ABC-123",
16815
+ " automatify allure open ./allure-report.zip",
16816
+ " automatify allure open ./allure-report.zip --port 8080 --no-open"
16817
+ ];
16818
+ for (const line of lines) {
16819
+ console.log(line);
16820
+ }
16821
+ }
16246
16822
  async function main() {
16247
16823
  const args = process.argv.slice(2);
16248
16824
  const [productArg, ...restArgs] = args;
@@ -16251,6 +16827,28 @@ async function main() {
16251
16827
  process.exitCode = 0;
16252
16828
  return;
16253
16829
  }
16830
+ if (productArg === "allure") {
16831
+ const [commandArg, ...commandRest] = restArgs;
16832
+ if (!commandArg || commandArg === "--help" || commandArg === "-h" || commandArg === "help") {
16833
+ showAllureHelp();
16834
+ process.exitCode = 0;
16835
+ return;
16836
+ }
16837
+ if (commandArg === "doctor") {
16838
+ const exitCode2 = await runCli(["doctor", "allure", ...commandRest]);
16839
+ process.exitCode = exitCode2;
16840
+ return;
16841
+ }
16842
+ if (commandArg === "open") {
16843
+ const exitCode2 = await runCli(["allure", "open", ...commandRest]);
16844
+ process.exitCode = exitCode2;
16845
+ return;
16846
+ }
16847
+ console.error(`Unknown allure command: ${commandArg}`);
16848
+ console.error("Run `automatify allure --help` to see available commands.");
16849
+ process.exitCode = 1;
16850
+ return;
16851
+ }
16254
16852
  if (productArg !== "testops") {
16255
16853
  console.error(`Unknown product: ${productArg}`);
16256
16854
  console.error("Run `automatify --help` to see available products.");