@automatify-au/cli 0.1.9 → 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,7 +12447,8 @@ function createAutoHandler(deps = {}) {
12447
12447
  }
12448
12448
 
12449
12449
  // src/allure.ts
12450
- var import_node_path8 = __toESM(require("node:path"), 1);
12450
+ var import_node_path10 = __toESM(require("node:path"), 1);
12451
+ var import_node_fs9 = require("node:fs");
12451
12452
 
12452
12453
  // ../../packages/jira-client/src/adf.ts
12453
12454
  function paragraph(text = "") {
@@ -12533,6 +12534,62 @@ async function uploadIssueAttachment(params) {
12533
12534
  size: typeof first?.size === "number" ? first.size : fileStat.size
12534
12535
  };
12535
12536
  }
12537
+ function parseIssueAttachment(value) {
12538
+ if (!value || typeof value !== "object") {
12539
+ return void 0;
12540
+ }
12541
+ const item = value;
12542
+ if (typeof item.id !== "string" || typeof item.filename !== "string") {
12543
+ return void 0;
12544
+ }
12545
+ return {
12546
+ id: item.id,
12547
+ filename: item.filename,
12548
+ self: typeof item.self === "string" ? item.self : void 0,
12549
+ content: typeof item.content === "string" ? item.content : void 0,
12550
+ size: typeof item.size === "number" ? item.size : void 0,
12551
+ created: typeof item.created === "string" ? item.created : void 0
12552
+ };
12553
+ }
12554
+ async function listIssueAttachments(params) {
12555
+ const site = normalizeSite(params.site);
12556
+ const response = await fetch(`${site}/rest/api/3/issue/${encodeURIComponent(params.issueKey)}?fields=attachment`, {
12557
+ headers: {
12558
+ authorization: authHeader(params.email, params.apiToken),
12559
+ accept: "application/json"
12560
+ }
12561
+ });
12562
+ if (!response.ok) {
12563
+ throw new Error(`Jira attachment listing failed: ${await readError(response)}`);
12564
+ }
12565
+ const parsed = await response.json();
12566
+ const attachments = Array.isArray(parsed.fields?.attachment) ? parsed.fields.attachment : [];
12567
+ return attachments.map(parseIssueAttachment).filter((item) => Boolean(item));
12568
+ }
12569
+ async function downloadIssueAttachment(params) {
12570
+ const url = params.attachment.content;
12571
+ if (!url) {
12572
+ throw new Error(`Jira attachment ${params.attachment.id} does not include a downloadable URL.`);
12573
+ }
12574
+ const response = await fetch(url, {
12575
+ headers: {
12576
+ authorization: authHeader(params.email, params.apiToken)
12577
+ }
12578
+ });
12579
+ if (!response.ok) {
12580
+ throw new Error(`Jira attachment download failed: ${await readError(response)}`);
12581
+ }
12582
+ const bytes = new Uint8Array(await response.arrayBuffer());
12583
+ (0, import_node_fs5.writeFileSync)(params.outputPath, bytes);
12584
+ return {
12585
+ filename: params.attachment.filename,
12586
+ id: params.attachment.id,
12587
+ outputPath: params.outputPath,
12588
+ self: params.attachment.self,
12589
+ content: params.attachment.content,
12590
+ size: bytes.byteLength
12591
+ };
12592
+ }
12536
12593
  async function addIssueComment(params) {
12537
12594
  const site = normalizeSite(params.site);
12538
12595
  const response = await fetch(`${site}/rest/api/3/issue/${encodeURIComponent(params.issueKey)}/comment`, {
@@ -12624,6 +12681,438 @@ async function parseAllureReportZip(zipPath) {
12624
12681
  };
12625
12682
  }
12626
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
+
12627
13116
  // src/allure.ts
12628
13117
  var CONFIG_FLAGS2 = /* @__PURE__ */ new Set([
12629
13118
  "--config",
@@ -12634,7 +13123,7 @@ var CONFIG_FLAGS2 = /* @__PURE__ */ new Set([
12634
13123
  "--jira-email",
12635
13124
  "--jira-api-token"
12636
13125
  ]);
12637
- function parseArgs2(args) {
13126
+ function parseArgs3(args) {
12638
13127
  const positionals = [];
12639
13128
  const flags = {};
12640
13129
  const boolFlags = /* @__PURE__ */ new Set();
@@ -12643,9 +13132,12 @@ function parseArgs2(args) {
12643
13132
  "--site",
12644
13133
  "--email",
12645
13134
  "--api-token",
13135
+ "--attachment-id",
13136
+ "--filename",
13137
+ "--output-dir",
12646
13138
  ...CONFIG_FLAGS2
12647
13139
  ]);
12648
- const supportedBoolFlags = /* @__PURE__ */ new Set(["--no-comment", "--comment", "--json", "--dry-run", "--verbose"]);
13140
+ const supportedBoolFlags = /* @__PURE__ */ new Set(["--no-comment", "--comment", "--json", "--dry-run", "--verbose", "--force"]);
12649
13141
  for (let i = 0; i < args.length; i += 1) {
12650
13142
  const token = args[i];
12651
13143
  if (!token.startsWith("--")) {
@@ -12682,12 +13174,29 @@ function pickConfigArgs2(parsed) {
12682
13174
  function normalizeSite2(value) {
12683
13175
  return value.trim().replace(/\/+$/, "");
12684
13176
  }
12685
- function normalizeError(error) {
13177
+ function normalizeError2(error) {
12686
13178
  return error instanceof Error ? error.message : "Unknown Allure upload error.";
12687
13179
  }
12688
13180
  function hasCredential(value) {
12689
13181
  return value.trim().length > 0;
12690
13182
  }
13183
+ function resolveJiraConfig(parsed, env, cwd) {
13184
+ const config = resolveCliConfig(pickConfigArgs2(parsed), env, cwd);
13185
+ const site = normalizeSite2(parsed.flags["--site"] ?? env.JIRA_SITE ?? config.values.baseUrl);
13186
+ const email = (parsed.flags["--email"] ?? config.values.jiraEmail).trim();
13187
+ const apiToken = (parsed.flags["--api-token"] ?? config.values.jiraApiToken).trim();
13188
+ const errors = [];
13189
+ if (!hasCredential(site)) {
13190
+ errors.push("Missing Jira site. Pass --site, set JIRA_SITE/JIRA_BASE_URL, or configure baseUrl.");
13191
+ }
13192
+ if (!hasCredential(email)) {
13193
+ errors.push("Missing Jira email. Pass --email or set JIRA_EMAIL.");
13194
+ }
13195
+ if (!hasCredential(apiToken)) {
13196
+ errors.push("Missing Jira API token. Pass --api-token or set JIRA_API_TOKEN.");
13197
+ }
13198
+ return errors.length > 0 ? { errors } : { config: { site, email, apiToken }, errors: [] };
13199
+ }
12691
13200
  function formatHumanSummary(summary) {
12692
13201
  const counts = summary.counts;
12693
13202
  return [
@@ -12712,7 +13221,65 @@ function formatHumanAttachment(attachment) {
12712
13221
  }
12713
13222
  return lines;
12714
13223
  }
12715
- function buildJsonOutput(params) {
13224
+ function isAllureReportZip(attachment) {
13225
+ const filename = attachment.filename.toLowerCase();
13226
+ return filename.endsWith(".zip") && filename.includes("allure-report");
13227
+ }
13228
+ function isZip(attachment) {
13229
+ return attachment.filename.toLowerCase().endsWith(".zip");
13230
+ }
13231
+ function sortNewestFirst(attachments) {
13232
+ return [...attachments].sort((left, right) => {
13233
+ const leftTime = left.created ? Date.parse(left.created) : 0;
13234
+ const rightTime = right.created ? Date.parse(right.created) : 0;
13235
+ if (rightTime !== leftTime) {
13236
+ return rightTime - leftTime;
13237
+ }
13238
+ return right.id.localeCompare(left.id);
13239
+ });
13240
+ }
13241
+ function selectAllureAttachment(params) {
13242
+ const { attachments, attachmentId, filename } = params;
13243
+ if (attachmentId) {
13244
+ const match = attachments.find((attachment) => attachment.id === attachmentId);
13245
+ if (!match) {
13246
+ throw new Error(`Could not find Jira attachment with id ${attachmentId}.`);
13247
+ }
13248
+ return match;
13249
+ }
13250
+ if (filename) {
13251
+ const matches = sortNewestFirst(attachments.filter((attachment) => attachment.filename === filename));
13252
+ if (matches.length === 0) {
13253
+ throw new Error(`Could not find Jira attachment named ${filename}.`);
13254
+ }
13255
+ return matches[0];
13256
+ }
13257
+ const allureMatches = sortNewestFirst(attachments.filter(isAllureReportZip));
13258
+ if (allureMatches.length > 0) {
13259
+ return allureMatches[0];
13260
+ }
13261
+ const zipMatches = sortNewestFirst(attachments.filter(isZip));
13262
+ if (zipMatches.length > 0) {
13263
+ return zipMatches[0];
13264
+ }
13265
+ throw new Error("Could not find an Allure report ZIP attachment on this Jira issue.");
13266
+ }
13267
+ function buildDownloadJsonOutput(params) {
13268
+ return {
13269
+ issueKey: params.issueKey,
13270
+ dryRun: params.dryRun,
13271
+ downloaded: params.downloaded,
13272
+ attachment: {
13273
+ filename: params.attachment.filename,
13274
+ id: params.attachment.id,
13275
+ ...params.attachment.self ? { self: params.attachment.self } : {},
13276
+ ...params.attachment.content ? { content: params.attachment.content } : {},
13277
+ ...typeof params.attachment.size === "number" ? { size: params.attachment.size } : {}
13278
+ },
13279
+ outputPath: params.outputPath
13280
+ };
13281
+ }
13282
+ function buildJsonOutput2(params) {
12716
13283
  const attachment = {
12717
13284
  filename: params.attachment.filename
12718
13285
  };
@@ -12750,25 +13317,126 @@ function createAllureHandler(deps = {}) {
12750
13317
  const parseZip = deps.parseAllureReportZip ?? parseAllureReportZip;
12751
13318
  const uploadAttachment = deps.uploadIssueAttachment ?? uploadIssueAttachment;
12752
13319
  const createComment = deps.addIssueComment ?? addIssueComment;
12753
- return async (request) => {
13320
+ const listAttachments = deps.listIssueAttachments ?? listIssueAttachments;
13321
+ const downloadAttachment = deps.downloadIssueAttachment ?? downloadIssueAttachment;
13322
+ const openHandler = createAllureOpenHandler({ cwd });
13323
+ return async (request, context) => {
12754
13324
  const [subcommand, ...subArgs] = request.args;
12755
- if (subcommand !== "upload") {
13325
+ if (subcommand === "open") {
13326
+ return openHandler({ ...request, args: subArgs }, context);
13327
+ }
13328
+ if (subcommand !== "upload" && subcommand !== "download") {
12756
13329
  return {
12757
13330
  exitCode: ExitCode.UsageError,
12758
- stderr: ["Usage: automatify testops allure upload <issueKey> <zipPath> [options]"]
13331
+ stderr: [
13332
+ "Usage:",
13333
+ " automatify testops allure upload <issueKey> <zipPath> [options]",
13334
+ " automatify testops allure download <issueKey> [options]",
13335
+ " automatify testops allure open <zipPath> [options]"
13336
+ ]
12759
13337
  };
12760
13338
  }
12761
- const parsed = parseArgs2(subArgs);
13339
+ const parsed = parseArgs3(subArgs);
12762
13340
  const useJson = parsed.boolFlags.has("--json");
12763
13341
  const dryRun = parsed.boolFlags.has("--dry-run");
12764
13342
  const verbose = parsed.boolFlags.has("--verbose");
12765
13343
  const addComment = !parsed.boolFlags.has("--no-comment");
13344
+ const force = parsed.boolFlags.has("--force");
12766
13345
  if (parsed.unknownFlags.length > 0) {
12767
13346
  return {
12768
13347
  exitCode: ExitCode.UsageError,
12769
13348
  stderr: [`ERROR: Unknown or invalid flags: ${parsed.unknownFlags.join(", ")}`]
12770
13349
  };
12771
13350
  }
13351
+ if (subcommand === "download") {
13352
+ const [issueKey2] = parsed.positionals;
13353
+ if (!issueKey2 || parsed.positionals.length > 1) {
13354
+ return {
13355
+ exitCode: ExitCode.UsageError,
13356
+ stderr: ["Usage: automatify testops allure download <issueKey> [options]"]
13357
+ };
13358
+ }
13359
+ const resolved2 = resolveJiraConfig(parsed, env, cwd);
13360
+ if (!resolved2.config) {
13361
+ return {
13362
+ exitCode: ExitCode.ValidationError,
13363
+ stderr: resolved2.errors.map((line) => `ERROR: ${line}`)
13364
+ };
13365
+ }
13366
+ try {
13367
+ const attachments = await listAttachments({
13368
+ ...resolved2.config,
13369
+ issueKey: issueKey2
13370
+ });
13371
+ const selected = selectAllureAttachment({
13372
+ attachments,
13373
+ attachmentId: parsed.flags["--attachment-id"],
13374
+ filename: parsed.flags["--filename"]
13375
+ });
13376
+ const outputDir = import_node_path10.default.resolve(cwd, parsed.flags["--output-dir"] ?? ".");
13377
+ const outputPath = import_node_path10.default.join(outputDir, selected.filename);
13378
+ if (dryRun) {
13379
+ if (useJson) {
13380
+ return {
13381
+ exitCode: ExitCode.Success,
13382
+ stdout: toJsonLine(buildDownloadJsonOutput({
13383
+ issueKey: issueKey2,
13384
+ dryRun: true,
13385
+ downloaded: false,
13386
+ attachment: selected,
13387
+ outputPath
13388
+ }))
13389
+ };
13390
+ }
13391
+ return {
13392
+ exitCode: ExitCode.Success,
13393
+ stdout: [
13394
+ `Selected Jira attachment ${selected.filename} (${selected.id}) from issue ${issueKey2}.`,
13395
+ ...verbose && selected.created ? [`Created: ${selected.created}`] : [],
13396
+ `Dry run: would download to ${outputPath}.`
13397
+ ]
13398
+ };
13399
+ }
13400
+ (0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
13401
+ if ((0, import_node_fs9.existsSync)(outputPath) && !force) {
13402
+ return {
13403
+ exitCode: ExitCode.ValidationError,
13404
+ stderr: [`ERROR: Output file already exists: ${outputPath}. Pass --force to overwrite.`]
13405
+ };
13406
+ }
13407
+ const downloaded = await downloadAttachment({
13408
+ ...resolved2.config,
13409
+ attachment: selected,
13410
+ outputPath
13411
+ });
13412
+ if (useJson) {
13413
+ return {
13414
+ exitCode: ExitCode.Success,
13415
+ stdout: toJsonLine(buildDownloadJsonOutput({
13416
+ issueKey: issueKey2,
13417
+ dryRun: false,
13418
+ downloaded: true,
13419
+ attachment: downloaded,
13420
+ outputPath
13421
+ }))
13422
+ };
13423
+ }
13424
+ return {
13425
+ exitCode: ExitCode.Success,
13426
+ stdout: [
13427
+ `Downloaded Allure report attachment from Jira issue ${issueKey2}.`,
13428
+ `Attachment: ${downloaded.filename}`,
13429
+ `Attachment ID: ${downloaded.id}`,
13430
+ `Saved to: ${downloaded.outputPath}`
13431
+ ]
13432
+ };
13433
+ } catch (error) {
13434
+ return {
13435
+ exitCode: ExitCode.RemoteError,
13436
+ stderr: [`ERROR: ${normalizeError2(error)}`]
13437
+ };
13438
+ }
13439
+ }
12772
13440
  const [issueKey, zipPathInput] = parsed.positionals;
12773
13441
  if (!issueKey || !zipPathInput || parsed.positionals.length > 2) {
12774
13442
  return {
@@ -12776,26 +13444,13 @@ function createAllureHandler(deps = {}) {
12776
13444
  stderr: ["Usage: automatify testops allure upload <issueKey> <zipPath> [options]"]
12777
13445
  };
12778
13446
  }
12779
- const config = resolveCliConfig(pickConfigArgs2(parsed), env, cwd);
12780
- const site = normalizeSite2(parsed.flags["--site"] ?? env.JIRA_SITE ?? config.values.baseUrl);
12781
- const email = (parsed.flags["--email"] ?? config.values.jiraEmail).trim();
12782
- const apiToken = (parsed.flags["--api-token"] ?? config.values.jiraApiToken).trim();
12783
- const zipPath = import_node_path8.default.resolve(cwd, zipPathInput);
12784
- const attachmentFilename = import_node_path8.default.basename(zipPath);
12785
- const credentialErrors = [];
12786
- if (!hasCredential(site)) {
12787
- credentialErrors.push("Missing Jira site. Pass --site, set JIRA_SITE/JIRA_BASE_URL, or configure baseUrl.");
12788
- }
12789
- if (!hasCredential(email)) {
12790
- credentialErrors.push("Missing Jira email. Pass --email or set JIRA_EMAIL.");
12791
- }
12792
- if (!hasCredential(apiToken)) {
12793
- credentialErrors.push("Missing Jira API token. Pass --api-token or set JIRA_API_TOKEN.");
12794
- }
12795
- if (credentialErrors.length > 0) {
13447
+ const resolved = resolveJiraConfig(parsed, env, cwd);
13448
+ const zipPath = import_node_path10.default.resolve(cwd, zipPathInput);
13449
+ const attachmentFilename = import_node_path10.default.basename(zipPath);
13450
+ if (!resolved.config) {
12796
13451
  return {
12797
13452
  exitCode: ExitCode.ValidationError,
12798
- stderr: credentialErrors.map((line) => `ERROR: ${line}`)
13453
+ stderr: resolved.errors.map((line) => `ERROR: ${line}`)
12799
13454
  };
12800
13455
  }
12801
13456
  let summary;
@@ -12804,14 +13459,14 @@ function createAllureHandler(deps = {}) {
12804
13459
  } catch (error) {
12805
13460
  return {
12806
13461
  exitCode: ExitCode.ValidationError,
12807
- stderr: [`ERROR: ${normalizeError(error)}`]
13462
+ stderr: [`ERROR: ${normalizeError2(error)}`]
12808
13463
  };
12809
13464
  }
12810
13465
  if (dryRun) {
12811
13466
  if (useJson) {
12812
13467
  return {
12813
13468
  exitCode: ExitCode.Success,
12814
- stdout: toJsonLine(buildJsonOutput({
13469
+ stdout: toJsonLine(buildJsonOutput2({
12815
13470
  issueKey,
12816
13471
  uploaded: false,
12817
13472
  commentAdded: false,
@@ -12834,18 +13489,14 @@ function createAllureHandler(deps = {}) {
12834
13489
  }
12835
13490
  try {
12836
13491
  const attachment = await uploadAttachment({
12837
- site,
12838
- email,
12839
- apiToken,
13492
+ ...resolved.config,
12840
13493
  issueKey,
12841
13494
  filePath: zipPath
12842
13495
  });
12843
13496
  let commentAdded = false;
12844
13497
  if (addComment) {
12845
13498
  await createComment({
12846
- site,
12847
- email,
12848
- apiToken,
13499
+ ...resolved.config,
12849
13500
  issueKey,
12850
13501
  body: buildAllureEvidenceCommentAdf({
12851
13502
  counts: summary.counts,
@@ -12858,7 +13509,7 @@ function createAllureHandler(deps = {}) {
12858
13509
  if (useJson) {
12859
13510
  return {
12860
13511
  exitCode: ExitCode.Success,
12861
- stdout: toJsonLine(buildJsonOutput({
13512
+ stdout: toJsonLine(buildJsonOutput2({
12862
13513
  issueKey,
12863
13514
  uploaded: true,
12864
13515
  commentAdded,
@@ -12882,15 +13533,15 @@ function createAllureHandler(deps = {}) {
12882
13533
  } catch (error) {
12883
13534
  return {
12884
13535
  exitCode: ExitCode.RemoteError,
12885
- stderr: [`ERROR: ${normalizeError(error)}`]
13536
+ stderr: [`ERROR: ${normalizeError2(error)}`]
12886
13537
  };
12887
13538
  }
12888
13539
  };
12889
13540
  }
12890
13541
 
12891
13542
  // src/bdd.ts
12892
- var import_node_fs7 = require("node:fs");
12893
- 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);
12894
13545
  var import_yazl = __toESM(require_yazl(), 1);
12895
13546
 
12896
13547
  // src/forgeClient.ts
@@ -13055,7 +13706,7 @@ var CONFIG_FLAGS3 = /* @__PURE__ */ new Set([
13055
13706
  "--jira-email",
13056
13707
  "--jira-api-token"
13057
13708
  ]);
13058
- function parseArgs3(args) {
13709
+ function parseArgs4(args) {
13059
13710
  const flags = {};
13060
13711
  const boolFlags = /* @__PURE__ */ new Set();
13061
13712
  const unknownFlags = [];
@@ -13102,7 +13753,7 @@ function pickConfigArgs3(parsed) {
13102
13753
  }
13103
13754
  return args;
13104
13755
  }
13105
- function normalizeError2(error) {
13756
+ function normalizeError3(error) {
13106
13757
  if (error instanceof ForgeClientError) {
13107
13758
  return `${error.code}: ${error.message}`;
13108
13759
  }
@@ -13194,7 +13845,7 @@ function parseScenarioFeatureFile(raw, filePath) {
13194
13845
  }
13195
13846
  const scenarioTag = tags.map((tag) => tag.match(SCENARIO_METADATA_PATTERN)).find((match) => Boolean(match));
13196
13847
  const scenarioKeyFromTag = scenarioTag?.[1]?.trim().toUpperCase() ?? "";
13197
- 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);
13198
13849
  const scenarioKeyFromFile = fileKeyMatch?.[1]?.trim().toUpperCase() ?? "";
13199
13850
  const scenarioKey = scenarioKeyFromTag || scenarioKeyFromFile;
13200
13851
  if (scenarioKeyFromTag && scenarioKeyFromFile && scenarioKeyFromTag !== scenarioKeyFromFile) {
@@ -13312,7 +13963,7 @@ function toJsonExportManifestItems(items) {
13312
13963
  async function defaultCreateZipArchive(archivePath, entries) {
13313
13964
  await new Promise((resolve, reject) => {
13314
13965
  const zip = new import_yazl.ZipFile();
13315
- const output = zip.outputStream.pipe((0, import_node_fs7.createWriteStream)(archivePath));
13966
+ const output = zip.outputStream.pipe((0, import_node_fs10.createWriteStream)(archivePath));
13316
13967
  output.on("close", () => resolve());
13317
13968
  output.on("error", reject);
13318
13969
  zip.outputStream.on("error", reject);
@@ -13342,14 +13993,14 @@ function missingProjectResponse() {
13342
13993
  function createBddHandler(deps = {}) {
13343
13994
  const cwd = deps.cwd ?? process.cwd();
13344
13995
  const env = deps.env ?? process.env;
13345
- const mkdir = deps.mkdir ?? ((targetPath, options) => (0, import_node_fs7.mkdirSync)(targetPath, options));
13346
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs7.readFileSync)(filePath, "utf8"));
13347
- const readDir = deps.readDir ?? ((dirPath) => (0, import_node_fs7.readdirSync)(dirPath));
13348
- const writeFile = deps.writeFile ?? ((filePath, content) => (0, import_node_fs7.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"));
13349
14000
  const createZipArchive = deps.createZipArchive ?? defaultCreateZipArchive;
13350
14001
  return async (request, context) => {
13351
14002
  const [subcommand, ...restArgs] = request.args;
13352
- const parsed = parseArgs3(restArgs);
14003
+ const parsed = parseArgs4(restArgs);
13353
14004
  const requestedFormat = (parsed.flags["--format"] ?? "").trim().toLowerCase();
13354
14005
  const outputFormat = parsed.boolFlags.has("--json") ? "json" : parsed.boolFlags.has("--feature") ? "feature" : requestedFormat || "text";
13355
14006
  const config = resolveCliConfig(pickConfigArgs3(parsed), env, cwd);
@@ -13389,7 +14040,7 @@ function createBddHandler(deps = {}) {
13389
14040
  stderr: ["ERROR: Missing required --output-dir for testops bdd features export."]
13390
14041
  };
13391
14042
  }
13392
- const outputDir = import_node_path9.default.resolve(cwd, outputDirFlag);
14043
+ const outputDir = import_node_path11.default.resolve(cwd, outputDirFlag);
13393
14044
  try {
13394
14045
  const result = await context.invokeForgeContract("listBddFeatures", {
13395
14046
  context: {
@@ -13425,7 +14076,7 @@ function createBddHandler(deps = {}) {
13425
14076
  `
13426
14077
  }));
13427
14078
  for (let index = 0; index < features.length; index += 1) {
13428
- 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);
13429
14080
  }
13430
14081
  const manifest = {
13431
14082
  action: "bdd-features-export",
@@ -13438,7 +14089,7 @@ function createBddHandler(deps = {}) {
13438
14089
  };
13439
14090
  const manifestContent = `${JSON.stringify(manifest, null, 2)}
13440
14091
  `;
13441
- writeFile(import_node_path9.default.join(outputDir, "manifest.json"), manifestContent);
14092
+ writeFile(import_node_path11.default.join(outputDir, "manifest.json"), manifestContent);
13442
14093
  const archivePath = `${outputDir}.zip`;
13443
14094
  if (zipRequested) {
13444
14095
  await createZipArchive(archivePath, [
@@ -13477,7 +14128,7 @@ function createBddHandler(deps = {}) {
13477
14128
  } catch (error) {
13478
14129
  return {
13479
14130
  exitCode: ExitCode.TransportError,
13480
- stderr: [`ERROR: ${normalizeError2(error)}`]
14131
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13481
14132
  };
13482
14133
  }
13483
14134
  }
@@ -13508,7 +14159,7 @@ function createBddHandler(deps = {}) {
13508
14159
  stderr: ["ERROR: Missing required --output-dir for testops bdd scenarios export."]
13509
14160
  };
13510
14161
  }
13511
- const outputDir = import_node_path9.default.resolve(cwd, outputDirFlag);
14162
+ const outputDir = import_node_path11.default.resolve(cwd, outputDirFlag);
13512
14163
  const zipRequested = parsed.boolFlags.has("--zip");
13513
14164
  try {
13514
14165
  const scenariosResult = await context.invokeForgeContract("listBddScenarios", {
@@ -13543,7 +14194,7 @@ function createBddHandler(deps = {}) {
13543
14194
  const fileName = scenarioToExportFileName(scenario);
13544
14195
  const fileContent = `${scenarioToFeatureLines(scenario).join("\n")}
13545
14196
  `;
13546
- writeFile(import_node_path9.default.join(outputDir, fileName), fileContent);
14197
+ writeFile(import_node_path11.default.join(outputDir, fileName), fileContent);
13547
14198
  return {
13548
14199
  scenario,
13549
14200
  fileName,
@@ -13569,7 +14220,7 @@ function createBddHandler(deps = {}) {
13569
14220
  };
13570
14221
  const manifestContent = `${JSON.stringify(manifest, null, 2)}
13571
14222
  `;
13572
- writeFile(import_node_path9.default.join(outputDir, "manifest.json"), manifestContent);
14223
+ writeFile(import_node_path11.default.join(outputDir, "manifest.json"), manifestContent);
13573
14224
  const archivePath = `${outputDir}.zip`;
13574
14225
  if (zipRequested) {
13575
14226
  await createZipArchive(archivePath, [
@@ -13612,13 +14263,13 @@ function createBddHandler(deps = {}) {
13612
14263
  } catch (error) {
13613
14264
  return {
13614
14265
  exitCode: ExitCode.TransportError,
13615
- stderr: [`ERROR: ${normalizeError2(error)}`]
14266
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13616
14267
  };
13617
14268
  }
13618
14269
  }
13619
14270
  if (nested === "import") {
13620
- const sourceFile = parsed.flags["--file"] ? import_node_path9.default.resolve(cwd, parsed.flags["--file"]) : "";
13621
- 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"]) : "";
13622
14273
  const dryRun = parsed.boolFlags.has("--dry-run");
13623
14274
  if (!sourceFile && !inputDir) {
13624
14275
  return {
@@ -13627,7 +14278,7 @@ function createBddHandler(deps = {}) {
13627
14278
  };
13628
14279
  }
13629
14280
  try {
13630
- 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));
13631
14282
  if (scenarioFiles.length === 0) {
13632
14283
  return {
13633
14284
  exitCode: ExitCode.ValidationError,
@@ -13766,7 +14417,7 @@ function createBddHandler(deps = {}) {
13766
14417
  } catch (error) {
13767
14418
  return {
13768
14419
  exitCode: ExitCode.TransportError,
13769
- stderr: [`ERROR: ${normalizeError2(error)}`]
14420
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13770
14421
  };
13771
14422
  }
13772
14423
  }
@@ -13869,7 +14520,7 @@ function createBddHandler(deps = {}) {
13869
14520
  } catch (error) {
13870
14521
  return {
13871
14522
  exitCode: ExitCode.TransportError,
13872
- stderr: [`ERROR: ${normalizeError2(error)}`]
14523
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13873
14524
  };
13874
14525
  }
13875
14526
  }
@@ -13922,7 +14573,7 @@ function createBddHandler(deps = {}) {
13922
14573
  } catch (error) {
13923
14574
  return {
13924
14575
  exitCode: ExitCode.TransportError,
13925
- stderr: [`ERROR: ${normalizeError2(error)}`]
14576
+ stderr: [`ERROR: ${normalizeError3(error)}`]
13926
14577
  };
13927
14578
  }
13928
14579
  }
@@ -13943,7 +14594,7 @@ var CONFIG_FLAGS4 = /* @__PURE__ */ new Set([
13943
14594
  "--jira-email",
13944
14595
  "--jira-api-token"
13945
14596
  ]);
13946
- function parseArgs4(args) {
14597
+ function parseArgs5(args) {
13947
14598
  const flags = {};
13948
14599
  const boolFlags = /* @__PURE__ */ new Set();
13949
14600
  const unknownFlags = [];
@@ -13981,7 +14632,7 @@ function pickConfigArgs4(parsed) {
13981
14632
  }
13982
14633
  return args;
13983
14634
  }
13984
- function normalizeError3(error) {
14635
+ function normalizeError4(error) {
13985
14636
  if (error instanceof ForgeClientError) {
13986
14637
  return `${error.code}: ${error.message}`;
13987
14638
  }
@@ -14115,7 +14766,7 @@ function createCasesHandler(deps = {}) {
14115
14766
  const env = deps.env ?? process.env;
14116
14767
  return async (request, context) => {
14117
14768
  const [subcommand, ...restArgs] = request.args;
14118
- const parsed = parseArgs4(restArgs);
14769
+ const parsed = parseArgs5(restArgs);
14119
14770
  const useJson = parsed.boolFlags.has("--json");
14120
14771
  const config = resolveCliConfig(pickConfigArgs4(parsed), env, cwd);
14121
14772
  const projectKey = config.values.projectKey;
@@ -14162,7 +14813,7 @@ function createCasesHandler(deps = {}) {
14162
14813
  } catch (error) {
14163
14814
  return {
14164
14815
  exitCode: ExitCode.TransportError,
14165
- stderr: [`ERROR: ${normalizeError3(error)}`]
14816
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14166
14817
  };
14167
14818
  }
14168
14819
  }
@@ -14204,7 +14855,7 @@ function createCasesHandler(deps = {}) {
14204
14855
  } catch (error) {
14205
14856
  return {
14206
14857
  exitCode: ExitCode.TransportError,
14207
- stderr: [`ERROR: ${normalizeError3(error)}`]
14858
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14208
14859
  };
14209
14860
  }
14210
14861
  }
@@ -14267,7 +14918,7 @@ function createCasesHandler(deps = {}) {
14267
14918
  } catch (error) {
14268
14919
  return {
14269
14920
  exitCode: ExitCode.TransportError,
14270
- stderr: [`ERROR: ${normalizeError3(error)}`]
14921
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14271
14922
  };
14272
14923
  }
14273
14924
  }
@@ -14330,7 +14981,7 @@ function createCasesHandler(deps = {}) {
14330
14981
  } catch (error) {
14331
14982
  return {
14332
14983
  exitCode: ExitCode.TransportError,
14333
- stderr: [`ERROR: ${normalizeError3(error)}`]
14984
+ stderr: [`ERROR: ${normalizeError4(error)}`]
14334
14985
  };
14335
14986
  }
14336
14987
  }
@@ -14342,6 +14993,7 @@ function createCasesHandler(deps = {}) {
14342
14993
  }
14343
14994
 
14344
14995
  // src/doctor.ts
14996
+ var import_node_child_process3 = require("node:child_process");
14345
14997
  var CONFIG_FLAGS5 = /* @__PURE__ */ new Set([
14346
14998
  "--config",
14347
14999
  "--base-url",
@@ -14351,11 +15003,16 @@ var CONFIG_FLAGS5 = /* @__PURE__ */ new Set([
14351
15003
  "--jira-email",
14352
15004
  "--jira-api-token"
14353
15005
  ]);
14354
- function parseArgs5(args) {
15006
+ var DIRECT_JIRA_FLAGS = /* @__PURE__ */ new Set([
15007
+ "--site",
15008
+ "--email",
15009
+ "--api-token"
15010
+ ]);
15011
+ function parseArgs6(args) {
14355
15012
  const flags = {};
14356
15013
  const boolFlags = /* @__PURE__ */ new Set();
14357
15014
  const unknownFlags = [];
14358
- const valuedFlags = /* @__PURE__ */ new Set([...CONFIG_FLAGS5]);
15015
+ const valuedFlags = /* @__PURE__ */ new Set([...CONFIG_FLAGS5, ...DIRECT_JIRA_FLAGS]);
14359
15016
  const supportedBoolFlags = /* @__PURE__ */ new Set(["--json", "--check-context"]);
14360
15017
  for (let i = 0; i < args.length; i += 1) {
14361
15018
  const token = args[i];
@@ -14421,6 +15078,49 @@ function checkFromServiceResult(result) {
14421
15078
  message: `Forge endpoint reachable (service returned ${result.error.code}).`
14422
15079
  };
14423
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
+ }
14424
15124
  function toJsonPayload(summary) {
14425
15125
  return {
14426
15126
  status: summary.status,
@@ -14453,9 +15153,11 @@ function toHumanLines2(summary) {
14453
15153
  function createDoctorHandler(deps = {}) {
14454
15154
  const cwd = deps.cwd ?? process.cwd();
14455
15155
  const env = deps.env ?? process.env;
15156
+ const runCommand = deps.execFileSync ?? import_node_child_process3.execFileSync;
15157
+ const listAttachments = deps.listIssueAttachments ?? listIssueAttachments;
14456
15158
  return async (request, context) => {
14457
- const [, ...subArgs] = request.args;
14458
- const parsed = parseArgs5(subArgs);
15159
+ const [subcommand, ...subArgs] = request.args;
15160
+ const parsed = parseArgs6(subArgs);
14459
15161
  const useJson = parsed.boolFlags.has("--json");
14460
15162
  const enforceContextCheck = parsed.boolFlags.has("--check-context");
14461
15163
  const checks = [];
@@ -14471,6 +15173,70 @@ function createDoctorHandler(deps = {}) {
14471
15173
  stdout: useJson ? toJsonLine(toJsonPayload(summary2)) : toHumanLines2(summary2)
14472
15174
  };
14473
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
+ }
14474
15240
  const configArgs = pickConfigArgs5(parsed);
14475
15241
  const resolution = resolveCliConfig(configArgs, env, cwd);
14476
15242
  const configValidation = validateCliConfigResolution(resolution);
@@ -14559,8 +15325,8 @@ function createDoctorHandler(deps = {}) {
14559
15325
  }
14560
15326
 
14561
15327
  // src/ingestFeature.ts
14562
- var import_node_fs8 = require("node:fs");
14563
- 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);
14564
15330
  var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
14565
15331
  "--config",
14566
15332
  "--base-url",
@@ -14570,7 +15336,7 @@ var CONFIG_FLAGS6 = /* @__PURE__ */ new Set([
14570
15336
  "--jira-email",
14571
15337
  "--jira-api-token"
14572
15338
  ]);
14573
- function parseArgs6(args) {
15339
+ function parseArgs7(args) {
14574
15340
  const flags = {};
14575
15341
  const boolFlags = /* @__PURE__ */ new Set();
14576
15342
  const unknownFlags = [];
@@ -14636,7 +15402,7 @@ function summarizeSuccess(result) {
14636
15402
  }
14637
15403
  return lines;
14638
15404
  }
14639
- function normalizeError4(error) {
15405
+ function normalizeError5(error) {
14640
15406
  if (error instanceof ForgeClientError) {
14641
15407
  return `${error.code}: ${error.message}`;
14642
15408
  }
@@ -14646,18 +15412,18 @@ function normalizeError4(error) {
14646
15412
  return "Unknown ingest error.";
14647
15413
  }
14648
15414
  function createIngestFeatureHandler(deps = {}) {
14649
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs8.readFileSync)(filePath, "utf8"));
14650
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs8.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"));
14651
15417
  const cwd = deps.cwd ?? process.cwd();
14652
15418
  const env = deps.env ?? process.env;
14653
15419
  return async (request, context) => {
14654
15420
  const [, ...subArgs] = request.args;
14655
- const parsed = parseArgs6(subArgs);
15421
+ const parsed = parseArgs7(subArgs);
14656
15422
  const configArgs = pickConfigArgs6(parsed);
14657
15423
  const config = resolveCliConfig(configArgs, env, cwd);
14658
15424
  const projectKey = config.values.projectKey;
14659
15425
  const issueKey = config.values.issueKey;
14660
- 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"]) : "";
14661
15427
  const useStdin = parsed.boolFlags.has("--stdin");
14662
15428
  const useJson = parsed.boolFlags.has("--json");
14663
15429
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -14673,7 +15439,7 @@ function createIngestFeatureHandler(deps = {}) {
14673
15439
  } catch (error) {
14674
15440
  return {
14675
15441
  exitCode: ExitCode.InternalError,
14676
- stderr: [`ERROR: Failed to read feature source: ${normalizeError4(error)}`]
15442
+ stderr: [`ERROR: Failed to read feature source: ${normalizeError5(error)}`]
14677
15443
  };
14678
15444
  }
14679
15445
  const name = (parsed.flags["--name"] ?? deriveNameFromGherkin(gherkin)).trim();
@@ -14768,7 +15534,7 @@ function createIngestFeatureHandler(deps = {}) {
14768
15534
  status: "failed",
14769
15535
  featureName: name,
14770
15536
  source: useStdin ? "stdin" : sourceFile,
14771
- errorMessage: normalizeError4(error)
15537
+ errorMessage: normalizeError5(error)
14772
15538
  })
14773
15539
  };
14774
15540
  }
@@ -14779,15 +15545,15 @@ function createIngestFeatureHandler(deps = {}) {
14779
15545
  `Feature: ${name}`,
14780
15546
  `Source: ${useStdin ? "stdin" : sourceFile}`
14781
15547
  ],
14782
- stderr: [`ERROR: ${normalizeError4(error)}`]
15548
+ stderr: [`ERROR: ${normalizeError5(error)}`]
14783
15549
  };
14784
15550
  }
14785
15551
  };
14786
15552
  }
14787
15553
 
14788
15554
  // src/runUpload.ts
14789
- var import_node_fs9 = require("node:fs");
14790
- 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);
14791
15557
  var STEP_RESULTS = [
14792
15558
  StepResult.Passed,
14793
15559
  StepResult.Failed,
@@ -14803,7 +15569,7 @@ var CONFIG_FLAGS7 = /* @__PURE__ */ new Set([
14803
15569
  "--jira-email",
14804
15570
  "--jira-api-token"
14805
15571
  ]);
14806
- function parseArgs7(args) {
15572
+ function parseArgs8(args) {
14807
15573
  const flags = {};
14808
15574
  const boolFlags = /* @__PURE__ */ new Set();
14809
15575
  const unknownFlags = [];
@@ -14849,7 +15615,7 @@ function pickConfigArgs7(parsed) {
14849
15615
  }
14850
15616
  return configArgs;
14851
15617
  }
14852
- function normalizeError5(error) {
15618
+ function normalizeError6(error) {
14853
15619
  if (error instanceof ForgeClientError) {
14854
15620
  return `${error.code}: ${error.message}`;
14855
15621
  }
@@ -14928,19 +15694,19 @@ function summarizeRunResult(result) {
14928
15694
  return lines;
14929
15695
  }
14930
15696
  function createRunUploadHandler(deps = {}) {
14931
- const readFile = deps.readFile ?? ((filePath) => (0, import_node_fs9.readFileSync)(filePath, "utf8"));
14932
- const readStdin = deps.readStdin ?? (() => (0, import_node_fs9.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"));
14933
15699
  const cwd = deps.cwd ?? process.cwd();
14934
15700
  const env = deps.env ?? process.env;
14935
15701
  return async (request, context) => {
14936
15702
  const [, ...subArgs] = request.args;
14937
- const parsed = parseArgs7(subArgs);
15703
+ const parsed = parseArgs8(subArgs);
14938
15704
  const configArgs = pickConfigArgs7(parsed);
14939
15705
  const config = resolveCliConfig(configArgs, env, cwd);
14940
15706
  const projectKey = config.values.projectKey;
14941
15707
  const issueKey = config.values.issueKey;
14942
15708
  const testCaseKey = parsed.flags["--test-case-key"]?.trim().toUpperCase() ?? "";
14943
- 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"]) : "";
14944
15710
  const useStdin = parsed.boolFlags.has("--stdin");
14945
15711
  const useJson = parsed.boolFlags.has("--json");
14946
15712
  const sourceCount = Number(Boolean(sourceFile)) + Number(useStdin);
@@ -14968,7 +15734,7 @@ function createRunUploadHandler(deps = {}) {
14968
15734
  } catch (error) {
14969
15735
  return {
14970
15736
  exitCode: ExitCode.InternalError,
14971
- stderr: [`ERROR: Failed to read run payload source: ${normalizeError5(error)}`]
15737
+ stderr: [`ERROR: Failed to read run payload source: ${normalizeError6(error)}`]
14972
15738
  };
14973
15739
  }
14974
15740
  const payloadFromSource = parseRunPayload(raw);
@@ -14998,7 +15764,7 @@ function createRunUploadHandler(deps = {}) {
14998
15764
  } catch (error) {
14999
15765
  return {
15000
15766
  exitCode: ExitCode.TransportError,
15001
- stderr: [`ERROR: ${normalizeError5(error)}`]
15767
+ stderr: [`ERROR: ${normalizeError6(error)}`]
15002
15768
  };
15003
15769
  }
15004
15770
  }
@@ -15078,7 +15844,7 @@ function createRunUploadHandler(deps = {}) {
15078
15844
  featureName: runInput.featureName,
15079
15845
  scenarioName: runInput.scenarioName,
15080
15846
  executedAt: runInput.executedAt,
15081
- errorMessage: normalizeError5(error)
15847
+ errorMessage: normalizeError6(error)
15082
15848
  })
15083
15849
  };
15084
15850
  }
@@ -15090,7 +15856,7 @@ function createRunUploadHandler(deps = {}) {
15090
15856
  `Scenario: ${runInput.scenarioName}`,
15091
15857
  `ExecutedAt: ${runInput.executedAt}`
15092
15858
  ],
15093
- stderr: [`ERROR: ${normalizeError5(error)}`]
15859
+ stderr: [`ERROR: ${normalizeError6(error)}`]
15094
15860
  };
15095
15861
  }
15096
15862
  };
@@ -15106,7 +15872,7 @@ var CONFIG_FLAGS8 = /* @__PURE__ */ new Set([
15106
15872
  "--jira-email",
15107
15873
  "--jira-api-token"
15108
15874
  ]);
15109
- function parseArgs8(args) {
15875
+ function parseArgs9(args) {
15110
15876
  const flags = {};
15111
15877
  const boolFlags = /* @__PURE__ */ new Set();
15112
15878
  const unknownFlags = [];
@@ -15144,7 +15910,7 @@ function pickConfigArgs8(parsed) {
15144
15910
  }
15145
15911
  return args;
15146
15912
  }
15147
- function normalizeError6(error) {
15913
+ function normalizeError7(error) {
15148
15914
  if (error instanceof ForgeClientError) {
15149
15915
  return `${error.code}: ${error.message}`;
15150
15916
  }
@@ -15208,7 +15974,7 @@ function createRunsHandler(deps = {}) {
15208
15974
  const env = deps.env ?? process.env;
15209
15975
  return async (request, context) => {
15210
15976
  const [subcommand, ...restArgs] = request.args;
15211
- const parsed = parseArgs8(restArgs);
15977
+ const parsed = parseArgs9(restArgs);
15212
15978
  const useJson = parsed.boolFlags.has("--json");
15213
15979
  const config = resolveCliConfig(pickConfigArgs8(parsed), env, cwd);
15214
15980
  const projectKey = config.values.projectKey;
@@ -15262,7 +16028,7 @@ function createRunsHandler(deps = {}) {
15262
16028
  } catch (error) {
15263
16029
  return {
15264
16030
  exitCode: ExitCode.TransportError,
15265
- stderr: [`ERROR: ${normalizeError6(error)}`]
16031
+ stderr: [`ERROR: ${normalizeError7(error)}`]
15266
16032
  };
15267
16033
  }
15268
16034
  }
@@ -15283,7 +16049,7 @@ var CONFIG_FLAGS9 = /* @__PURE__ */ new Set([
15283
16049
  "--jira-email",
15284
16050
  "--jira-api-token"
15285
16051
  ]);
15286
- function parseArgs9(args) {
16052
+ function parseArgs10(args) {
15287
16053
  const flags = {};
15288
16054
  const boolFlags = /* @__PURE__ */ new Set();
15289
16055
  const unknownFlags = [];
@@ -15321,7 +16087,7 @@ function pickConfigArgs9(parsed) {
15321
16087
  }
15322
16088
  return args;
15323
16089
  }
15324
- function normalizeError7(error) {
16090
+ function normalizeError8(error) {
15325
16091
  if (error instanceof ForgeClientError) {
15326
16092
  return `${error.code}: ${error.message}`;
15327
16093
  }
@@ -15403,7 +16169,7 @@ function createSuitesHandler(deps = {}) {
15403
16169
  const env = deps.env ?? process.env;
15404
16170
  return async (request, context) => {
15405
16171
  const [subcommand, ...restArgs] = request.args;
15406
- const parsed = parseArgs9(restArgs);
16172
+ const parsed = parseArgs10(restArgs);
15407
16173
  const useJson = parsed.boolFlags.has("--json");
15408
16174
  const config = resolveCliConfig(pickConfigArgs9(parsed), env, cwd);
15409
16175
  const projectKey = config.values.projectKey;
@@ -15450,7 +16216,7 @@ function createSuitesHandler(deps = {}) {
15450
16216
  } catch (error) {
15451
16217
  return {
15452
16218
  exitCode: ExitCode.TransportError,
15453
- stderr: [`ERROR: ${normalizeError7(error)}`]
16219
+ stderr: [`ERROR: ${normalizeError8(error)}`]
15454
16220
  };
15455
16221
  }
15456
16222
  }
@@ -15491,7 +16257,7 @@ function createSuitesHandler(deps = {}) {
15491
16257
  } catch (error) {
15492
16258
  return {
15493
16259
  exitCode: ExitCode.TransportError,
15494
- stderr: [`ERROR: ${normalizeError7(error)}`]
16260
+ stderr: [`ERROR: ${normalizeError8(error)}`]
15495
16261
  };
15496
16262
  }
15497
16263
  }
@@ -15555,7 +16321,7 @@ function createSuitesHandler(deps = {}) {
15555
16321
  } catch (error) {
15556
16322
  return {
15557
16323
  exitCode: ExitCode.TransportError,
15558
- stderr: [`ERROR: ${normalizeError7(error)}`]
16324
+ stderr: [`ERROR: ${normalizeError8(error)}`]
15559
16325
  };
15560
16326
  }
15561
16327
  }
@@ -15577,7 +16343,7 @@ var CONFIG_FLAGS10 = /* @__PURE__ */ new Set([
15577
16343
  "--jira-api-token"
15578
16344
  ]);
15579
16345
  var RECONCILE_CONFIRM_TOKEN = "RECONCILE";
15580
- function parseArgs10(args) {
16346
+ function parseArgs11(args) {
15581
16347
  const flags = {};
15582
16348
  const boolFlags = /* @__PURE__ */ new Set();
15583
16349
  const unknownFlags = [];
@@ -15615,7 +16381,7 @@ function pickConfigArgs10(parsed) {
15615
16381
  }
15616
16382
  return args;
15617
16383
  }
15618
- function normalizeError8(error) {
16384
+ function normalizeError9(error) {
15619
16385
  if (error instanceof ForgeClientError) {
15620
16386
  return `${error.code}: ${error.message}`;
15621
16387
  }
@@ -15666,7 +16432,7 @@ function createSyncHandler(deps = {}) {
15666
16432
  const env = deps.env ?? process.env;
15667
16433
  return async (request, context) => {
15668
16434
  const [subcommand, ...restArgs] = request.args;
15669
- const parsed = parseArgs10(restArgs);
16435
+ const parsed = parseArgs11(restArgs);
15670
16436
  const useJson = parsed.boolFlags.has("--json");
15671
16437
  const config = resolveCliConfig(pickConfigArgs10(parsed), env, cwd);
15672
16438
  const projectKey = config.values.projectKey;
@@ -15718,7 +16484,7 @@ function createSyncHandler(deps = {}) {
15718
16484
  } catch (error) {
15719
16485
  return {
15720
16486
  exitCode: ExitCode.TransportError,
15721
- stderr: [`ERROR: ${normalizeError8(error)}`]
16487
+ stderr: [`ERROR: ${normalizeError9(error)}`]
15722
16488
  };
15723
16489
  }
15724
16490
  }
@@ -15764,7 +16530,7 @@ function createSyncHandler(deps = {}) {
15764
16530
  } catch (error) {
15765
16531
  return {
15766
16532
  exitCode: ExitCode.TransportError,
15767
- stderr: [`ERROR: ${normalizeError8(error)}`]
16533
+ stderr: [`ERROR: ${normalizeError9(error)}`]
15768
16534
  };
15769
16535
  }
15770
16536
  }
@@ -15779,8 +16545,8 @@ function createSyncHandler(deps = {}) {
15779
16545
  var COMMAND_REGISTRY = [
15780
16546
  {
15781
16547
  name: "allure",
15782
- description: "Allure evidence upload commands for Jira issues",
15783
- subcommands: ["upload"],
16548
+ description: "Allure evidence upload/download/open commands for Jira issues",
16549
+ subcommands: ["upload", "download", "open"],
15784
16550
  handler: createAllureHandler()
15785
16551
  },
15786
16552
  {
@@ -15835,7 +16601,7 @@ var COMMAND_REGISTRY = [
15835
16601
  {
15836
16602
  name: "doctor",
15837
16603
  description: "Preflight and health checks",
15838
- subcommands: ["check"],
16604
+ subcommands: ["check", "allure"],
15839
16605
  handler: createDoctorHandler()
15840
16606
  },
15841
16607
  {
@@ -16020,6 +16786,7 @@ function showTopLevelHelp() {
16020
16786
  "",
16021
16787
  "Available products:",
16022
16788
  " testops TestOps \u2014 test management, BDD, execution, reporting",
16789
+ " allure Allure evidence diagnostics and local report workflows (doctor, open)",
16023
16790
  "",
16024
16791
  "Boundary:",
16025
16792
  " Thin client over Forge contracts or Jira Cloud APIs.",
@@ -16029,6 +16796,29 @@ function showTopLevelHelp() {
16029
16796
  console.log(line);
16030
16797
  }
16031
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
+ }
16032
16822
  async function main() {
16033
16823
  const args = process.argv.slice(2);
16034
16824
  const [productArg, ...restArgs] = args;
@@ -16037,6 +16827,28 @@ async function main() {
16037
16827
  process.exitCode = 0;
16038
16828
  return;
16039
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
+ }
16040
16852
  if (productArg !== "testops") {
16041
16853
  console.error(`Unknown product: ${productArg}`);
16042
16854
  console.error("Run `automatify --help` to see available products.");