executable-stories-formatters 0.7.7 → 0.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli.js +1118 -96
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +756 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +212 -6
- package/dist/index.d.ts +212 -6
- package/dist/index.js +753 -52
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -596,8 +596,8 @@ function generateRunId(startedAtMs, projectRoot) {
|
|
|
596
596
|
const input = `${startedAtMs}::${projectRoot}`;
|
|
597
597
|
return createHash("sha1").update(input).digest("hex").slice(0, 16);
|
|
598
598
|
}
|
|
599
|
-
function slugify(
|
|
600
|
-
return
|
|
599
|
+
function slugify(text2) {
|
|
600
|
+
return text2.toLowerCase().replace(/[/\\]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
601
601
|
}
|
|
602
602
|
function generateFeatureId(uri) {
|
|
603
603
|
const pathWithoutExt = uri.replace(/\.[^.]+$/, "");
|
|
@@ -13696,7 +13696,7 @@ function renderDocEntry(entry, deps) {
|
|
|
13696
13696
|
// src/formatters/html/renderers/steps.ts
|
|
13697
13697
|
var CONTINUATION_KEYWORDS = ["And", "But", "*"];
|
|
13698
13698
|
function renderStep(step, stepResult, index, deps) {
|
|
13699
|
-
const
|
|
13699
|
+
const statusIcon2 = stepResult ? deps.getStatusIcon(stepResult.status) : "\u25CB";
|
|
13700
13700
|
const statusClass = stepResult ? `status-${stepResult.status}` : "";
|
|
13701
13701
|
const duration = stepResult && stepResult.durationMs > 0 ? `${stepResult.durationMs}ms` : "";
|
|
13702
13702
|
const keywordTrimmed = step.keyword.trim();
|
|
@@ -13705,7 +13705,7 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
13705
13705
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
13706
13706
|
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
13707
13707
|
return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
|
|
13708
|
-
<span class="step-status ${statusClass}">${
|
|
13708
|
+
<span class="step-status ${statusClass}">${statusIcon2}</span>
|
|
13709
13709
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
13710
13710
|
<span class="step-text">${textHtml}</span>
|
|
13711
13711
|
<span class="step-duration">${duration}</span>
|
|
@@ -13721,10 +13721,10 @@ function renderSteps(args, deps) {
|
|
|
13721
13721
|
|
|
13722
13722
|
// src/formatters/html/renderers/step-params.ts
|
|
13723
13723
|
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.\-])\d+(?:\.\d+)?(?![\w.\-])/g;
|
|
13724
|
-
function highlightStepParams(
|
|
13725
|
-
const matches = Array.from(
|
|
13724
|
+
function highlightStepParams(text2, deps) {
|
|
13725
|
+
const matches = Array.from(text2.matchAll(STEP_PARAM_PATTERN));
|
|
13726
13726
|
if (matches.length === 0) {
|
|
13727
|
-
return deps.escapeHtml(
|
|
13727
|
+
return deps.escapeHtml(text2);
|
|
13728
13728
|
}
|
|
13729
13729
|
let result = "";
|
|
13730
13730
|
let lastIndex = 0;
|
|
@@ -13732,13 +13732,13 @@ function highlightStepParams(text, deps) {
|
|
|
13732
13732
|
const matchStart = match.index;
|
|
13733
13733
|
const matchEnd = matchStart + match[0].length;
|
|
13734
13734
|
if (matchStart > lastIndex) {
|
|
13735
|
-
result += deps.escapeHtml(
|
|
13735
|
+
result += deps.escapeHtml(text2.slice(lastIndex, matchStart));
|
|
13736
13736
|
}
|
|
13737
13737
|
result += `<span class="step-param">${deps.escapeHtml(match[0])}</span>`;
|
|
13738
13738
|
lastIndex = matchEnd;
|
|
13739
13739
|
}
|
|
13740
|
-
if (lastIndex <
|
|
13741
|
-
result += deps.escapeHtml(
|
|
13740
|
+
if (lastIndex < text2.length) {
|
|
13741
|
+
result += deps.escapeHtml(text2.slice(lastIndex));
|
|
13742
13742
|
}
|
|
13743
13743
|
return result;
|
|
13744
13744
|
}
|
|
@@ -13756,7 +13756,7 @@ function renderTicket(ticket, template, escapeHtml3) {
|
|
|
13756
13756
|
}
|
|
13757
13757
|
function renderScenario(args, deps) {
|
|
13758
13758
|
const { tc } = args;
|
|
13759
|
-
const
|
|
13759
|
+
const statusIcon2 = deps.getStatusIcon(tc.status);
|
|
13760
13760
|
const statusClass = `status-${tc.status}`;
|
|
13761
13761
|
const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
|
|
13762
13762
|
const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
|
|
@@ -13825,7 +13825,7 @@ function renderScenario(args, deps) {
|
|
|
13825
13825
|
<div class="scenario-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
|
|
13826
13826
|
<div class="scenario-info">
|
|
13827
13827
|
<div class="scenario-title">
|
|
13828
|
-
<span class="status-icon ${statusClass}">${
|
|
13828
|
+
<span class="status-icon ${statusClass}">${statusIcon2}</span>
|
|
13829
13829
|
<span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
|
|
13830
13830
|
</div>
|
|
13831
13831
|
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
@@ -13964,11 +13964,11 @@ function buildTooltip(span, escapeHtml3) {
|
|
|
13964
13964
|
parts.push(`${key}=${formatted}`);
|
|
13965
13965
|
}
|
|
13966
13966
|
}
|
|
13967
|
-
let
|
|
13968
|
-
if (
|
|
13969
|
-
|
|
13967
|
+
let text2 = parts.join("\n");
|
|
13968
|
+
if (text2.length > TOOLTIP_MAX_LENGTH) {
|
|
13969
|
+
text2 = text2.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
|
|
13970
13970
|
}
|
|
13971
|
-
return escapeHtml3(
|
|
13971
|
+
return escapeHtml3(text2);
|
|
13972
13972
|
}
|
|
13973
13973
|
function renderTraceView(args, deps) {
|
|
13974
13974
|
if (!args.spans || args.spans.length === 0) return "";
|
|
@@ -14191,11 +14191,11 @@ function renderToc(args, deps) {
|
|
|
14191
14191
|
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
14192
14192
|
const featureSlug = `feature-${slugify(file)}`;
|
|
14193
14193
|
const scenarios = testCases.map((tc) => {
|
|
14194
|
-
const
|
|
14194
|
+
const statusIcon2 = deps.getStatusIcon(tc.status);
|
|
14195
14195
|
const statusClass = `status-${tc.status}`;
|
|
14196
14196
|
const failedClass = tc.status === "failed" ? " toc-failed" : "";
|
|
14197
14197
|
return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
|
|
14198
|
-
<span class="toc-status ${statusClass}">${
|
|
14198
|
+
<span class="toc-status ${statusClass}">${statusIcon2}</span>
|
|
14199
14199
|
${deps.escapeHtml(tc.story.scenario)}
|
|
14200
14200
|
</a>`;
|
|
14201
14201
|
}).join("\n");
|
|
@@ -14253,7 +14253,7 @@ function createHtmlFormatter(options = {}) {
|
|
|
14253
14253
|
escapeHtml,
|
|
14254
14254
|
getStatusIcon,
|
|
14255
14255
|
renderDocs,
|
|
14256
|
-
highlightStepParams: (
|
|
14256
|
+
highlightStepParams: (text2) => highlightStepParams(text2, { escapeHtml })
|
|
14257
14257
|
};
|
|
14258
14258
|
const scenarioDeps = {
|
|
14259
14259
|
escapeHtml,
|
|
@@ -15188,7 +15188,7 @@ function deterministicId(kind, salt, ...parts) {
|
|
|
15188
15188
|
|
|
15189
15189
|
// src/formatters/cucumber-messages/build-gherkin-document.ts
|
|
15190
15190
|
function buildGherkinDocumentEnvelopes(uri, testCases, synthesized, salt) {
|
|
15191
|
-
const { lineMap, featureName, featureTags, text } = synthesized;
|
|
15191
|
+
const { lineMap, featureName, featureTags, text: text2 } = synthesized;
|
|
15192
15192
|
const featureTagNodes = featureTags.map((tag, i) => ({
|
|
15193
15193
|
location: {
|
|
15194
15194
|
line: lineMap.featureTagLine ?? 1,
|
|
@@ -15257,7 +15257,7 @@ function buildGherkinDocumentEnvelopes(uri, testCases, synthesized, salt) {
|
|
|
15257
15257
|
sourceEnvelope: {
|
|
15258
15258
|
source: {
|
|
15259
15259
|
uri,
|
|
15260
|
-
data:
|
|
15260
|
+
data: text2,
|
|
15261
15261
|
mediaType: "text/x.cucumber.gherkin+plain"
|
|
15262
15262
|
}
|
|
15263
15263
|
},
|
|
@@ -15268,8 +15268,8 @@ function buildStepArguments(step, stepLine) {
|
|
|
15268
15268
|
if (!step.docs || step.docs.length === 0) return {};
|
|
15269
15269
|
const tableDocs = step.docs.filter((d) => d.kind === "table");
|
|
15270
15270
|
if (tableDocs.length > 0) {
|
|
15271
|
-
const
|
|
15272
|
-
return { dataTable: buildDataTable(
|
|
15271
|
+
const table2 = tableDocs[0];
|
|
15272
|
+
return { dataTable: buildDataTable(table2, stepLine + 1) };
|
|
15273
15273
|
}
|
|
15274
15274
|
for (const doc of step.docs) {
|
|
15275
15275
|
const ds = docEntryToDocString(doc, stepLine + 1);
|
|
@@ -15340,21 +15340,21 @@ function docEntryToDocString(doc, line) {
|
|
|
15340
15340
|
return void 0;
|
|
15341
15341
|
}
|
|
15342
15342
|
}
|
|
15343
|
-
function buildDataTable(
|
|
15343
|
+
function buildDataTable(table2, line) {
|
|
15344
15344
|
const rows = [];
|
|
15345
15345
|
rows.push({
|
|
15346
15346
|
location: { line },
|
|
15347
|
-
cells:
|
|
15347
|
+
cells: table2.columns.map((col) => ({
|
|
15348
15348
|
location: { line },
|
|
15349
15349
|
value: col
|
|
15350
15350
|
})),
|
|
15351
15351
|
id: ""
|
|
15352
15352
|
});
|
|
15353
|
-
for (let r = 0; r <
|
|
15353
|
+
for (let r = 0; r < table2.rows.length; r++) {
|
|
15354
15354
|
const rowLine = line + 1 + r;
|
|
15355
15355
|
rows.push({
|
|
15356
15356
|
location: { line: rowLine },
|
|
15357
|
-
cells:
|
|
15357
|
+
cells: table2.rows[r].map((cell) => ({
|
|
15358
15358
|
location: { line: rowLine },
|
|
15359
15359
|
value: cell
|
|
15360
15360
|
})),
|
|
@@ -15445,12 +15445,12 @@ function docEntryToPickleDocString(doc) {
|
|
|
15445
15445
|
return void 0;
|
|
15446
15446
|
}
|
|
15447
15447
|
}
|
|
15448
|
-
function buildPickleTable(
|
|
15448
|
+
function buildPickleTable(table2) {
|
|
15449
15449
|
const rows = [];
|
|
15450
15450
|
rows.push({
|
|
15451
|
-
cells:
|
|
15451
|
+
cells: table2.columns.map((col) => ({ value: col }))
|
|
15452
15452
|
});
|
|
15453
|
-
for (const row of
|
|
15453
|
+
for (const row of table2.rows) {
|
|
15454
15454
|
rows.push({
|
|
15455
15455
|
cells: row.map((cell) => ({ value: cell }))
|
|
15456
15456
|
});
|
|
@@ -15813,11 +15813,11 @@ var CucumberHtmlFormatter = class {
|
|
|
15813
15813
|
for (const envelope of envelopes) {
|
|
15814
15814
|
const accepted = htmlStream.write(envelope);
|
|
15815
15815
|
if (!accepted) {
|
|
15816
|
-
await new Promise((
|
|
15816
|
+
await new Promise((resolve8) => htmlStream.once("drain", resolve8));
|
|
15817
15817
|
}
|
|
15818
15818
|
}
|
|
15819
|
-
await new Promise((
|
|
15820
|
-
collector.on("finish",
|
|
15819
|
+
await new Promise((resolve8, reject) => {
|
|
15820
|
+
collector.on("finish", resolve8);
|
|
15821
15821
|
collector.on("error", reject);
|
|
15822
15822
|
htmlStream.end();
|
|
15823
15823
|
});
|
|
@@ -16896,6 +16896,414 @@ ${body}`;
|
|
|
16896
16896
|
}
|
|
16897
16897
|
};
|
|
16898
16898
|
|
|
16899
|
+
// src/formatters/confluence.ts
|
|
16900
|
+
var ConfluenceFormatter = class {
|
|
16901
|
+
options;
|
|
16902
|
+
constructor(options = {}) {
|
|
16903
|
+
this.options = {
|
|
16904
|
+
title: options.title ?? "User Stories",
|
|
16905
|
+
includeStatusIcons: options.includeStatusIcons ?? true,
|
|
16906
|
+
includeMetadata: options.includeMetadata ?? true,
|
|
16907
|
+
includeSummaryTable: options.includeSummaryTable ?? true,
|
|
16908
|
+
includeErrors: options.includeErrors ?? true,
|
|
16909
|
+
scenarioHeadingLevel: options.scenarioHeadingLevel ?? 3,
|
|
16910
|
+
groupBy: options.groupBy ?? "file",
|
|
16911
|
+
sortScenarios: options.sortScenarios ?? "source",
|
|
16912
|
+
pretty: options.pretty ?? true,
|
|
16913
|
+
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
16914
|
+
ticketUrlTemplate: options.ticketUrlTemplate
|
|
16915
|
+
};
|
|
16916
|
+
}
|
|
16917
|
+
/** Build the ADF document tree. Returns the JS object (not stringified). */
|
|
16918
|
+
formatToAdf(run) {
|
|
16919
|
+
const content = [];
|
|
16920
|
+
content.push(heading(1, [text(this.options.title)]));
|
|
16921
|
+
if (this.options.includeMetadata) {
|
|
16922
|
+
const metaTable = this.renderMetadataTable(run);
|
|
16923
|
+
if (metaTable) content.push(metaTable);
|
|
16924
|
+
}
|
|
16925
|
+
if (this.options.includeSummaryTable) {
|
|
16926
|
+
content.push(this.renderSummaryTable(run));
|
|
16927
|
+
}
|
|
16928
|
+
switch (this.options.groupBy) {
|
|
16929
|
+
case "none":
|
|
16930
|
+
this.renderFlat(content, run.testCases);
|
|
16931
|
+
break;
|
|
16932
|
+
case "suite":
|
|
16933
|
+
this.renderBySuite(content, run.testCases);
|
|
16934
|
+
break;
|
|
16935
|
+
case "file":
|
|
16936
|
+
default:
|
|
16937
|
+
this.renderByFile(content, run.testCases);
|
|
16938
|
+
break;
|
|
16939
|
+
}
|
|
16940
|
+
return { version: 1, type: "doc", content };
|
|
16941
|
+
}
|
|
16942
|
+
/** Format a test run as an ADF JSON string. */
|
|
16943
|
+
format(run) {
|
|
16944
|
+
const adf = this.formatToAdf(run);
|
|
16945
|
+
return this.options.pretty ? JSON.stringify(adf, null, 2) : JSON.stringify(adf);
|
|
16946
|
+
}
|
|
16947
|
+
// --------------------------------------------------------------------------
|
|
16948
|
+
// Metadata / summary tables
|
|
16949
|
+
// --------------------------------------------------------------------------
|
|
16950
|
+
renderMetadataTable(run) {
|
|
16951
|
+
const rows = [];
|
|
16952
|
+
rows.push(["Date", new Date(run.startedAtMs).toISOString()]);
|
|
16953
|
+
if (run.packageVersion) rows.push(["Version", run.packageVersion]);
|
|
16954
|
+
if (run.gitSha) {
|
|
16955
|
+
const shortSha = run.gitSha.length > 7 ? run.gitSha.slice(0, 7) : run.gitSha;
|
|
16956
|
+
rows.push(["Git SHA", shortSha]);
|
|
16957
|
+
}
|
|
16958
|
+
if (rows.length === 0) return null;
|
|
16959
|
+
return table([
|
|
16960
|
+
tableRow([tableHeader("Key"), tableHeader("Value")]),
|
|
16961
|
+
...rows.map(([k, v]) => tableRow([tableCell(k), tableCell(v)]))
|
|
16962
|
+
]);
|
|
16963
|
+
}
|
|
16964
|
+
renderSummaryTable(run) {
|
|
16965
|
+
const total = run.testCases.length;
|
|
16966
|
+
const steps = run.testCases.reduce(
|
|
16967
|
+
(acc, tc) => acc + tc.story.steps.length,
|
|
16968
|
+
0
|
|
16969
|
+
);
|
|
16970
|
+
const passed = run.testCases.filter((tc) => tc.status === "passed").length;
|
|
16971
|
+
const failed = run.testCases.filter((tc) => tc.status === "failed").length;
|
|
16972
|
+
const skipped = run.testCases.filter((tc) => tc.status === "skipped").length;
|
|
16973
|
+
const pending = run.testCases.filter((tc) => tc.status === "pending").length;
|
|
16974
|
+
return table([
|
|
16975
|
+
tableRow([
|
|
16976
|
+
tableHeader("Scenarios"),
|
|
16977
|
+
tableHeader("Steps"),
|
|
16978
|
+
tableHeader("Passed"),
|
|
16979
|
+
tableHeader("Failed"),
|
|
16980
|
+
tableHeader("Skipped"),
|
|
16981
|
+
tableHeader("Pending"),
|
|
16982
|
+
tableHeader("Duration")
|
|
16983
|
+
]),
|
|
16984
|
+
tableRow([
|
|
16985
|
+
tableCell(String(total)),
|
|
16986
|
+
tableCell(String(steps)),
|
|
16987
|
+
tableCell(String(passed)),
|
|
16988
|
+
tableCell(String(failed)),
|
|
16989
|
+
tableCell(String(skipped)),
|
|
16990
|
+
tableCell(String(pending)),
|
|
16991
|
+
tableCell(formatDuration2(run.durationMs))
|
|
16992
|
+
])
|
|
16993
|
+
]);
|
|
16994
|
+
}
|
|
16995
|
+
// --------------------------------------------------------------------------
|
|
16996
|
+
// Grouping
|
|
16997
|
+
// --------------------------------------------------------------------------
|
|
16998
|
+
renderByFile(content, testCases) {
|
|
16999
|
+
const byFile = groupBy7(testCases, (tc) => tc.sourceFile);
|
|
17000
|
+
for (const [file, fileCases] of byFile) {
|
|
17001
|
+
content.push(heading(2, [codeInline(file)]));
|
|
17002
|
+
this.renderSuiteGroups(content, fileCases, 3);
|
|
17003
|
+
}
|
|
17004
|
+
}
|
|
17005
|
+
renderBySuite(content, testCases) {
|
|
17006
|
+
this.renderSuiteGroups(content, testCases, 2);
|
|
17007
|
+
}
|
|
17008
|
+
renderFlat(content, testCases) {
|
|
17009
|
+
const sorted = this.sortCases(testCases);
|
|
17010
|
+
for (const tc of sorted) this.renderScenario(content, tc);
|
|
17011
|
+
}
|
|
17012
|
+
renderSuiteGroups(content, testCases, baseLevel) {
|
|
17013
|
+
const bySuite = groupBy7(testCases, (tc) => tc.titlePath.join(" - "));
|
|
17014
|
+
const entries = this.sortSuiteGroups([...bySuite.entries()]);
|
|
17015
|
+
for (const [suitePath, cases] of entries) {
|
|
17016
|
+
if (suitePath) {
|
|
17017
|
+
content.push(
|
|
17018
|
+
heading(clampHeadingLevel(baseLevel), [text(suitePath)])
|
|
17019
|
+
);
|
|
17020
|
+
}
|
|
17021
|
+
for (const tc of this.sortCases(cases)) {
|
|
17022
|
+
this.renderScenario(content, tc);
|
|
17023
|
+
}
|
|
17024
|
+
}
|
|
17025
|
+
}
|
|
17026
|
+
// --------------------------------------------------------------------------
|
|
17027
|
+
// Scenario
|
|
17028
|
+
// --------------------------------------------------------------------------
|
|
17029
|
+
renderScenario(content, tc) {
|
|
17030
|
+
const level = clampHeadingLevel(this.options.scenarioHeadingLevel);
|
|
17031
|
+
const headingNodes = [];
|
|
17032
|
+
if (this.options.includeStatusIcons) {
|
|
17033
|
+
headingNodes.push(text(`${statusIcon(tc.status)} `));
|
|
17034
|
+
}
|
|
17035
|
+
headingNodes.push(text(tc.story.scenario));
|
|
17036
|
+
content.push(heading(level, headingNodes));
|
|
17037
|
+
const metaChildren = [];
|
|
17038
|
+
if (tc.tags.length > 0) {
|
|
17039
|
+
metaChildren.push(text("Tags: ", strong()));
|
|
17040
|
+
tc.tags.forEach((t, i) => {
|
|
17041
|
+
if (i > 0) metaChildren.push(text(", "));
|
|
17042
|
+
metaChildren.push(codeInline(t));
|
|
17043
|
+
});
|
|
17044
|
+
}
|
|
17045
|
+
if (tc.story.tickets && tc.story.tickets.length > 0) {
|
|
17046
|
+
if (metaChildren.length > 0) metaChildren.push(text(" | "));
|
|
17047
|
+
metaChildren.push(text("Tickets: ", strong()));
|
|
17048
|
+
tc.story.tickets.forEach((ticket, i) => {
|
|
17049
|
+
if (i > 0) metaChildren.push(text(", "));
|
|
17050
|
+
const url = ticket.url ?? (this.options.ticketUrlTemplate ? this.options.ticketUrlTemplate.replace("{ticket}", ticket.id) : void 0);
|
|
17051
|
+
metaChildren.push(
|
|
17052
|
+
url ? link(ticket.id, url) : codeInline(ticket.id)
|
|
17053
|
+
);
|
|
17054
|
+
});
|
|
17055
|
+
}
|
|
17056
|
+
if (this.options.permalinkBaseUrl && tc.sourceFile !== "unknown" && tc.sourceFile) {
|
|
17057
|
+
if (metaChildren.length > 0) metaChildren.push(text(" | "));
|
|
17058
|
+
metaChildren.push(text("Source: ", strong()));
|
|
17059
|
+
const base = this.options.permalinkBaseUrl.replace(/\/$/, "");
|
|
17060
|
+
const url = `${base}/${tc.sourceFile}${tc.sourceLine > 0 ? `#L${tc.sourceLine}` : ""}`;
|
|
17061
|
+
metaChildren.push(link(tc.sourceFile, url));
|
|
17062
|
+
}
|
|
17063
|
+
if (metaChildren.length > 0) {
|
|
17064
|
+
content.push(paragraph(metaChildren));
|
|
17065
|
+
}
|
|
17066
|
+
if (tc.story.docs && tc.story.docs.length > 0) {
|
|
17067
|
+
for (const doc of tc.story.docs) this.renderDocEntry(content, doc);
|
|
17068
|
+
}
|
|
17069
|
+
if (tc.story.steps.length > 0) {
|
|
17070
|
+
content.push(this.renderStepsList(tc.story.steps));
|
|
17071
|
+
for (const step of tc.story.steps) {
|
|
17072
|
+
if (step.docs && step.docs.length > 0) {
|
|
17073
|
+
for (const doc of step.docs) this.renderDocEntry(content, doc);
|
|
17074
|
+
}
|
|
17075
|
+
}
|
|
17076
|
+
}
|
|
17077
|
+
if (tc.status === "failed" && tc.errorMessage && this.options.includeErrors) {
|
|
17078
|
+
const errorContent = (tc.errorMessage ?? "") + (tc.errorStack ? `
|
|
17079
|
+
|
|
17080
|
+
${tc.errorStack}` : "");
|
|
17081
|
+
content.push(
|
|
17082
|
+
panel("warning", [paragraph([text("Failure", strong())])])
|
|
17083
|
+
);
|
|
17084
|
+
content.push(codeBlock(errorContent, "text"));
|
|
17085
|
+
}
|
|
17086
|
+
}
|
|
17087
|
+
renderStepsList(steps) {
|
|
17088
|
+
return {
|
|
17089
|
+
type: "bulletList",
|
|
17090
|
+
content: steps.map((step) => {
|
|
17091
|
+
const children = [text(`${step.keyword} `, strong()), text(step.text)];
|
|
17092
|
+
if (step.mode && step.mode !== "normal") {
|
|
17093
|
+
children.push(text(` (${step.mode})`, em()));
|
|
17094
|
+
}
|
|
17095
|
+
return {
|
|
17096
|
+
type: "listItem",
|
|
17097
|
+
content: [paragraph(children)]
|
|
17098
|
+
};
|
|
17099
|
+
})
|
|
17100
|
+
};
|
|
17101
|
+
}
|
|
17102
|
+
// --------------------------------------------------------------------------
|
|
17103
|
+
// Doc entries
|
|
17104
|
+
// --------------------------------------------------------------------------
|
|
17105
|
+
renderDocEntry(content, entry) {
|
|
17106
|
+
switch (entry.kind) {
|
|
17107
|
+
case "note":
|
|
17108
|
+
content.push(panel("info", [paragraph([text(entry.text)])]));
|
|
17109
|
+
break;
|
|
17110
|
+
case "tag": {
|
|
17111
|
+
const kids = [];
|
|
17112
|
+
entry.names.forEach((name, i) => {
|
|
17113
|
+
if (i > 0) kids.push(text(" "));
|
|
17114
|
+
kids.push(codeInline(name));
|
|
17115
|
+
});
|
|
17116
|
+
if (kids.length > 0) content.push(paragraph(kids));
|
|
17117
|
+
break;
|
|
17118
|
+
}
|
|
17119
|
+
case "kv": {
|
|
17120
|
+
const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
|
|
17121
|
+
content.push(
|
|
17122
|
+
paragraph([text(`${entry.label}: `, strong()), codeInline(val)])
|
|
17123
|
+
);
|
|
17124
|
+
break;
|
|
17125
|
+
}
|
|
17126
|
+
case "code":
|
|
17127
|
+
if (entry.label) {
|
|
17128
|
+
content.push(paragraph([text(entry.label, strong())]));
|
|
17129
|
+
}
|
|
17130
|
+
content.push(codeBlock(entry.content ?? "", entry.lang));
|
|
17131
|
+
break;
|
|
17132
|
+
case "table":
|
|
17133
|
+
if (entry.label) {
|
|
17134
|
+
content.push(paragraph([text(entry.label, strong())]));
|
|
17135
|
+
}
|
|
17136
|
+
content.push(
|
|
17137
|
+
table([
|
|
17138
|
+
tableRow(entry.columns.map((c) => tableHeader(c))),
|
|
17139
|
+
...entry.rows.map(
|
|
17140
|
+
(row) => tableRow(row.map((cell) => tableCell(cell)))
|
|
17141
|
+
)
|
|
17142
|
+
])
|
|
17143
|
+
);
|
|
17144
|
+
break;
|
|
17145
|
+
case "link":
|
|
17146
|
+
content.push(paragraph([link(entry.label, entry.url)]));
|
|
17147
|
+
break;
|
|
17148
|
+
case "section":
|
|
17149
|
+
if (entry.title) {
|
|
17150
|
+
content.push(paragraph([text(entry.title, strong())]));
|
|
17151
|
+
}
|
|
17152
|
+
if (entry.markdown) {
|
|
17153
|
+
for (const para of entry.markdown.split(/\n{2,}/)) {
|
|
17154
|
+
const trimmed = para.trim();
|
|
17155
|
+
if (trimmed) content.push(paragraph([text(trimmed)]));
|
|
17156
|
+
}
|
|
17157
|
+
}
|
|
17158
|
+
break;
|
|
17159
|
+
case "mermaid":
|
|
17160
|
+
if (entry.title) {
|
|
17161
|
+
content.push(paragraph([text(entry.title, strong())]));
|
|
17162
|
+
}
|
|
17163
|
+
content.push(codeBlock(entry.code ?? "", "mermaid"));
|
|
17164
|
+
break;
|
|
17165
|
+
case "screenshot":
|
|
17166
|
+
content.push(
|
|
17167
|
+
paragraph([
|
|
17168
|
+
text(entry.alt ?? "Screenshot", strong()),
|
|
17169
|
+
text(": "),
|
|
17170
|
+
link(entry.path, entry.path)
|
|
17171
|
+
])
|
|
17172
|
+
);
|
|
17173
|
+
break;
|
|
17174
|
+
case "custom":
|
|
17175
|
+
content.push(paragraph([text(`[${entry.type}]`, strong())]));
|
|
17176
|
+
content.push(codeBlock(JSON.stringify(entry.data ?? null, null, 2), "json"));
|
|
17177
|
+
break;
|
|
17178
|
+
}
|
|
17179
|
+
if (entry.children && entry.children.length > 0) {
|
|
17180
|
+
for (const child of entry.children) {
|
|
17181
|
+
this.renderDocEntry(content, child);
|
|
17182
|
+
}
|
|
17183
|
+
}
|
|
17184
|
+
}
|
|
17185
|
+
// --------------------------------------------------------------------------
|
|
17186
|
+
// Sorting
|
|
17187
|
+
// --------------------------------------------------------------------------
|
|
17188
|
+
sortCases(cases) {
|
|
17189
|
+
if (this.options.sortScenarios === "alpha") {
|
|
17190
|
+
return [...cases].sort(
|
|
17191
|
+
(a, b) => a.story.scenario.localeCompare(b.story.scenario)
|
|
17192
|
+
);
|
|
17193
|
+
}
|
|
17194
|
+
if (this.options.sortScenarios === "source") {
|
|
17195
|
+
return [...cases].sort(
|
|
17196
|
+
(a, b) => (a.story.sourceOrder ?? 0) - (b.story.sourceOrder ?? 0)
|
|
17197
|
+
);
|
|
17198
|
+
}
|
|
17199
|
+
return cases;
|
|
17200
|
+
}
|
|
17201
|
+
sortSuiteGroups(entries) {
|
|
17202
|
+
if (this.options.sortScenarios === "alpha") {
|
|
17203
|
+
return entries.sort(([a], [b]) => a.localeCompare(b));
|
|
17204
|
+
}
|
|
17205
|
+
if (this.options.sortScenarios === "source") {
|
|
17206
|
+
return entries.sort(([, a], [, b]) => {
|
|
17207
|
+
const minA = Math.min(...a.map((s) => s.story.sourceOrder ?? Infinity));
|
|
17208
|
+
const minB = Math.min(...b.map((s) => s.story.sourceOrder ?? Infinity));
|
|
17209
|
+
return minA - minB;
|
|
17210
|
+
});
|
|
17211
|
+
}
|
|
17212
|
+
return entries;
|
|
17213
|
+
}
|
|
17214
|
+
};
|
|
17215
|
+
function text(value, mark) {
|
|
17216
|
+
const node = { type: "text", text: value };
|
|
17217
|
+
if (mark) {
|
|
17218
|
+
node.marks = Array.isArray(mark) ? mark : [mark];
|
|
17219
|
+
}
|
|
17220
|
+
return node;
|
|
17221
|
+
}
|
|
17222
|
+
function strong() {
|
|
17223
|
+
return { type: "strong" };
|
|
17224
|
+
}
|
|
17225
|
+
function em() {
|
|
17226
|
+
return { type: "em" };
|
|
17227
|
+
}
|
|
17228
|
+
function codeMark() {
|
|
17229
|
+
return { type: "code" };
|
|
17230
|
+
}
|
|
17231
|
+
function codeInline(value) {
|
|
17232
|
+
return text(value, codeMark());
|
|
17233
|
+
}
|
|
17234
|
+
function link(label, href) {
|
|
17235
|
+
return text(label, { type: "link", attrs: { href } });
|
|
17236
|
+
}
|
|
17237
|
+
function paragraph(content) {
|
|
17238
|
+
return { type: "paragraph", content };
|
|
17239
|
+
}
|
|
17240
|
+
function heading(level, content) {
|
|
17241
|
+
return {
|
|
17242
|
+
type: "heading",
|
|
17243
|
+
attrs: { level: clampHeadingLevel(level) },
|
|
17244
|
+
content
|
|
17245
|
+
};
|
|
17246
|
+
}
|
|
17247
|
+
function codeBlock(content, lang) {
|
|
17248
|
+
return {
|
|
17249
|
+
type: "codeBlock",
|
|
17250
|
+
attrs: lang ? { language: lang } : {},
|
|
17251
|
+
content: content ? [{ type: "text", text: content }] : []
|
|
17252
|
+
};
|
|
17253
|
+
}
|
|
17254
|
+
function panel(panelType, content) {
|
|
17255
|
+
return { type: "panel", attrs: { panelType }, content };
|
|
17256
|
+
}
|
|
17257
|
+
function table(rows) {
|
|
17258
|
+
return {
|
|
17259
|
+
type: "table",
|
|
17260
|
+
attrs: { isNumberColumnEnabled: false, layout: "default" },
|
|
17261
|
+
content: rows
|
|
17262
|
+
};
|
|
17263
|
+
}
|
|
17264
|
+
function tableRow(cells) {
|
|
17265
|
+
return { type: "tableRow", content: cells };
|
|
17266
|
+
}
|
|
17267
|
+
function tableHeader(value) {
|
|
17268
|
+
return { type: "tableHeader", content: [paragraph([text(value)])] };
|
|
17269
|
+
}
|
|
17270
|
+
function tableCell(value) {
|
|
17271
|
+
return { type: "tableCell", content: [paragraph([text(value)])] };
|
|
17272
|
+
}
|
|
17273
|
+
function clampHeadingLevel(level) {
|
|
17274
|
+
if (level < 1) return 1;
|
|
17275
|
+
if (level > 6) return 6;
|
|
17276
|
+
return level;
|
|
17277
|
+
}
|
|
17278
|
+
function statusIcon(status) {
|
|
17279
|
+
switch (status) {
|
|
17280
|
+
case "passed":
|
|
17281
|
+
return "\u2705";
|
|
17282
|
+
case "failed":
|
|
17283
|
+
return "\u274C";
|
|
17284
|
+
case "skipped":
|
|
17285
|
+
return "\u23E9";
|
|
17286
|
+
case "pending":
|
|
17287
|
+
return "\u{1F4DD}";
|
|
17288
|
+
default:
|
|
17289
|
+
return "\u26A0\uFE0F";
|
|
17290
|
+
}
|
|
17291
|
+
}
|
|
17292
|
+
function formatDuration2(ms) {
|
|
17293
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
17294
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
17295
|
+
}
|
|
17296
|
+
function groupBy7(items, keyFn) {
|
|
17297
|
+
const map = /* @__PURE__ */ new Map();
|
|
17298
|
+
for (const item of items) {
|
|
17299
|
+
const key = keyFn(item);
|
|
17300
|
+
const existing = map.get(key);
|
|
17301
|
+
if (existing) existing.push(item);
|
|
17302
|
+
else map.set(key, [item]);
|
|
17303
|
+
}
|
|
17304
|
+
return map;
|
|
17305
|
+
}
|
|
17306
|
+
|
|
16899
17307
|
// src/formatters/astro-assets.ts
|
|
16900
17308
|
import * as fs4 from "fs";
|
|
16901
17309
|
import * as path4 from "path";
|
|
@@ -17020,6 +17428,235 @@ function copyMarkdownAssets(options) {
|
|
|
17020
17428
|
};
|
|
17021
17429
|
}
|
|
17022
17430
|
|
|
17431
|
+
// src/publishers/confluence.ts
|
|
17432
|
+
function parseAdf(adf) {
|
|
17433
|
+
let parsed;
|
|
17434
|
+
try {
|
|
17435
|
+
parsed = JSON.parse(adf);
|
|
17436
|
+
} catch (err) {
|
|
17437
|
+
throw new Error(
|
|
17438
|
+
`ADF payload is not valid JSON: ${err.message}`
|
|
17439
|
+
);
|
|
17440
|
+
}
|
|
17441
|
+
if (!parsed || typeof parsed !== "object" || parsed.type !== "doc" || !Array.isArray(parsed.content)) {
|
|
17442
|
+
throw new Error(
|
|
17443
|
+
`ADF payload must be an object with { version, type: "doc", content: [...] }`
|
|
17444
|
+
);
|
|
17445
|
+
}
|
|
17446
|
+
return parsed;
|
|
17447
|
+
}
|
|
17448
|
+
function basicAuthHeader(auth) {
|
|
17449
|
+
const raw = `${auth.email}:${auth.token}`;
|
|
17450
|
+
const encoded = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(raw);
|
|
17451
|
+
return `Basic ${encoded}`;
|
|
17452
|
+
}
|
|
17453
|
+
async function parseErrorBody(response) {
|
|
17454
|
+
try {
|
|
17455
|
+
const body = await response.text();
|
|
17456
|
+
return body ? body.slice(0, 800) : "";
|
|
17457
|
+
} catch {
|
|
17458
|
+
return "";
|
|
17459
|
+
}
|
|
17460
|
+
}
|
|
17461
|
+
async function publishConfluencePage(args, deps) {
|
|
17462
|
+
parseAdf(args.adf);
|
|
17463
|
+
if (!args.pageId && !args.spaceId) {
|
|
17464
|
+
throw new Error(
|
|
17465
|
+
"publishConfluencePage requires either pageId (update) or spaceId (create)"
|
|
17466
|
+
);
|
|
17467
|
+
}
|
|
17468
|
+
if (!args.pageId && !args.title) {
|
|
17469
|
+
throw new Error("Creating a new page requires a title");
|
|
17470
|
+
}
|
|
17471
|
+
const base = args.baseUrl.replace(/\/$/, "");
|
|
17472
|
+
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
17473
|
+
if (!fetchFn) {
|
|
17474
|
+
throw new Error("No fetch implementation available (Node >= 22 expected)");
|
|
17475
|
+
}
|
|
17476
|
+
const headers = {
|
|
17477
|
+
Authorization: basicAuthHeader(deps.auth),
|
|
17478
|
+
Accept: "application/json",
|
|
17479
|
+
"Content-Type": "application/json"
|
|
17480
|
+
};
|
|
17481
|
+
if (args.pageId) {
|
|
17482
|
+
return updatePage(args, base, headers, fetchFn);
|
|
17483
|
+
}
|
|
17484
|
+
return createPage(args, base, headers, fetchFn);
|
|
17485
|
+
}
|
|
17486
|
+
async function updatePage(args, base, headers, fetchFn) {
|
|
17487
|
+
const getUrl = `${base}/api/v2/pages/${encodeURIComponent(args.pageId)}`;
|
|
17488
|
+
const getResp = await fetchFn(getUrl, { method: "GET", headers });
|
|
17489
|
+
if (!getResp.ok) {
|
|
17490
|
+
const body = await parseErrorBody(getResp);
|
|
17491
|
+
throw new Error(
|
|
17492
|
+
`GET ${getUrl} failed with ${getResp.status} ${getResp.statusText}${body ? `: ${body}` : ""}`
|
|
17493
|
+
);
|
|
17494
|
+
}
|
|
17495
|
+
const current = await getResp.json();
|
|
17496
|
+
const nextVersion = current.version.number + 1;
|
|
17497
|
+
const title = args.title ?? current.title;
|
|
17498
|
+
const putUrl = `${base}/api/v2/pages/${encodeURIComponent(args.pageId)}`;
|
|
17499
|
+
const putResp = await fetchFn(putUrl, {
|
|
17500
|
+
method: "PUT",
|
|
17501
|
+
headers,
|
|
17502
|
+
body: JSON.stringify({
|
|
17503
|
+
id: args.pageId,
|
|
17504
|
+
status: "current",
|
|
17505
|
+
title,
|
|
17506
|
+
body: {
|
|
17507
|
+
representation: "atlas_doc_format",
|
|
17508
|
+
value: args.adf
|
|
17509
|
+
},
|
|
17510
|
+
version: { number: nextVersion }
|
|
17511
|
+
})
|
|
17512
|
+
});
|
|
17513
|
+
if (!putResp.ok) {
|
|
17514
|
+
const body = await parseErrorBody(putResp);
|
|
17515
|
+
throw new Error(
|
|
17516
|
+
`PUT ${putUrl} failed with ${putResp.status} ${putResp.statusText}${body ? `: ${body}` : ""}`
|
|
17517
|
+
);
|
|
17518
|
+
}
|
|
17519
|
+
const updated = await putResp.json();
|
|
17520
|
+
return {
|
|
17521
|
+
id: updated.id,
|
|
17522
|
+
title: updated.title,
|
|
17523
|
+
version: updated.version.number,
|
|
17524
|
+
url: buildPageUrl(base, updated._links?.webui, updated.id),
|
|
17525
|
+
action: "updated"
|
|
17526
|
+
};
|
|
17527
|
+
}
|
|
17528
|
+
async function createPage(args, base, headers, fetchFn) {
|
|
17529
|
+
const body = {
|
|
17530
|
+
spaceId: args.spaceId,
|
|
17531
|
+
status: "current",
|
|
17532
|
+
title: args.title,
|
|
17533
|
+
body: {
|
|
17534
|
+
representation: "atlas_doc_format",
|
|
17535
|
+
value: args.adf
|
|
17536
|
+
}
|
|
17537
|
+
};
|
|
17538
|
+
if (args.parentId) body.parentId = args.parentId;
|
|
17539
|
+
const postUrl = `${base}/api/v2/pages`;
|
|
17540
|
+
const resp = await fetchFn(postUrl, {
|
|
17541
|
+
method: "POST",
|
|
17542
|
+
headers,
|
|
17543
|
+
body: JSON.stringify(body)
|
|
17544
|
+
});
|
|
17545
|
+
if (!resp.ok) {
|
|
17546
|
+
const errBody = await parseErrorBody(resp);
|
|
17547
|
+
throw new Error(
|
|
17548
|
+
`POST ${postUrl} failed with ${resp.status} ${resp.statusText}${errBody ? `: ${errBody}` : ""}`
|
|
17549
|
+
);
|
|
17550
|
+
}
|
|
17551
|
+
const created = await resp.json();
|
|
17552
|
+
return {
|
|
17553
|
+
id: created.id,
|
|
17554
|
+
title: created.title,
|
|
17555
|
+
version: created.version.number,
|
|
17556
|
+
url: buildPageUrl(base, created._links?.webui, created.id),
|
|
17557
|
+
action: "created"
|
|
17558
|
+
};
|
|
17559
|
+
}
|
|
17560
|
+
function buildPageUrl(base, webui, id) {
|
|
17561
|
+
if (webui) {
|
|
17562
|
+
return webui.startsWith("http") ? webui : `${base}${webui}`;
|
|
17563
|
+
}
|
|
17564
|
+
return `${base}/pages/${id}`;
|
|
17565
|
+
}
|
|
17566
|
+
|
|
17567
|
+
// src/publishers/jira.ts
|
|
17568
|
+
function parseAdf2(adf) {
|
|
17569
|
+
let parsed;
|
|
17570
|
+
try {
|
|
17571
|
+
parsed = JSON.parse(adf);
|
|
17572
|
+
} catch (err) {
|
|
17573
|
+
throw new Error(
|
|
17574
|
+
`ADF payload is not valid JSON: ${err.message}`
|
|
17575
|
+
);
|
|
17576
|
+
}
|
|
17577
|
+
if (!parsed || typeof parsed !== "object" || parsed.type !== "doc" || !Array.isArray(parsed.content)) {
|
|
17578
|
+
throw new Error(
|
|
17579
|
+
`ADF payload must be an object with { version, type: "doc", content: [...] }`
|
|
17580
|
+
);
|
|
17581
|
+
}
|
|
17582
|
+
return parsed;
|
|
17583
|
+
}
|
|
17584
|
+
function basicAuthHeader2(auth) {
|
|
17585
|
+
const raw = `${auth.email}:${auth.token}`;
|
|
17586
|
+
const encoded = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(raw);
|
|
17587
|
+
return `Basic ${encoded}`;
|
|
17588
|
+
}
|
|
17589
|
+
async function parseErrorBody2(resp) {
|
|
17590
|
+
try {
|
|
17591
|
+
const body = await resp.text();
|
|
17592
|
+
return body ? body.slice(0, 800) : "";
|
|
17593
|
+
} catch {
|
|
17594
|
+
return "";
|
|
17595
|
+
}
|
|
17596
|
+
}
|
|
17597
|
+
async function publishJiraIssue(args, deps) {
|
|
17598
|
+
const adfObject = parseAdf2(args.adf);
|
|
17599
|
+
if (!args.issueKey) {
|
|
17600
|
+
throw new Error("publishJiraIssue requires an issueKey, e.g. PROJ-123");
|
|
17601
|
+
}
|
|
17602
|
+
const base = args.baseUrl.replace(/\/$/, "");
|
|
17603
|
+
const fetchFn = deps.fetch ?? globalThis.fetch;
|
|
17604
|
+
if (!fetchFn) {
|
|
17605
|
+
throw new Error("No fetch implementation available (Node >= 22 expected)");
|
|
17606
|
+
}
|
|
17607
|
+
const headers = {
|
|
17608
|
+
Authorization: basicAuthHeader2(deps.auth),
|
|
17609
|
+
Accept: "application/json",
|
|
17610
|
+
"Content-Type": "application/json"
|
|
17611
|
+
};
|
|
17612
|
+
const mode = args.mode ?? "comment";
|
|
17613
|
+
if (mode === "description") {
|
|
17614
|
+
return updateDescription(args.issueKey, base, adfObject, headers, fetchFn);
|
|
17615
|
+
}
|
|
17616
|
+
return addComment(args.issueKey, base, adfObject, headers, fetchFn);
|
|
17617
|
+
}
|
|
17618
|
+
async function addComment(issueKey, base, adf, headers, fetchFn) {
|
|
17619
|
+
const url = `${base}/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment`;
|
|
17620
|
+
const resp = await fetchFn(url, {
|
|
17621
|
+
method: "POST",
|
|
17622
|
+
headers,
|
|
17623
|
+
body: JSON.stringify({ body: adf })
|
|
17624
|
+
});
|
|
17625
|
+
if (!resp.ok) {
|
|
17626
|
+
const body = await parseErrorBody2(resp);
|
|
17627
|
+
throw new Error(
|
|
17628
|
+
`POST ${url} failed with ${resp.status} ${resp.statusText}${body ? `: ${body}` : ""}`
|
|
17629
|
+
);
|
|
17630
|
+
}
|
|
17631
|
+
const comment = await resp.json();
|
|
17632
|
+
const issueUrl = `${base}/browse/${encodeURIComponent(issueKey)}`;
|
|
17633
|
+
return {
|
|
17634
|
+
issueKey,
|
|
17635
|
+
action: "comment-added",
|
|
17636
|
+
url: `${issueUrl}?focusedCommentId=${encodeURIComponent(comment.id)}`,
|
|
17637
|
+
commentId: comment.id
|
|
17638
|
+
};
|
|
17639
|
+
}
|
|
17640
|
+
async function updateDescription(issueKey, base, adf, headers, fetchFn) {
|
|
17641
|
+
const url = `${base}/rest/api/3/issue/${encodeURIComponent(issueKey)}`;
|
|
17642
|
+
const resp = await fetchFn(url, {
|
|
17643
|
+
method: "PUT",
|
|
17644
|
+
headers,
|
|
17645
|
+
body: JSON.stringify({ fields: { description: adf } })
|
|
17646
|
+
});
|
|
17647
|
+
if (!resp.ok) {
|
|
17648
|
+
const body = await parseErrorBody2(resp);
|
|
17649
|
+
throw new Error(
|
|
17650
|
+
`PUT ${url} failed with ${resp.status} ${resp.statusText}${body ? `: ${body}` : ""}`
|
|
17651
|
+
);
|
|
17652
|
+
}
|
|
17653
|
+
return {
|
|
17654
|
+
issueKey,
|
|
17655
|
+
action: "description-updated",
|
|
17656
|
+
url: `${base}/browse/${encodeURIComponent(issueKey)}`
|
|
17657
|
+
};
|
|
17658
|
+
}
|
|
17659
|
+
|
|
17023
17660
|
// src/converters/ndjson-parser.ts
|
|
17024
17661
|
function parseNdjson(ndjson) {
|
|
17025
17662
|
const lines = ndjson.trim().split("\n").filter(Boolean);
|
|
@@ -17320,10 +17957,10 @@ function pickleStepArgumentToDocs(ps) {
|
|
|
17320
17957
|
const docs = [];
|
|
17321
17958
|
const phase = "static";
|
|
17322
17959
|
if (ps.argument.dataTable) {
|
|
17323
|
-
const
|
|
17324
|
-
if (
|
|
17325
|
-
const columns =
|
|
17326
|
-
const rows =
|
|
17960
|
+
const table2 = ps.argument.dataTable;
|
|
17961
|
+
if (table2.rows.length > 0) {
|
|
17962
|
+
const columns = table2.rows[0].cells.map((c) => c.value);
|
|
17963
|
+
const rows = table2.rows.slice(1).map((r) => r.cells.map((c) => c.value));
|
|
17327
17964
|
docs.push({
|
|
17328
17965
|
kind: "table",
|
|
17329
17966
|
label: "",
|
|
@@ -17386,16 +18023,16 @@ function pickleStepArgumentToDocs(ps) {
|
|
|
17386
18023
|
}
|
|
17387
18024
|
|
|
17388
18025
|
// src/notifiers/ansi-strip.ts
|
|
17389
|
-
function stripAnsi(
|
|
17390
|
-
return
|
|
18026
|
+
function stripAnsi(text2) {
|
|
18027
|
+
return text2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
17391
18028
|
}
|
|
17392
18029
|
|
|
17393
18030
|
// src/notifiers/slack.ts
|
|
17394
|
-
function truncate(
|
|
17395
|
-
if (
|
|
17396
|
-
return
|
|
18031
|
+
function truncate(text2, maxLen) {
|
|
18032
|
+
if (text2.length <= maxLen) return text2;
|
|
18033
|
+
return text2.slice(0, maxLen - 3) + "...";
|
|
17397
18034
|
}
|
|
17398
|
-
function
|
|
18035
|
+
function formatDuration3(ms) {
|
|
17399
18036
|
const seconds = ms / 1e3;
|
|
17400
18037
|
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
17401
18038
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -17422,7 +18059,7 @@ function buildSlackPayload(summary, maxFailedTests) {
|
|
|
17422
18059
|
{ type: "mrkdwn", text: `*Passed:* ${summary.passed}` },
|
|
17423
18060
|
{ type: "mrkdwn", text: `*Failed:* ${summary.failed}` },
|
|
17424
18061
|
{ type: "mrkdwn", text: `*Skipped:* ${summary.skipped}` },
|
|
17425
|
-
{ type: "mrkdwn", text: `*Duration:* ${
|
|
18062
|
+
{ type: "mrkdwn", text: `*Duration:* ${formatDuration3(summary.durationMs)}` },
|
|
17426
18063
|
{ type: "mrkdwn", text: `*Status:* ${statusText}` }
|
|
17427
18064
|
]
|
|
17428
18065
|
});
|
|
@@ -17437,9 +18074,9 @@ function buildSlackPayload(summary, maxFailedTests) {
|
|
|
17437
18074
|
}
|
|
17438
18075
|
return `*${name}*`;
|
|
17439
18076
|
});
|
|
17440
|
-
let
|
|
18077
|
+
let text2 = lines.join("\n\n");
|
|
17441
18078
|
if (summary.failedTests.length > maxFailedTests) {
|
|
17442
|
-
|
|
18079
|
+
text2 += `
|
|
17443
18080
|
|
|
17444
18081
|
_...and ${summary.failedTests.length - maxFailedTests} more_`;
|
|
17445
18082
|
}
|
|
@@ -17447,7 +18084,7 @@ _...and ${summary.failedTests.length - maxFailedTests} more_`;
|
|
|
17447
18084
|
type: "section",
|
|
17448
18085
|
text: {
|
|
17449
18086
|
type: "mrkdwn",
|
|
17450
|
-
text
|
|
18087
|
+
text: text2
|
|
17451
18088
|
}
|
|
17452
18089
|
});
|
|
17453
18090
|
}
|
|
@@ -17524,11 +18161,11 @@ async function sendSlackNotification(args, deps) {
|
|
|
17524
18161
|
}
|
|
17525
18162
|
|
|
17526
18163
|
// src/notifiers/teams.ts
|
|
17527
|
-
function truncate2(
|
|
17528
|
-
if (
|
|
17529
|
-
return
|
|
18164
|
+
function truncate2(text2, maxLen) {
|
|
18165
|
+
if (text2.length <= maxLen) return text2;
|
|
18166
|
+
return text2.slice(0, maxLen - 3) + "...";
|
|
17530
18167
|
}
|
|
17531
|
-
function
|
|
18168
|
+
function formatDuration4(ms) {
|
|
17532
18169
|
const seconds = ms / 1e3;
|
|
17533
18170
|
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
17534
18171
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -17554,7 +18191,7 @@ function buildTeamsPayload(summary, maxFailedTests) {
|
|
|
17554
18191
|
{ title: "Passed", value: String(summary.passed) },
|
|
17555
18192
|
{ title: "Failed", value: String(summary.failed) },
|
|
17556
18193
|
{ title: "Skipped", value: String(summary.skipped) },
|
|
17557
|
-
{ title: "Duration", value:
|
|
18194
|
+
{ title: "Duration", value: formatDuration4(summary.durationMs) }
|
|
17558
18195
|
]
|
|
17559
18196
|
});
|
|
17560
18197
|
if (summary.failedTests.length > 0) {
|
|
@@ -17973,6 +18610,37 @@ function listScenarios(args, _deps) {
|
|
|
17973
18610
|
}));
|
|
17974
18611
|
return JSON.stringify(items, null, 2);
|
|
17975
18612
|
}
|
|
18613
|
+
if (format === "csv") {
|
|
18614
|
+
const header = "id,scenario,status,sourceFile,sourceLine,tags";
|
|
18615
|
+
const rows = testCases.map((tc) => {
|
|
18616
|
+
const fields = [
|
|
18617
|
+
tc.id,
|
|
18618
|
+
tc.story.scenario,
|
|
18619
|
+
tc.status,
|
|
18620
|
+
tc.sourceFile,
|
|
18621
|
+
String(tc.sourceLine),
|
|
18622
|
+
tc.tags.join(" ")
|
|
18623
|
+
];
|
|
18624
|
+
return fields.map((f) => {
|
|
18625
|
+
if (f.includes(",") || f.includes('"') || f.includes("\n")) {
|
|
18626
|
+
return `"${f.replace(/"/g, '""')}"`;
|
|
18627
|
+
}
|
|
18628
|
+
return f;
|
|
18629
|
+
}).join(",");
|
|
18630
|
+
});
|
|
18631
|
+
return [header, ...rows].join("\n");
|
|
18632
|
+
}
|
|
18633
|
+
if (format === "markdown-table") {
|
|
18634
|
+
const header = "| Status | Scenario | Location | Tags |";
|
|
18635
|
+
const divider = "|--------|----------|----------|------|";
|
|
18636
|
+
const rows = testCases.map((tc) => {
|
|
18637
|
+
const icon = STATUS_ICONS[tc.status] ?? "?";
|
|
18638
|
+
const location = `${tc.sourceFile}:${tc.sourceLine}`;
|
|
18639
|
+
const tags = tc.tags.map((t) => `@${t}`).join(" ");
|
|
18640
|
+
return `| ${icon} | ${tc.story.scenario} | ${location} | ${tags} |`;
|
|
18641
|
+
});
|
|
18642
|
+
return [header, divider, ...rows].join("\n");
|
|
18643
|
+
}
|
|
17976
18644
|
if (testCases.length === 0) {
|
|
17977
18645
|
return "No scenarios found.";
|
|
17978
18646
|
}
|
|
@@ -18020,7 +18688,8 @@ var FORMAT_EXTENSIONS = {
|
|
|
18020
18688
|
"cucumber-html": ".cucumber.html",
|
|
18021
18689
|
junit: ".junit.xml",
|
|
18022
18690
|
"cucumber-json": ".cucumber.json",
|
|
18023
|
-
"cucumber-messages": ".ndjson"
|
|
18691
|
+
"cucumber-messages": ".ndjson",
|
|
18692
|
+
confluence: ".adf.json"
|
|
18024
18693
|
};
|
|
18025
18694
|
var TEST_EXTENSIONS = [
|
|
18026
18695
|
".test.ts",
|
|
@@ -18188,6 +18857,19 @@ var ReportGenerator = class {
|
|
|
18188
18857
|
includeSourceLinks: options.markdown?.includeSourceLinks ?? true,
|
|
18189
18858
|
customRenderers: options.markdown?.customRenderers
|
|
18190
18859
|
},
|
|
18860
|
+
confluence: {
|
|
18861
|
+
title: options.confluence?.title ?? "User Stories",
|
|
18862
|
+
includeStatusIcons: options.confluence?.includeStatusIcons ?? true,
|
|
18863
|
+
includeMetadata: options.confluence?.includeMetadata ?? true,
|
|
18864
|
+
includeSummaryTable: options.confluence?.includeSummaryTable ?? true,
|
|
18865
|
+
includeErrors: options.confluence?.includeErrors ?? true,
|
|
18866
|
+
scenarioHeadingLevel: options.confluence?.scenarioHeadingLevel ?? 3,
|
|
18867
|
+
groupBy: options.confluence?.groupBy ?? "file",
|
|
18868
|
+
sortScenarios: options.confluence?.sortScenarios ?? "source",
|
|
18869
|
+
pretty: options.confluence?.pretty ?? true,
|
|
18870
|
+
permalinkBaseUrl: options.confluence?.permalinkBaseUrl,
|
|
18871
|
+
ticketUrlTemplate: options.confluence?.ticketUrlTemplate
|
|
18872
|
+
},
|
|
18191
18873
|
astro: {
|
|
18192
18874
|
assetsDir: options.astro?.assetsDir ?? "public/stories/assets",
|
|
18193
18875
|
assetsBaseUrl: options.astro?.assetsBaseUrl ?? "/stories/assets",
|
|
@@ -18363,6 +19045,22 @@ var ReportGenerator = class {
|
|
|
18363
19045
|
});
|
|
18364
19046
|
return formatter.format(run);
|
|
18365
19047
|
}
|
|
19048
|
+
case "confluence": {
|
|
19049
|
+
const formatter = new ConfluenceFormatter({
|
|
19050
|
+
title: this.options.confluence.title,
|
|
19051
|
+
includeStatusIcons: this.options.confluence.includeStatusIcons,
|
|
19052
|
+
includeMetadata: this.options.confluence.includeMetadata,
|
|
19053
|
+
includeSummaryTable: this.options.confluence.includeSummaryTable,
|
|
19054
|
+
includeErrors: this.options.confluence.includeErrors,
|
|
19055
|
+
scenarioHeadingLevel: this.options.confluence.scenarioHeadingLevel,
|
|
19056
|
+
groupBy: this.options.confluence.groupBy,
|
|
19057
|
+
sortScenarios: this.options.confluence.sortScenarios,
|
|
19058
|
+
pretty: this.options.confluence.pretty,
|
|
19059
|
+
permalinkBaseUrl: this.options.confluence.permalinkBaseUrl,
|
|
19060
|
+
ticketUrlTemplate: this.options.confluence.ticketUrlTemplate
|
|
19061
|
+
});
|
|
19062
|
+
return formatter.format(run);
|
|
19063
|
+
}
|
|
18366
19064
|
case "markdown": {
|
|
18367
19065
|
const formatter = new MarkdownFormatter({
|
|
18368
19066
|
title: this.options.markdown.title,
|
|
@@ -18444,6 +19142,22 @@ function copyDirRecursive(src, dest) {
|
|
|
18444
19142
|
}
|
|
18445
19143
|
}
|
|
18446
19144
|
|
|
19145
|
+
// src/config.ts
|
|
19146
|
+
import { existsSync as existsSync6 } from "fs";
|
|
19147
|
+
import { resolve as resolve6 } from "path";
|
|
19148
|
+
async function loadConfig(configPath) {
|
|
19149
|
+
const resolved = configPath ? resolve6(configPath) : resolve6(process.cwd(), "executable-stories.config.js");
|
|
19150
|
+
if (!existsSync6(resolved)) return {};
|
|
19151
|
+
const mod = await import(resolved);
|
|
19152
|
+
const config = mod.default;
|
|
19153
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
19154
|
+
throw new Error(
|
|
19155
|
+
`Config file at ${resolved} must export a default object. Got: ${typeof config}`
|
|
19156
|
+
);
|
|
19157
|
+
}
|
|
19158
|
+
return config;
|
|
19159
|
+
}
|
|
19160
|
+
|
|
18447
19161
|
// src/cli.ts
|
|
18448
19162
|
var EXIT_SUCCESS = 0;
|
|
18449
19163
|
var EXIT_SCHEMA_VALIDATION = 1;
|
|
@@ -18461,23 +19175,29 @@ USAGE
|
|
|
18461
19175
|
executable-stories validate <file>
|
|
18462
19176
|
executable-stories validate --stdin
|
|
18463
19177
|
executable-stories init-astro [directory]
|
|
19178
|
+
executable-stories publish-confluence <file.adf.json> [options]
|
|
19179
|
+
executable-stories publish-jira <file.adf.json> [options]
|
|
18464
19180
|
|
|
18465
19181
|
SUBCOMMANDS
|
|
18466
|
-
format
|
|
18467
|
-
compare
|
|
18468
|
-
list
|
|
18469
|
-
validate
|
|
18470
|
-
init-astro
|
|
19182
|
+
format Read raw test results and generate reports
|
|
19183
|
+
compare Compare two runs and generate a diff report
|
|
19184
|
+
list List scenarios from a test run (text table or JSON)
|
|
19185
|
+
validate Validate a JSON file against the schema (no output generated)
|
|
19186
|
+
init-astro Scaffold an Astro docs site for story output (Starlight with themed CSS)
|
|
19187
|
+
publish-confluence Publish an ADF JSON file to a Confluence page via REST API
|
|
19188
|
+
publish-jira Publish an ADF JSON file to a Jira issue (as comment or description)
|
|
18471
19189
|
|
|
18472
19190
|
OPTIONS
|
|
18473
|
-
--format <formats> Comma-separated formats (default: html)
|
|
18474
|
-
astro
|
|
19191
|
+
--format <formats> Comma-separated formats: html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, or custom names from config (default: html)
|
|
19192
|
+
astro Themed Markdown (for Astro docs sites with matching CSS)
|
|
19193
|
+
confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
|
|
18475
19194
|
html Custom HTML report (accessible, dark mode, mermaid)
|
|
18476
19195
|
cucumber-html Official Cucumber HTML report
|
|
18477
19196
|
markdown Markdown documentation
|
|
18478
19197
|
junit JUnit XML
|
|
18479
19198
|
cucumber-json Cucumber JSON
|
|
18480
19199
|
cucumber-messages Raw NDJSON (Cucumber Messages)
|
|
19200
|
+
--config <path> Path to executable-stories.config.js (default: ./executable-stories.config.js)
|
|
18481
19201
|
--input-type <type> Input type: raw, canonical, or ndjson (default: raw)
|
|
18482
19202
|
--output-dir <dir> Output directory (default: reports)
|
|
18483
19203
|
--output-name <name> Base filename (default: index)
|
|
@@ -18501,7 +19221,8 @@ OPTIONS
|
|
|
18501
19221
|
--asset-mode <mode> Asset bundling: "none" (default) or "copy"
|
|
18502
19222
|
--allow-missing-assets Warn on missing assets instead of failing
|
|
18503
19223
|
--stdin Read JSON from stdin instead of file
|
|
18504
|
-
--
|
|
19224
|
+
--list-format <format> list output format: text (default), json, csv, markdown-table
|
|
19225
|
+
--json-summary Deprecated alias for --list-format json
|
|
18505
19226
|
--baseline <path|auto> Compare baseline file, or auto-pick a prior run for compare
|
|
18506
19227
|
--baseline-dir <dir> Directory to scan when --baseline auto is used
|
|
18507
19228
|
--pr-summary Print a PR-friendly markdown summary after compare
|
|
@@ -18510,7 +19231,8 @@ OPTIONS
|
|
|
18510
19231
|
--help Show this help message
|
|
18511
19232
|
|
|
18512
19233
|
LIST
|
|
18513
|
-
list prints one scenario per line (text by default
|
|
19234
|
+
list prints one scenario per line (--list-format text by default)
|
|
19235
|
+
list --list-format json outputs machine-parsable JSON (--json-summary is a deprecated alias)
|
|
18514
19236
|
list supports --include-tags, --exclude-tags for filtering
|
|
18515
19237
|
list supports --input-type and --stdin
|
|
18516
19238
|
|
|
@@ -18522,6 +19244,26 @@ INIT-ASTRO
|
|
|
18522
19244
|
executable-stories init-astro [directory] Scaffold into directory (default: ./story-docs)
|
|
18523
19245
|
--force Overwrite existing directory
|
|
18524
19246
|
|
|
19247
|
+
PUBLISH-CONFLUENCE
|
|
19248
|
+
executable-stories publish-confluence <file.adf.json> [options]
|
|
19249
|
+
--page-id <id> Update an existing page (alternative to --space-id)
|
|
19250
|
+
--space-id <id> Create a new page (requires --title)
|
|
19251
|
+
--parent-id <id> Parent page ID (for new pages)
|
|
19252
|
+
--title <title> Page title
|
|
19253
|
+
--base-url <url> Confluence base URL (env: CONFLUENCE_BASE_URL)
|
|
19254
|
+
--email <email> Atlassian email (env: CONFLUENCE_EMAIL)
|
|
19255
|
+
--token <token> API token (env: CONFLUENCE_TOKEN)
|
|
19256
|
+
--dry-run Validate and print request plan, don't POST
|
|
19257
|
+
|
|
19258
|
+
PUBLISH-JIRA
|
|
19259
|
+
executable-stories publish-jira <file.adf.json> [options]
|
|
19260
|
+
--issue <KEY> Issue key, e.g. PROJ-123 (required)
|
|
19261
|
+
--mode <mode> "comment" (default) or "description"
|
|
19262
|
+
--base-url <url> Jira base URL (env: JIRA_BASE_URL)
|
|
19263
|
+
--email <email> Atlassian email (env: JIRA_EMAIL)
|
|
19264
|
+
--token <token> API token (env: JIRA_TOKEN)
|
|
19265
|
+
--dry-run Validate and print request plan, don't POST
|
|
19266
|
+
|
|
18525
19267
|
NOTIFICATIONS
|
|
18526
19268
|
--slack-webhook <url> Slack incoming webhook URL (fallback: SLACK_WEBHOOK_URL env var)
|
|
18527
19269
|
--teams-webhook <url> Teams incoming webhook URL (fallback: TEAMS_WEBHOOK_URL env var)
|
|
@@ -18549,24 +19291,45 @@ EXIT CODES
|
|
|
18549
19291
|
3 Formatter/generation failure
|
|
18550
19292
|
4 Bad arguments / usage error
|
|
18551
19293
|
`.trim();
|
|
18552
|
-
function parseCliArgs(argv) {
|
|
19294
|
+
async function parseCliArgs(argv) {
|
|
18553
19295
|
const args = argv.slice(2);
|
|
18554
19296
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
18555
19297
|
console.log(HELP_TEXT);
|
|
18556
19298
|
process.exit(EXIT_SUCCESS);
|
|
18557
19299
|
}
|
|
18558
19300
|
const subcommand = args[0];
|
|
18559
|
-
if (subcommand !== "format" && subcommand !== "compare" && subcommand !== "list" && subcommand !== "validate" && subcommand !== "init-astro") {
|
|
18560
|
-
console.error(
|
|
19301
|
+
if (subcommand !== "format" && subcommand !== "compare" && subcommand !== "list" && subcommand !== "validate" && subcommand !== "init-astro" && subcommand !== "publish-confluence" && subcommand !== "publish-jira") {
|
|
19302
|
+
console.error(
|
|
19303
|
+
`Unknown subcommand: "${subcommand}". Use "format", "compare", "list", "validate", "init-astro", "publish-confluence", or "publish-jira".`
|
|
19304
|
+
);
|
|
18561
19305
|
process.exit(EXIT_USAGE);
|
|
18562
19306
|
}
|
|
19307
|
+
if (subcommand === "publish-confluence") {
|
|
19308
|
+
await runPublishConfluence(args.slice(1));
|
|
19309
|
+
process.exit(EXIT_SUCCESS);
|
|
19310
|
+
}
|
|
19311
|
+
if (subcommand === "publish-jira") {
|
|
19312
|
+
await runPublishJira(args.slice(1));
|
|
19313
|
+
process.exit(EXIT_SUCCESS);
|
|
19314
|
+
}
|
|
18563
19315
|
if (subcommand === "init-astro") {
|
|
18564
19316
|
const initArgs = args.slice(1);
|
|
18565
19317
|
const targetDir = initArgs.find((a) => !a.startsWith("--")) ?? "./story-docs";
|
|
18566
19318
|
const force = initArgs.includes("--force");
|
|
18567
19319
|
try {
|
|
18568
19320
|
const result = initAstro({ targetDir, force });
|
|
18569
|
-
console.log(`Scaffolded Astro
|
|
19321
|
+
console.log(`Scaffolded Astro docs site at ${result.targetDir}`);
|
|
19322
|
+
console.log("");
|
|
19323
|
+
console.log("Themes available in src/styles/themes/:");
|
|
19324
|
+
console.log(" default.css IBM Plex Sans, cucumber green (default)");
|
|
19325
|
+
console.log(" corporate.css DM Sans, navy accent");
|
|
19326
|
+
console.log(" terminal.css JetBrains Mono, green-on-dark");
|
|
19327
|
+
console.log(" minimal.css DM Sans, warm teal");
|
|
19328
|
+
console.log(" dashboard.css DM Sans, blue accent");
|
|
19329
|
+
console.log(" playful.css Source Sans, coral pastels");
|
|
19330
|
+
console.log("");
|
|
19331
|
+
console.log("To change theme, edit astro.config.mjs customCss array.");
|
|
19332
|
+
console.log("");
|
|
18570
19333
|
console.log("");
|
|
18571
19334
|
console.log("Next steps:");
|
|
18572
19335
|
console.log(` cd ${result.targetDir}`);
|
|
@@ -18609,6 +19372,7 @@ function parseCliArgs(argv) {
|
|
|
18609
19372
|
"html-theme-picker": { type: "boolean", default: false },
|
|
18610
19373
|
stdin: { type: "boolean", default: false },
|
|
18611
19374
|
"json-summary": { type: "boolean", default: false },
|
|
19375
|
+
"list-format": { type: "string", default: "text" },
|
|
18612
19376
|
"emit-canonical": { type: "string" },
|
|
18613
19377
|
"slack-webhook": { type: "string" },
|
|
18614
19378
|
"teams-webhook": { type: "string" },
|
|
@@ -18627,6 +19391,7 @@ function parseCliArgs(argv) {
|
|
|
18627
19391
|
"allow-missing-assets": { type: "boolean", default: false },
|
|
18628
19392
|
"pr-summary": { type: "boolean", default: false },
|
|
18629
19393
|
"pr-summary-file": { type: "string" },
|
|
19394
|
+
"config": { type: "string" },
|
|
18630
19395
|
help: { type: "boolean", default: false }
|
|
18631
19396
|
},
|
|
18632
19397
|
allowPositionals: true,
|
|
@@ -18664,15 +19429,20 @@ function parseCliArgs(argv) {
|
|
|
18664
19429
|
console.error(`Error: --input-type must be "raw", "canonical", or "ndjson", got "${inputType}".`);
|
|
18665
19430
|
process.exit(EXIT_USAGE);
|
|
18666
19431
|
}
|
|
18667
|
-
const
|
|
19432
|
+
const pluginConfig = await loadConfig(values["config"]);
|
|
19433
|
+
const customFormatterNames = new Set(Object.keys(pluginConfig.formatters ?? {}));
|
|
19434
|
+
const builtInFormats = /* @__PURE__ */ new Set(["astro", "confluence", "html", "markdown", "junit", "cucumber-json", "cucumber-messages", "cucumber-html"]);
|
|
18668
19435
|
const formatStr = values.format;
|
|
18669
|
-
const
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
}
|
|
19436
|
+
const allRequestedFormats = formatStr.split(",").map((f) => f.trim());
|
|
19437
|
+
const builtInRequested = allRequestedFormats.filter((f) => builtInFormats.has(f));
|
|
19438
|
+
const customRequested = allRequestedFormats.filter((f) => customFormatterNames.has(f));
|
|
19439
|
+
const unknownFormats = allRequestedFormats.filter((f) => !builtInFormats.has(f) && !customFormatterNames.has(f));
|
|
19440
|
+
if (unknownFormats.length > 0) {
|
|
19441
|
+
const knownCustom = customFormatterNames.size > 0 ? `, ${[...customFormatterNames].join(", ")}` : "";
|
|
19442
|
+
console.error(`Error: Unknown format(s): ${unknownFormats.join(", ")}. Valid built-in: astro, confluence, html, markdown, junit, cucumber-json, cucumber-messages, cucumber-html${knownCustom}.`);
|
|
19443
|
+
process.exit(EXIT_USAGE);
|
|
18675
19444
|
}
|
|
19445
|
+
const formats = builtInRequested;
|
|
18676
19446
|
const htmlTheme = values["html-theme"];
|
|
18677
19447
|
const validThemes = /* @__PURE__ */ new Set(["default", "corporate", "terminal", "minimal", "dashboard", "playful"]);
|
|
18678
19448
|
if (!validThemes.has(htmlTheme)) {
|
|
@@ -18740,7 +19510,7 @@ function parseCliArgs(argv) {
|
|
|
18740
19510
|
console.error(`Error: --asset-mode must be "none" or "copy", got "${assetModeRaw}".`);
|
|
18741
19511
|
process.exit(EXIT_USAGE);
|
|
18742
19512
|
}
|
|
18743
|
-
|
|
19513
|
+
const cliArgs = {
|
|
18744
19514
|
subcommand,
|
|
18745
19515
|
inputFile,
|
|
18746
19516
|
baselineFile,
|
|
@@ -18769,6 +19539,7 @@ function parseCliArgs(argv) {
|
|
|
18769
19539
|
htmlNoToc: values["html-no-toc"],
|
|
18770
19540
|
htmlThemePicker: values["html-theme-picker"],
|
|
18771
19541
|
jsonSummary: values["json-summary"],
|
|
19542
|
+
listFormat: values["list-format"],
|
|
18772
19543
|
emitCanonical: values["emit-canonical"],
|
|
18773
19544
|
slackWebhook,
|
|
18774
19545
|
teamsWebhook,
|
|
@@ -18786,8 +19557,10 @@ function parseCliArgs(argv) {
|
|
|
18786
19557
|
assetMode: assetModeRaw,
|
|
18787
19558
|
allowMissingAssets: values["allow-missing-assets"],
|
|
18788
19559
|
prSummary: values["pr-summary"],
|
|
18789
|
-
prSummaryFile: values["pr-summary-file"]
|
|
19560
|
+
prSummaryFile: values["pr-summary-file"],
|
|
19561
|
+
config: values["config"]
|
|
18790
19562
|
};
|
|
19563
|
+
return { args: cliArgs, pluginConfig, customRequested };
|
|
18791
19564
|
}
|
|
18792
19565
|
async function readInput(args) {
|
|
18793
19566
|
if (args.stdin) {
|
|
@@ -18809,26 +19582,26 @@ function readFileInput(filePath) {
|
|
|
18809
19582
|
return fs7.readFileSync(resolved, "utf8");
|
|
18810
19583
|
}
|
|
18811
19584
|
function readStdin() {
|
|
18812
|
-
return new Promise((
|
|
19585
|
+
return new Promise((resolve8, reject) => {
|
|
18813
19586
|
const chunks = [];
|
|
18814
19587
|
process.stdin.setEncoding("utf8");
|
|
18815
19588
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
18816
|
-
process.stdin.on("end", () =>
|
|
19589
|
+
process.stdin.on("end", () => resolve8(chunks.join("")));
|
|
18817
19590
|
process.stdin.on("error", reject);
|
|
18818
19591
|
});
|
|
18819
19592
|
}
|
|
18820
|
-
function parseJson(
|
|
19593
|
+
function parseJson(text2) {
|
|
18821
19594
|
try {
|
|
18822
|
-
return JSON.parse(
|
|
19595
|
+
return JSON.parse(text2);
|
|
18823
19596
|
} catch (err) {
|
|
18824
19597
|
const msg = err instanceof Error ? err.message : String(err);
|
|
18825
19598
|
console.error(`Error: Invalid JSON \u2014 ${msg}`);
|
|
18826
19599
|
process.exit(EXIT_USAGE);
|
|
18827
19600
|
}
|
|
18828
19601
|
}
|
|
18829
|
-
function tryParseJson(
|
|
19602
|
+
function tryParseJson(text2) {
|
|
18830
19603
|
try {
|
|
18831
|
-
return JSON.parse(
|
|
19604
|
+
return JSON.parse(text2);
|
|
18832
19605
|
} catch {
|
|
18833
19606
|
return void 0;
|
|
18834
19607
|
}
|
|
@@ -18878,17 +19651,17 @@ ${msg}`);
|
|
|
18878
19651
|
}
|
|
18879
19652
|
return { run: canonical, droppedMissingStory };
|
|
18880
19653
|
}
|
|
18881
|
-
function normalizeRunFromText(
|
|
19654
|
+
function normalizeRunFromText(text2, args) {
|
|
18882
19655
|
if (args.inputType === "ndjson") {
|
|
18883
19656
|
try {
|
|
18884
|
-
return { run: parseNdjson(
|
|
19657
|
+
return { run: parseNdjson(text2), droppedMissingStory: 0 };
|
|
18885
19658
|
} catch (err) {
|
|
18886
19659
|
const msg = err instanceof Error ? err.message : String(err);
|
|
18887
19660
|
console.error(`NDJSON parse failed: ${msg}`);
|
|
18888
19661
|
process.exit(EXIT_SCHEMA_VALIDATION);
|
|
18889
19662
|
}
|
|
18890
19663
|
}
|
|
18891
|
-
return normalizeRunFromJsonData(parseJson(
|
|
19664
|
+
return normalizeRunFromJsonData(parseJson(text2), args);
|
|
18892
19665
|
}
|
|
18893
19666
|
function applySelection(run, args) {
|
|
18894
19667
|
const testCases = selectTestCases(
|
|
@@ -18904,15 +19677,15 @@ function applySelection(run, args) {
|
|
|
18904
19677
|
);
|
|
18905
19678
|
return { ...run, testCases };
|
|
18906
19679
|
}
|
|
18907
|
-
function tryNormalizeRunFromText(
|
|
19680
|
+
function tryNormalizeRunFromText(text2, args) {
|
|
18908
19681
|
if (args.inputType === "ndjson") {
|
|
18909
19682
|
try {
|
|
18910
|
-
return parseNdjson(
|
|
19683
|
+
return parseNdjson(text2);
|
|
18911
19684
|
} catch {
|
|
18912
19685
|
return void 0;
|
|
18913
19686
|
}
|
|
18914
19687
|
}
|
|
18915
|
-
const data = tryParseJson(
|
|
19688
|
+
const data = tryParseJson(text2);
|
|
18916
19689
|
if (data === void 0) return void 0;
|
|
18917
19690
|
if (args.inputType === "canonical") {
|
|
18918
19691
|
try {
|
|
@@ -18973,7 +19746,7 @@ function resolveBaselineAuto(currentFile, currentRun, args) {
|
|
|
18973
19746
|
return picked.file;
|
|
18974
19747
|
}
|
|
18975
19748
|
async function main() {
|
|
18976
|
-
const args = parseCliArgs(process.argv);
|
|
19749
|
+
const { args, pluginConfig, customRequested } = await parseCliArgs(process.argv);
|
|
18977
19750
|
const startMs = Date.now();
|
|
18978
19751
|
if (args.subcommand === "compare") {
|
|
18979
19752
|
const currentText = readFileInput(args.currentFile);
|
|
@@ -18992,17 +19765,25 @@ async function main() {
|
|
|
18992
19765
|
}
|
|
18993
19766
|
}
|
|
18994
19767
|
if (args.subcommand === "list") {
|
|
18995
|
-
const
|
|
18996
|
-
const run = applySelection(normalizeRunFromText(
|
|
18997
|
-
const
|
|
18998
|
-
const
|
|
19768
|
+
const text3 = await readInput(args);
|
|
19769
|
+
const run = applySelection(normalizeRunFromText(text3, args).run, args);
|
|
19770
|
+
const resolvedFormat = args.jsonSummary ? "json" : args.listFormat;
|
|
19771
|
+
const validListFormats = /* @__PURE__ */ new Set(["text", "json", "csv", "markdown-table"]);
|
|
19772
|
+
if (!validListFormats.has(resolvedFormat)) {
|
|
19773
|
+
console.error(`Error: Unknown list format "${resolvedFormat}". Valid: text, json, csv, markdown-table.`);
|
|
19774
|
+
process.exit(EXIT_USAGE);
|
|
19775
|
+
}
|
|
19776
|
+
const output = listScenarios(
|
|
19777
|
+
{ testCases: run.testCases, format: resolvedFormat },
|
|
19778
|
+
{}
|
|
19779
|
+
);
|
|
18999
19780
|
console.log(output);
|
|
19000
19781
|
process.exit(EXIT_SUCCESS);
|
|
19001
19782
|
}
|
|
19002
|
-
const
|
|
19783
|
+
const text2 = await readInput(args);
|
|
19003
19784
|
if (args.inputType === "ndjson") {
|
|
19004
19785
|
if (args.subcommand === "validate") {
|
|
19005
|
-
const lines =
|
|
19786
|
+
const lines = text2.trim().split("\n").filter(Boolean);
|
|
19006
19787
|
const validKeys = /* @__PURE__ */ new Set([
|
|
19007
19788
|
"meta",
|
|
19008
19789
|
"source",
|
|
@@ -19035,7 +19816,7 @@ async function main() {
|
|
|
19035
19816
|
}
|
|
19036
19817
|
let run;
|
|
19037
19818
|
try {
|
|
19038
|
-
run = parseNdjson(
|
|
19819
|
+
run = parseNdjson(text2);
|
|
19039
19820
|
} catch (err) {
|
|
19040
19821
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19041
19822
|
console.error(`NDJSON parse failed: ${msg}`);
|
|
@@ -19048,6 +19829,7 @@ async function main() {
|
|
|
19048
19829
|
}
|
|
19049
19830
|
try {
|
|
19050
19831
|
const result = await generateReports(run, args);
|
|
19832
|
+
runCustomFormatters(run, customRequested, pluginConfig.formatters ?? {}, args);
|
|
19051
19833
|
await dispatchNotifications(run, args);
|
|
19052
19834
|
runHistoryPipeline(run, args);
|
|
19053
19835
|
printResult(result, args, startMs);
|
|
@@ -19058,7 +19840,7 @@ async function main() {
|
|
|
19058
19840
|
process.exit(EXIT_GENERATION);
|
|
19059
19841
|
}
|
|
19060
19842
|
}
|
|
19061
|
-
const data = parseJson(
|
|
19843
|
+
const data = parseJson(text2);
|
|
19062
19844
|
if (args.subcommand === "validate") {
|
|
19063
19845
|
if (args.inputType === "canonical") {
|
|
19064
19846
|
try {
|
|
@@ -19106,6 +19888,7 @@ ${msg}`);
|
|
|
19106
19888
|
}
|
|
19107
19889
|
try {
|
|
19108
19890
|
const result = await generateReports(run, args);
|
|
19891
|
+
runCustomFormatters(run, customRequested, pluginConfig.formatters ?? {}, args);
|
|
19109
19892
|
await dispatchNotifications(run, args);
|
|
19110
19893
|
runHistoryPipeline(run, args);
|
|
19111
19894
|
printResult(result, args, startMs);
|
|
@@ -19163,6 +19946,7 @@ ${msg}`);
|
|
|
19163
19946
|
}
|
|
19164
19947
|
try {
|
|
19165
19948
|
const result = await generateReports(canonical, args, droppedMissingStory);
|
|
19949
|
+
runCustomFormatters(canonical, customRequested, pluginConfig.formatters ?? {}, args);
|
|
19166
19950
|
await dispatchNotifications(canonical, args);
|
|
19167
19951
|
runHistoryPipeline(canonical, args);
|
|
19168
19952
|
printResult(result, args, startMs, droppedMissingStory);
|
|
@@ -19173,6 +19957,25 @@ ${msg}`);
|
|
|
19173
19957
|
process.exit(EXIT_GENERATION);
|
|
19174
19958
|
}
|
|
19175
19959
|
}
|
|
19960
|
+
function runCustomFormatters(run, customRequested, formatters, args) {
|
|
19961
|
+
if (customRequested.length === 0) return;
|
|
19962
|
+
const outputDir = args.outputDir ?? ".";
|
|
19963
|
+
for (const formatName of customRequested) {
|
|
19964
|
+
const formatter = formatters[formatName];
|
|
19965
|
+
try {
|
|
19966
|
+
const content = formatter.format(run);
|
|
19967
|
+
const ext = formatter.fileExtension ?? formatName;
|
|
19968
|
+
const baseName = args.outputName ?? "report";
|
|
19969
|
+
const filename = args.outputNameTimestamp ? `${baseName}-${Math.floor(run.startedAtMs / 1e3)}.${ext}` : `${baseName}.${ext}`;
|
|
19970
|
+
const filepath = path7.join(outputDir, filename);
|
|
19971
|
+
fs7.mkdirSync(outputDir, { recursive: true });
|
|
19972
|
+
fs7.writeFileSync(filepath, content, "utf8");
|
|
19973
|
+
console.log(`Generated: ${filepath}`);
|
|
19974
|
+
} catch (err) {
|
|
19975
|
+
console.error(`Error running custom formatter "${formatName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
19976
|
+
}
|
|
19977
|
+
}
|
|
19978
|
+
}
|
|
19176
19979
|
async function dispatchNotifications(run, args) {
|
|
19177
19980
|
const webhooks = args.webhookUrls.map((url) => {
|
|
19178
19981
|
const opts = { url };
|
|
@@ -19366,6 +20169,225 @@ function printCompareResult(result, args, startMs) {
|
|
|
19366
20169
|
console.log(result.prSummary);
|
|
19367
20170
|
}
|
|
19368
20171
|
}
|
|
20172
|
+
async function runPublishConfluence(rawArgs) {
|
|
20173
|
+
const { values, positionals } = parseArgs({
|
|
20174
|
+
args: rawArgs,
|
|
20175
|
+
options: {
|
|
20176
|
+
"page-id": { type: "string" },
|
|
20177
|
+
"space-id": { type: "string" },
|
|
20178
|
+
"parent-id": { type: "string" },
|
|
20179
|
+
title: { type: "string" },
|
|
20180
|
+
"base-url": { type: "string" },
|
|
20181
|
+
email: { type: "string" },
|
|
20182
|
+
token: { type: "string" },
|
|
20183
|
+
"dry-run": { type: "boolean", default: false },
|
|
20184
|
+
help: { type: "boolean", default: false }
|
|
20185
|
+
},
|
|
20186
|
+
allowPositionals: true,
|
|
20187
|
+
strict: true
|
|
20188
|
+
});
|
|
20189
|
+
if (values.help) {
|
|
20190
|
+
console.log(`Usage: executable-stories publish-confluence <file.adf.json> [options]
|
|
20191
|
+
|
|
20192
|
+
Publishes an ADF JSON document to a Confluence Cloud page.
|
|
20193
|
+
|
|
20194
|
+
Required (one of):
|
|
20195
|
+
--page-id <id> Update an existing page in place
|
|
20196
|
+
--space-id <id> Create a new page in a space (also requires --title)
|
|
20197
|
+
|
|
20198
|
+
Optional:
|
|
20199
|
+
--parent-id <id> Parent page ID (new pages only)
|
|
20200
|
+
--title <title> Page title (required for create; overrides current title on update)
|
|
20201
|
+
--base-url <url> Confluence base URL, e.g. https://acme.atlassian.net/wiki
|
|
20202
|
+
(env: CONFLUENCE_BASE_URL)
|
|
20203
|
+
--email <email> Atlassian account email (env: CONFLUENCE_EMAIL)
|
|
20204
|
+
--token <token> Atlassian API token (env: CONFLUENCE_TOKEN)
|
|
20205
|
+
--dry-run Validate inputs and print the request plan, don't POST
|
|
20206
|
+
--help Show this help
|
|
20207
|
+
|
|
20208
|
+
Generate an API token at https://id.atlassian.com/manage-profile/security/api-tokens`);
|
|
20209
|
+
process.exit(EXIT_SUCCESS);
|
|
20210
|
+
}
|
|
20211
|
+
const inputFile = positionals[0];
|
|
20212
|
+
if (!inputFile) {
|
|
20213
|
+
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
20214
|
+
process.exit(EXIT_USAGE);
|
|
20215
|
+
}
|
|
20216
|
+
if (!fs7.existsSync(inputFile)) {
|
|
20217
|
+
console.error(`Error: file not found: ${inputFile}`);
|
|
20218
|
+
process.exit(EXIT_USAGE);
|
|
20219
|
+
}
|
|
20220
|
+
const baseUrl = values["base-url"] ?? process.env.CONFLUENCE_BASE_URL;
|
|
20221
|
+
const email = values.email ?? process.env.CONFLUENCE_EMAIL;
|
|
20222
|
+
const token = values.token ?? process.env.CONFLUENCE_TOKEN;
|
|
20223
|
+
const pageId = values["page-id"];
|
|
20224
|
+
const spaceId = values["space-id"];
|
|
20225
|
+
const parentId = values["parent-id"];
|
|
20226
|
+
const title = values.title;
|
|
20227
|
+
const dryRun = values["dry-run"];
|
|
20228
|
+
if (!baseUrl) {
|
|
20229
|
+
console.error(
|
|
20230
|
+
"Error: --base-url or CONFLUENCE_BASE_URL is required (e.g. https://acme.atlassian.net/wiki)"
|
|
20231
|
+
);
|
|
20232
|
+
process.exit(EXIT_USAGE);
|
|
20233
|
+
}
|
|
20234
|
+
if (!pageId && !spaceId) {
|
|
20235
|
+
console.error(
|
|
20236
|
+
"Error: specify either --page-id (to update) or --space-id (to create)"
|
|
20237
|
+
);
|
|
20238
|
+
process.exit(EXIT_USAGE);
|
|
20239
|
+
}
|
|
20240
|
+
if (!pageId && !title) {
|
|
20241
|
+
console.error("Error: --title is required when creating a new page");
|
|
20242
|
+
process.exit(EXIT_USAGE);
|
|
20243
|
+
}
|
|
20244
|
+
const adf = fs7.readFileSync(path7.resolve(inputFile), "utf8");
|
|
20245
|
+
if (dryRun) {
|
|
20246
|
+
console.log(
|
|
20247
|
+
JSON.stringify(
|
|
20248
|
+
{
|
|
20249
|
+
action: pageId ? "update" : "create",
|
|
20250
|
+
baseUrl,
|
|
20251
|
+
pageId,
|
|
20252
|
+
spaceId,
|
|
20253
|
+
parentId,
|
|
20254
|
+
title,
|
|
20255
|
+
adfBytes: adf.length
|
|
20256
|
+
},
|
|
20257
|
+
null,
|
|
20258
|
+
2
|
|
20259
|
+
)
|
|
20260
|
+
);
|
|
20261
|
+
process.exit(EXIT_SUCCESS);
|
|
20262
|
+
}
|
|
20263
|
+
if (!email || !token) {
|
|
20264
|
+
console.error(
|
|
20265
|
+
"Error: --email/CONFLUENCE_EMAIL and --token/CONFLUENCE_TOKEN are required unless --dry-run is set"
|
|
20266
|
+
);
|
|
20267
|
+
process.exit(EXIT_USAGE);
|
|
20268
|
+
}
|
|
20269
|
+
try {
|
|
20270
|
+
const result = await publishConfluencePage(
|
|
20271
|
+
{ adf, pageId, spaceId, parentId, title, baseUrl },
|
|
20272
|
+
{ auth: { email, token } }
|
|
20273
|
+
);
|
|
20274
|
+
console.log(
|
|
20275
|
+
`${result.action === "created" ? "Created" : "Updated"} "${result.title}" (v${result.version}) \u2192 ${result.url}`
|
|
20276
|
+
);
|
|
20277
|
+
process.exit(EXIT_SUCCESS);
|
|
20278
|
+
} catch (err) {
|
|
20279
|
+
console.error(`Error: ${err.message}`);
|
|
20280
|
+
process.exit(EXIT_GENERATION);
|
|
20281
|
+
}
|
|
20282
|
+
}
|
|
20283
|
+
async function runPublishJira(rawArgs) {
|
|
20284
|
+
const { values, positionals } = parseArgs({
|
|
20285
|
+
args: rawArgs,
|
|
20286
|
+
options: {
|
|
20287
|
+
issue: { type: "string" },
|
|
20288
|
+
mode: { type: "string", default: "comment" },
|
|
20289
|
+
"base-url": { type: "string" },
|
|
20290
|
+
email: { type: "string" },
|
|
20291
|
+
token: { type: "string" },
|
|
20292
|
+
"dry-run": { type: "boolean", default: false },
|
|
20293
|
+
help: { type: "boolean", default: false }
|
|
20294
|
+
},
|
|
20295
|
+
allowPositionals: true,
|
|
20296
|
+
strict: true
|
|
20297
|
+
});
|
|
20298
|
+
if (values.help) {
|
|
20299
|
+
console.log(`Usage: executable-stories publish-jira <file.adf.json> [options]
|
|
20300
|
+
|
|
20301
|
+
Publishes an ADF JSON document to a Jira Cloud issue.
|
|
20302
|
+
|
|
20303
|
+
Required:
|
|
20304
|
+
--issue <KEY> Issue key, e.g. PROJ-123
|
|
20305
|
+
|
|
20306
|
+
Optional:
|
|
20307
|
+
--mode <mode> "comment" (default, append-only) or "description" (replaces field)
|
|
20308
|
+
--base-url <url> Jira base URL, e.g. https://acme.atlassian.net
|
|
20309
|
+
(env: JIRA_BASE_URL)
|
|
20310
|
+
--email <email> Atlassian account email (env: JIRA_EMAIL)
|
|
20311
|
+
--token <token> Atlassian API token (env: JIRA_TOKEN)
|
|
20312
|
+
--dry-run Validate inputs and print the request plan, don't POST
|
|
20313
|
+
--help Show this help
|
|
20314
|
+
|
|
20315
|
+
Generate an API token at https://id.atlassian.com/manage-profile/security/api-tokens`);
|
|
20316
|
+
process.exit(EXIT_SUCCESS);
|
|
20317
|
+
}
|
|
20318
|
+
const inputFile = positionals[0];
|
|
20319
|
+
if (!inputFile) {
|
|
20320
|
+
console.error("Error: missing ADF file argument. Run with --help for usage.");
|
|
20321
|
+
process.exit(EXIT_USAGE);
|
|
20322
|
+
}
|
|
20323
|
+
if (!fs7.existsSync(inputFile)) {
|
|
20324
|
+
console.error(`Error: file not found: ${inputFile}`);
|
|
20325
|
+
process.exit(EXIT_USAGE);
|
|
20326
|
+
}
|
|
20327
|
+
const baseUrl = values["base-url"] ?? process.env.JIRA_BASE_URL;
|
|
20328
|
+
const email = values.email ?? process.env.JIRA_EMAIL;
|
|
20329
|
+
const token = values.token ?? process.env.JIRA_TOKEN;
|
|
20330
|
+
const issueKey = values.issue;
|
|
20331
|
+
const modeRaw = values.mode;
|
|
20332
|
+
const dryRun = values["dry-run"];
|
|
20333
|
+
if (!baseUrl) {
|
|
20334
|
+
console.error(
|
|
20335
|
+
"Error: --base-url or JIRA_BASE_URL is required (e.g. https://acme.atlassian.net)"
|
|
20336
|
+
);
|
|
20337
|
+
process.exit(EXIT_USAGE);
|
|
20338
|
+
}
|
|
20339
|
+
if (!issueKey) {
|
|
20340
|
+
console.error("Error: --issue <KEY> is required (e.g. --issue PROJ-123)");
|
|
20341
|
+
process.exit(EXIT_USAGE);
|
|
20342
|
+
}
|
|
20343
|
+
if (modeRaw !== "comment" && modeRaw !== "description") {
|
|
20344
|
+
console.error(
|
|
20345
|
+
`Error: --mode must be "comment" or "description" (got "${modeRaw}")`
|
|
20346
|
+
);
|
|
20347
|
+
process.exit(EXIT_USAGE);
|
|
20348
|
+
}
|
|
20349
|
+
const mode = modeRaw;
|
|
20350
|
+
const adf = fs7.readFileSync(path7.resolve(inputFile), "utf8");
|
|
20351
|
+
if (dryRun) {
|
|
20352
|
+
console.log(
|
|
20353
|
+
JSON.stringify(
|
|
20354
|
+
{
|
|
20355
|
+
action: mode === "description" ? "description-updated" : "comment-added",
|
|
20356
|
+
baseUrl,
|
|
20357
|
+
issueKey,
|
|
20358
|
+
mode,
|
|
20359
|
+
adfBytes: adf.length
|
|
20360
|
+
},
|
|
20361
|
+
null,
|
|
20362
|
+
2
|
|
20363
|
+
)
|
|
20364
|
+
);
|
|
20365
|
+
process.exit(EXIT_SUCCESS);
|
|
20366
|
+
}
|
|
20367
|
+
if (!email || !token) {
|
|
20368
|
+
console.error(
|
|
20369
|
+
"Error: --email/JIRA_EMAIL and --token/JIRA_TOKEN are required unless --dry-run is set"
|
|
20370
|
+
);
|
|
20371
|
+
process.exit(EXIT_USAGE);
|
|
20372
|
+
}
|
|
20373
|
+
try {
|
|
20374
|
+
const result = await publishJiraIssue(
|
|
20375
|
+
{ adf, issueKey, baseUrl, mode },
|
|
20376
|
+
{ auth: { email, token } }
|
|
20377
|
+
);
|
|
20378
|
+
if (result.action === "comment-added") {
|
|
20379
|
+
console.log(
|
|
20380
|
+
`Added comment to ${result.issueKey} (comment ${result.commentId}) \u2192 ${result.url}`
|
|
20381
|
+
);
|
|
20382
|
+
} else {
|
|
20383
|
+
console.log(`Updated description for ${result.issueKey} \u2192 ${result.url}`);
|
|
20384
|
+
}
|
|
20385
|
+
process.exit(EXIT_SUCCESS);
|
|
20386
|
+
} catch (err) {
|
|
20387
|
+
console.error(`Error: ${err.message}`);
|
|
20388
|
+
process.exit(EXIT_GENERATION);
|
|
20389
|
+
}
|
|
20390
|
+
}
|
|
19369
20391
|
main().catch((err) => {
|
|
19370
20392
|
console.error(err);
|
|
19371
20393
|
process.exit(EXIT_USAGE);
|