executable-stories-formatters 0.15.1 → 0.17.0
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/dist/cli.js +669 -301
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +511 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -6
- package/dist/index.d.ts +111 -6
- package/dist/index.js +504 -156
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -59,6 +59,7 @@ __export(src_exports, {
|
|
|
59
59
|
adaptJestRun: () => adaptJestRun,
|
|
60
60
|
adaptPlaywrightRun: () => adaptPlaywrightRun,
|
|
61
61
|
adaptVitestRun: () => adaptVitestRun,
|
|
62
|
+
advanceState: () => advanceState,
|
|
62
63
|
assertValidRun: () => assertValidRun,
|
|
63
64
|
buildCheck: () => buildCheck,
|
|
64
65
|
buildGoal: () => buildGoal,
|
|
@@ -71,6 +72,7 @@ __export(src_exports, {
|
|
|
71
72
|
canonicalizeRun: () => canonicalizeRun,
|
|
72
73
|
classifyStatusChange: () => classifyStatusChange,
|
|
73
74
|
clearVersionCache: () => clearVersionCache,
|
|
75
|
+
computeDeltas: () => computeDeltas,
|
|
74
76
|
computeTestMetrics: () => computeTestMetrics,
|
|
75
77
|
copyMarkdownAssets: () => copyMarkdownAssets,
|
|
76
78
|
createPrCommentSummary: () => createPrCommentSummary,
|
|
@@ -93,6 +95,7 @@ __export(src_exports, {
|
|
|
93
95
|
getEnvironmentDrift: () => getEnvironmentDrift,
|
|
94
96
|
gradeEvidence: () => gradeEvidence,
|
|
95
97
|
hasSufficientHistory: () => hasSufficientHistory,
|
|
98
|
+
injectLiveBits: () => injectLiveBits,
|
|
96
99
|
isReviewableSource: () => isReviewableSource,
|
|
97
100
|
isTestFile: () => isTestFile,
|
|
98
101
|
joinNameAndExt: () => joinNameAndExt,
|
|
@@ -114,7 +117,9 @@ __export(src_exports, {
|
|
|
114
117
|
readPackageVersion: () => readPackageVersion,
|
|
115
118
|
recordDeployment: () => recordDeployment,
|
|
116
119
|
regenerateArtifacts: () => regenerateArtifacts,
|
|
120
|
+
regenerateRun: () => regenerateRun,
|
|
117
121
|
renderCheck: () => renderCheck,
|
|
122
|
+
renderDeltaStrip: () => renderDeltaStrip,
|
|
118
123
|
renderGoal: () => renderGoal,
|
|
119
124
|
renderTriage: () => renderTriage,
|
|
120
125
|
resolveAttachment: () => resolveAttachment,
|
|
@@ -130,6 +135,7 @@ __export(src_exports, {
|
|
|
130
135
|
sendWebhookNotification: () => sendWebhookNotification,
|
|
131
136
|
signBody: () => signBody,
|
|
132
137
|
slugify: () => slugify,
|
|
138
|
+
startServe: () => startServe,
|
|
133
139
|
startWatch: () => startWatch,
|
|
134
140
|
stripAnsi: () => stripAnsi,
|
|
135
141
|
toBehaviorManifest: () => toBehaviorManifest,
|
|
@@ -144,8 +150,8 @@ __export(src_exports, {
|
|
|
144
150
|
validateCanonicalRun: () => validateCanonicalRun
|
|
145
151
|
});
|
|
146
152
|
module.exports = __toCommonJS(src_exports);
|
|
147
|
-
var
|
|
148
|
-
var
|
|
153
|
+
var fs11 = require("fs");
|
|
154
|
+
var path12 = __toESM(require("path"), 1);
|
|
149
155
|
var fsPromises = __toESM(require("fs/promises"), 1);
|
|
150
156
|
|
|
151
157
|
// src/converters/acl/status.ts
|
|
@@ -187,6 +193,41 @@ function generateFeatureId(uri) {
|
|
|
187
193
|
function generateScenarioId(featureId, scenarioName) {
|
|
188
194
|
return `${featureId};${slugify(scenarioName)}`;
|
|
189
195
|
}
|
|
196
|
+
function normalizeText(text2) {
|
|
197
|
+
return text2.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
198
|
+
}
|
|
199
|
+
function tokenize(text2) {
|
|
200
|
+
const normalized = normalizeText(text2);
|
|
201
|
+
return normalized.length === 0 ? [] : normalized.split(" ");
|
|
202
|
+
}
|
|
203
|
+
function behaviourFingerprint(input) {
|
|
204
|
+
const steps = input.steps.map((step) => `${step.keyword.toLowerCase()}:${normalizeText(step.text)}`).join("\n");
|
|
205
|
+
const covers = [...input.covers ?? []].map((path13) => path13.trim()).filter(Boolean).sort().join(",");
|
|
206
|
+
if (steps.length === 0 && covers.length === 0) return "";
|
|
207
|
+
return (0, import_node_crypto.createHash)("sha1").update(`${steps}\0${covers}`).digest("hex").slice(0, 16);
|
|
208
|
+
}
|
|
209
|
+
function jaccard(a, b) {
|
|
210
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
211
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
212
|
+
let intersection = 0;
|
|
213
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
214
|
+
return intersection / (a.size + b.size - intersection);
|
|
215
|
+
}
|
|
216
|
+
function stepTokens(steps) {
|
|
217
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
218
|
+
for (const step of steps) {
|
|
219
|
+
for (const token of tokenize(step.text)) tokens.add(token);
|
|
220
|
+
}
|
|
221
|
+
return tokens;
|
|
222
|
+
}
|
|
223
|
+
function behaviourSimilarity(a, b) {
|
|
224
|
+
const aSteps = stepTokens(a.steps);
|
|
225
|
+
const bSteps = stepTokens(b.steps);
|
|
226
|
+
if (aSteps.size === 0 || bSteps.size === 0) return 0;
|
|
227
|
+
const stepScore = jaccard(aSteps, bSteps);
|
|
228
|
+
const titleScore = jaccard(new Set(tokenize(a.scenario)), new Set(tokenize(b.scenario)));
|
|
229
|
+
return stepScore * 0.7 + titleScore * 0.3;
|
|
230
|
+
}
|
|
190
231
|
|
|
191
232
|
// src/converters/acl/steps.ts
|
|
192
233
|
function deriveStepResults(steps, scenarioStatus, error) {
|
|
@@ -14384,12 +14425,12 @@ function hasSufficientHistory(entries, min) {
|
|
|
14384
14425
|
}
|
|
14385
14426
|
|
|
14386
14427
|
// src/formatters/html/renderers/scenario.ts
|
|
14387
|
-
function renderTicket(ticket, template,
|
|
14428
|
+
function renderTicket(ticket, template, escapeHtml5) {
|
|
14388
14429
|
const url = ticket.url ?? (template ? template.replace("{ticket}", ticket.id) : void 0);
|
|
14389
14430
|
if (url) {
|
|
14390
|
-
return `<a class="tag ticket-tag" href="${
|
|
14431
|
+
return `<a class="tag ticket-tag" href="${escapeHtml5(url)}" target="_blank" rel="noopener noreferrer">${escapeHtml5(ticket.id)}</a>`;
|
|
14391
14432
|
}
|
|
14392
|
-
return `<span class="tag ticket-tag">${
|
|
14433
|
+
return `<span class="tag ticket-tag">${escapeHtml5(ticket.id)}</span>`;
|
|
14393
14434
|
}
|
|
14394
14435
|
function renderScenario(args, deps) {
|
|
14395
14436
|
const { tc } = args;
|
|
@@ -14589,7 +14630,7 @@ function flattenTree(roots) {
|
|
|
14589
14630
|
}
|
|
14590
14631
|
return result;
|
|
14591
14632
|
}
|
|
14592
|
-
function buildTooltip(span,
|
|
14633
|
+
function buildTooltip(span, escapeHtml5) {
|
|
14593
14634
|
const parts = [];
|
|
14594
14635
|
parts.push(`${span.name} (${formatDuration(span.durationMs)})`);
|
|
14595
14636
|
if (span.statusMessage) {
|
|
@@ -14607,7 +14648,7 @@ function buildTooltip(span, escapeHtml4) {
|
|
|
14607
14648
|
if (text2.length > TOOLTIP_MAX_LENGTH) {
|
|
14608
14649
|
text2 = text2.slice(0, TOOLTIP_MAX_LENGTH - 3) + "...";
|
|
14609
14650
|
}
|
|
14610
|
-
return
|
|
14651
|
+
return escapeHtml5(text2);
|
|
14611
14652
|
}
|
|
14612
14653
|
function renderTraceView(args, deps) {
|
|
14613
14654
|
if (!args.spans || args.spans.length === 0) return "";
|
|
@@ -15902,14 +15943,14 @@ var TraceabilityMatrixFormatter = class {
|
|
|
15902
15943
|
lines.push("");
|
|
15903
15944
|
lines.push(`Status: ${renderRequirementStatus(req.status)}`);
|
|
15904
15945
|
if (req.covers.length > 0) {
|
|
15905
|
-
lines.push(`Covers: ${req.covers.map((
|
|
15946
|
+
lines.push(`Covers: ${req.covers.map((path13) => `\`${path13}\``).join(", ")}`);
|
|
15906
15947
|
}
|
|
15907
15948
|
lines.push("");
|
|
15908
15949
|
lines.push("| Status | Scenario | Source | Covers |");
|
|
15909
15950
|
lines.push("| --- | --- | --- | --- |");
|
|
15910
15951
|
for (const scenario of req.scenarios) {
|
|
15911
15952
|
const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
|
|
15912
|
-
const covers = scenario.covers.length > 0 ? scenario.covers.map((
|
|
15953
|
+
const covers = scenario.covers.length > 0 ? scenario.covers.map((path13) => `\`${path13}\``).join(", ") : "";
|
|
15913
15954
|
lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` | ${covers} |`);
|
|
15914
15955
|
}
|
|
15915
15956
|
lines.push("");
|
|
@@ -16011,8 +16052,8 @@ function extractFeatureName(testCases, uri) {
|
|
|
16011
16052
|
return tc.titlePath[0];
|
|
16012
16053
|
}
|
|
16013
16054
|
}
|
|
16014
|
-
const
|
|
16015
|
-
return
|
|
16055
|
+
const basename4 = uri.replace(/^.*[\\/]/, "").replace(/\.[^.]+$/, "");
|
|
16056
|
+
return basename4.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
16016
16057
|
}
|
|
16017
16058
|
function synthesizeFeature(uri, testCases) {
|
|
16018
16059
|
const featureName = extractFeatureName(testCases, uri);
|
|
@@ -16624,8 +16665,8 @@ function extractDocAttachments(step) {
|
|
|
16624
16665
|
}
|
|
16625
16666
|
return attachments;
|
|
16626
16667
|
}
|
|
16627
|
-
function guessMediaType(
|
|
16628
|
-
const lower =
|
|
16668
|
+
function guessMediaType(path13) {
|
|
16669
|
+
const lower = path13.toLowerCase();
|
|
16629
16670
|
if (lower.endsWith(".png")) return "image/png";
|
|
16630
16671
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
16631
16672
|
if (lower.endsWith(".gif")) return "image/gif";
|
|
@@ -16766,11 +16807,11 @@ var CucumberHtmlFormatter = class {
|
|
|
16766
16807
|
for (const envelope of envelopes) {
|
|
16767
16808
|
const accepted = htmlStream.write(envelope);
|
|
16768
16809
|
if (!accepted) {
|
|
16769
|
-
await new Promise((
|
|
16810
|
+
await new Promise((resolve10) => htmlStream.once("drain", resolve10));
|
|
16770
16811
|
}
|
|
16771
16812
|
}
|
|
16772
|
-
await new Promise((
|
|
16773
|
-
collector.on("finish",
|
|
16813
|
+
await new Promise((resolve10, reject) => {
|
|
16814
|
+
collector.on("finish", resolve10);
|
|
16774
16815
|
collector.on("error", reject);
|
|
16775
16816
|
htmlStream.end();
|
|
16776
16817
|
});
|
|
@@ -16808,6 +16849,10 @@ function titleFor(kind) {
|
|
|
16808
16849
|
return "Added";
|
|
16809
16850
|
case "removed":
|
|
16810
16851
|
return "Removed";
|
|
16852
|
+
case "renamed":
|
|
16853
|
+
return "Renamed";
|
|
16854
|
+
case "moved":
|
|
16855
|
+
return "Moved";
|
|
16811
16856
|
case "changed":
|
|
16812
16857
|
return "Changed";
|
|
16813
16858
|
default:
|
|
@@ -16856,6 +16901,8 @@ function createPrCommentSummary(diff, maxScenarios = 10) {
|
|
|
16856
16901
|
addSection(lines, diff, "fixed", maxScenarios);
|
|
16857
16902
|
addSection(lines, diff, "added", maxScenarios);
|
|
16858
16903
|
addSection(lines, diff, "removed", maxScenarios);
|
|
16904
|
+
addSection(lines, diff, "renamed", maxScenarios);
|
|
16905
|
+
addSection(lines, diff, "moved", maxScenarios);
|
|
16859
16906
|
addSection(lines, diff, "changed", maxScenarios);
|
|
16860
16907
|
return lines.join("\n").trimEnd();
|
|
16861
16908
|
}
|
|
@@ -16902,8 +16949,10 @@ function sortDiffs(scenarios) {
|
|
|
16902
16949
|
fixed: 1,
|
|
16903
16950
|
added: 2,
|
|
16904
16951
|
removed: 3,
|
|
16905
|
-
|
|
16906
|
-
|
|
16952
|
+
renamed: 4,
|
|
16953
|
+
moved: 5,
|
|
16954
|
+
changed: 6,
|
|
16955
|
+
unchanged: 7
|
|
16907
16956
|
};
|
|
16908
16957
|
return [...scenarios].sort((a, b) => {
|
|
16909
16958
|
if (rank[a.kind] !== rank[b.kind]) {
|
|
@@ -16918,69 +16967,108 @@ function sortDiffs(scenarios) {
|
|
|
16918
16967
|
return a.scenario.localeCompare(b.scenario);
|
|
16919
16968
|
});
|
|
16920
16969
|
}
|
|
16970
|
+
var SIMILARITY_THRESHOLD = 0.75;
|
|
16971
|
+
function identityInput(tc) {
|
|
16972
|
+
return {
|
|
16973
|
+
scenario: tc.story.scenario,
|
|
16974
|
+
sourceFile: tc.sourceFile,
|
|
16975
|
+
steps: tc.story.steps,
|
|
16976
|
+
covers: tc.story.covers
|
|
16977
|
+
};
|
|
16978
|
+
}
|
|
16979
|
+
function changedFieldsOf(flags) {
|
|
16980
|
+
return Object.entries(flags).filter(([, changed]) => changed).map(([field]) => field);
|
|
16981
|
+
}
|
|
16982
|
+
function allChangedFlags(errorMessage) {
|
|
16983
|
+
return {
|
|
16984
|
+
status: true,
|
|
16985
|
+
steps: true,
|
|
16986
|
+
docs: true,
|
|
16987
|
+
tags: true,
|
|
16988
|
+
tickets: true,
|
|
16989
|
+
source: true,
|
|
16990
|
+
duration: true,
|
|
16991
|
+
attachments: true,
|
|
16992
|
+
error: Boolean(errorMessage),
|
|
16993
|
+
titlePath: true
|
|
16994
|
+
};
|
|
16995
|
+
}
|
|
16996
|
+
function matchIdentities(removed, added) {
|
|
16997
|
+
const pairs = [];
|
|
16998
|
+
const remainingRemoved = new Set(removed);
|
|
16999
|
+
const remainingAdded = new Set(added);
|
|
17000
|
+
const removedByFp = /* @__PURE__ */ new Map();
|
|
17001
|
+
const addedByFp = /* @__PURE__ */ new Map();
|
|
17002
|
+
for (const tc of remainingRemoved) {
|
|
17003
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17004
|
+
if (fp === "") continue;
|
|
17005
|
+
(removedByFp.get(fp) ?? removedByFp.set(fp, []).get(fp)).push(tc);
|
|
17006
|
+
}
|
|
17007
|
+
for (const tc of remainingAdded) {
|
|
17008
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17009
|
+
if (fp === "") continue;
|
|
17010
|
+
(addedByFp.get(fp) ?? addedByFp.set(fp, []).get(fp)).push(tc);
|
|
17011
|
+
}
|
|
17012
|
+
for (const [fp, removedGroup] of removedByFp) {
|
|
17013
|
+
const addedGroup = addedByFp.get(fp);
|
|
17014
|
+
if (removedGroup.length === 1 && addedGroup && addedGroup.length === 1) {
|
|
17015
|
+
pairs.push({ before: removedGroup[0], after: addedGroup[0], confidence: 1, matchedBy: "fingerprint" });
|
|
17016
|
+
remainingRemoved.delete(removedGroup[0]);
|
|
17017
|
+
remainingAdded.delete(addedGroup[0]);
|
|
17018
|
+
}
|
|
17019
|
+
}
|
|
17020
|
+
for (const before of [...remainingRemoved]) {
|
|
17021
|
+
let best;
|
|
17022
|
+
let bestScore = 0;
|
|
17023
|
+
let tied = false;
|
|
17024
|
+
for (const after of remainingAdded) {
|
|
17025
|
+
const score = behaviourSimilarity(identityInput(before), identityInput(after));
|
|
17026
|
+
if (score > bestScore) {
|
|
17027
|
+
bestScore = score;
|
|
17028
|
+
best = after;
|
|
17029
|
+
tied = false;
|
|
17030
|
+
} else if (score === bestScore) {
|
|
17031
|
+
tied = true;
|
|
17032
|
+
}
|
|
17033
|
+
}
|
|
17034
|
+
if (best && !tied && bestScore >= SIMILARITY_THRESHOLD) {
|
|
17035
|
+
pairs.push({ before, after: best, confidence: Math.round(bestScore * 100) / 100, matchedBy: "similarity" });
|
|
17036
|
+
remainingRemoved.delete(before);
|
|
17037
|
+
remainingAdded.delete(best);
|
|
17038
|
+
}
|
|
17039
|
+
}
|
|
17040
|
+
return {
|
|
17041
|
+
pairs,
|
|
17042
|
+
unmatchedRemoved: [...remainingRemoved],
|
|
17043
|
+
unmatchedAdded: [...remainingAdded]
|
|
17044
|
+
};
|
|
17045
|
+
}
|
|
17046
|
+
function identityKind(before, after) {
|
|
17047
|
+
const titleChanged = before.story.scenario !== after.story.scenario;
|
|
17048
|
+
const fileChanged = before.sourceFile !== after.sourceFile;
|
|
17049
|
+
return fileChanged && !titleChanged ? "moved" : "renamed";
|
|
17050
|
+
}
|
|
16921
17051
|
function diffRuns(baseline, current) {
|
|
16922
17052
|
const baselineById = new Map(baseline.testCases.map((tc) => [tc.id, tc]));
|
|
16923
17053
|
const currentById = new Map(current.testCases.map((tc) => [tc.id, tc]));
|
|
16924
17054
|
const ids = /* @__PURE__ */ new Set([...baselineById.keys(), ...currentById.keys()]);
|
|
16925
17055
|
const scenarios = [];
|
|
17056
|
+
const removedCases = [];
|
|
17057
|
+
const addedCases = [];
|
|
16926
17058
|
for (const id of ids) {
|
|
16927
17059
|
const before = baselineById.get(id);
|
|
16928
17060
|
const after = currentById.get(id);
|
|
16929
17061
|
if (!before && after) {
|
|
16930
|
-
|
|
16931
|
-
status: true,
|
|
16932
|
-
steps: true,
|
|
16933
|
-
docs: true,
|
|
16934
|
-
tags: true,
|
|
16935
|
-
tickets: true,
|
|
16936
|
-
source: true,
|
|
16937
|
-
duration: true,
|
|
16938
|
-
attachments: true,
|
|
16939
|
-
error: Boolean(after.errorMessage),
|
|
16940
|
-
titlePath: true
|
|
16941
|
-
};
|
|
16942
|
-
scenarios.push({
|
|
16943
|
-
kind: "added",
|
|
16944
|
-
id,
|
|
16945
|
-
scenario: after.story.scenario,
|
|
16946
|
-
sourceFile: after.sourceFile,
|
|
16947
|
-
sourceLine: after.sourceLine,
|
|
16948
|
-
current: toScenarioSnapshot(after),
|
|
16949
|
-
flags: flags2,
|
|
16950
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
16951
|
-
});
|
|
17062
|
+
addedCases.push(after);
|
|
16952
17063
|
continue;
|
|
16953
17064
|
}
|
|
16954
17065
|
if (before && !after) {
|
|
16955
|
-
|
|
16956
|
-
status: true,
|
|
16957
|
-
steps: true,
|
|
16958
|
-
docs: true,
|
|
16959
|
-
tags: true,
|
|
16960
|
-
tickets: true,
|
|
16961
|
-
source: true,
|
|
16962
|
-
duration: true,
|
|
16963
|
-
attachments: true,
|
|
16964
|
-
error: Boolean(before.errorMessage),
|
|
16965
|
-
titlePath: true
|
|
16966
|
-
};
|
|
16967
|
-
scenarios.push({
|
|
16968
|
-
kind: "removed",
|
|
16969
|
-
id,
|
|
16970
|
-
scenario: before.story.scenario,
|
|
16971
|
-
sourceFile: before.sourceFile,
|
|
16972
|
-
sourceLine: before.sourceLine,
|
|
16973
|
-
baseline: toScenarioSnapshot(before),
|
|
16974
|
-
flags: flags2,
|
|
16975
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
16976
|
-
});
|
|
16977
|
-
continue;
|
|
16978
|
-
}
|
|
16979
|
-
if (!before || !after) {
|
|
17066
|
+
removedCases.push(before);
|
|
16980
17067
|
continue;
|
|
16981
17068
|
}
|
|
17069
|
+
if (!before || !after) continue;
|
|
16982
17070
|
const flags = buildFlags(before, after);
|
|
16983
|
-
const changedFields =
|
|
17071
|
+
const changedFields = changedFieldsOf(flags);
|
|
16984
17072
|
const kind = getPrimaryKind(before, after, changedFields.length > 0);
|
|
16985
17073
|
scenarios.push({
|
|
16986
17074
|
kind,
|
|
@@ -16995,12 +17083,59 @@ function diffRuns(baseline, current) {
|
|
|
16995
17083
|
durationDeltaMs: after.durationMs - before.durationMs
|
|
16996
17084
|
});
|
|
16997
17085
|
}
|
|
17086
|
+
const { pairs, unmatchedRemoved, unmatchedAdded } = matchIdentities(removedCases, addedCases);
|
|
17087
|
+
for (const { before, after, confidence, matchedBy } of pairs) {
|
|
17088
|
+
const flags = buildFlags(before, after);
|
|
17089
|
+
scenarios.push({
|
|
17090
|
+
kind: identityKind(before, after),
|
|
17091
|
+
id: after.id,
|
|
17092
|
+
previousId: before.id,
|
|
17093
|
+
scenario: after.story.scenario,
|
|
17094
|
+
sourceFile: after.sourceFile,
|
|
17095
|
+
sourceLine: after.sourceLine,
|
|
17096
|
+
baseline: toScenarioSnapshot(before),
|
|
17097
|
+
current: toScenarioSnapshot(after),
|
|
17098
|
+
flags,
|
|
17099
|
+
changedFields: changedFieldsOf(flags),
|
|
17100
|
+
durationDeltaMs: after.durationMs - before.durationMs,
|
|
17101
|
+
matchConfidence: confidence,
|
|
17102
|
+
matchedBy
|
|
17103
|
+
});
|
|
17104
|
+
}
|
|
17105
|
+
for (const after of unmatchedAdded) {
|
|
17106
|
+
const flags = allChangedFlags(after.errorMessage);
|
|
17107
|
+
scenarios.push({
|
|
17108
|
+
kind: "added",
|
|
17109
|
+
id: after.id,
|
|
17110
|
+
scenario: after.story.scenario,
|
|
17111
|
+
sourceFile: after.sourceFile,
|
|
17112
|
+
sourceLine: after.sourceLine,
|
|
17113
|
+
current: toScenarioSnapshot(after),
|
|
17114
|
+
flags,
|
|
17115
|
+
changedFields: changedFieldsOf(flags)
|
|
17116
|
+
});
|
|
17117
|
+
}
|
|
17118
|
+
for (const before of unmatchedRemoved) {
|
|
17119
|
+
const flags = allChangedFlags(before.errorMessage);
|
|
17120
|
+
scenarios.push({
|
|
17121
|
+
kind: "removed",
|
|
17122
|
+
id: before.id,
|
|
17123
|
+
scenario: before.story.scenario,
|
|
17124
|
+
sourceFile: before.sourceFile,
|
|
17125
|
+
sourceLine: before.sourceLine,
|
|
17126
|
+
baseline: toScenarioSnapshot(before),
|
|
17127
|
+
flags,
|
|
17128
|
+
changedFields: changedFieldsOf(flags)
|
|
17129
|
+
});
|
|
17130
|
+
}
|
|
16998
17131
|
const sorted = sortDiffs(scenarios);
|
|
16999
17132
|
const summary = {
|
|
17000
17133
|
totalBaseline: baseline.testCases.length,
|
|
17001
17134
|
totalCurrent: current.testCases.length,
|
|
17002
17135
|
added: sorted.filter((s) => s.kind === "added").length,
|
|
17003
17136
|
removed: sorted.filter((s) => s.kind === "removed").length,
|
|
17137
|
+
renamed: sorted.filter((s) => s.kind === "renamed").length,
|
|
17138
|
+
moved: sorted.filter((s) => s.kind === "moved").length,
|
|
17004
17139
|
changed: sorted.filter((s) => s.kind === "changed").length,
|
|
17005
17140
|
regressed: sorted.filter((s) => s.kind === "regressed").length,
|
|
17006
17141
|
fixed: sorted.filter((s) => s.kind === "fixed").length,
|
|
@@ -17028,6 +17163,10 @@ function statusLabel(kind) {
|
|
|
17028
17163
|
return "Added";
|
|
17029
17164
|
case "removed":
|
|
17030
17165
|
return "Removed";
|
|
17166
|
+
case "renamed":
|
|
17167
|
+
return "Renamed";
|
|
17168
|
+
case "moved":
|
|
17169
|
+
return "Moved";
|
|
17031
17170
|
case "changed":
|
|
17032
17171
|
return "Changed";
|
|
17033
17172
|
default:
|
|
@@ -17353,6 +17492,8 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', fun
|
|
|
17353
17492
|
<div class="summary-card"><strong>${diff.summary.fixed}</strong><span>Fixed</span></div>
|
|
17354
17493
|
<div class="summary-card"><strong>${diff.summary.added}</strong><span>Added</span></div>
|
|
17355
17494
|
<div class="summary-card"><strong>${diff.summary.removed}</strong><span>Removed</span></div>
|
|
17495
|
+
<div class="summary-card"><strong>${diff.summary.renamed}</strong><span>Renamed</span></div>
|
|
17496
|
+
<div class="summary-card"><strong>${diff.summary.moved}</strong><span>Moved</span></div>
|
|
17356
17497
|
<div class="summary-card"><strong>${diff.summary.changed}</strong><span>Changed</span></div>
|
|
17357
17498
|
<div class="summary-card"><strong>${diff.summary.unchanged}</strong><span>Unchanged</span></div>
|
|
17358
17499
|
</section>
|
|
@@ -17413,6 +17554,10 @@ function formatStatus(kind) {
|
|
|
17413
17554
|
return "Added";
|
|
17414
17555
|
case "removed":
|
|
17415
17556
|
return "Removed";
|
|
17557
|
+
case "renamed":
|
|
17558
|
+
return "Renamed";
|
|
17559
|
+
case "moved":
|
|
17560
|
+
return "Moved";
|
|
17416
17561
|
case "changed":
|
|
17417
17562
|
return "Changed";
|
|
17418
17563
|
default:
|
|
@@ -17565,13 +17710,13 @@ var RunDiffMarkdownFormatter = class {
|
|
|
17565
17710
|
lines.push("No regressions or fixes detected. Remaining changes are neutral.");
|
|
17566
17711
|
}
|
|
17567
17712
|
lines.push("");
|
|
17568
|
-
lines.push("| Added | Removed | Regressed | Fixed | Changed | Unchanged |");
|
|
17569
|
-
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
17713
|
+
lines.push("| Added | Removed | Renamed | Moved | Regressed | Fixed | Changed | Unchanged |");
|
|
17714
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
17570
17715
|
lines.push(
|
|
17571
|
-
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
17716
|
+
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.renamed} | ${diff.summary.moved} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
17572
17717
|
);
|
|
17573
17718
|
lines.push("");
|
|
17574
|
-
for (const kind of ["regressed", "fixed", "added", "removed", "changed"]) {
|
|
17719
|
+
for (const kind of ["regressed", "fixed", "added", "removed", "renamed", "moved", "changed"]) {
|
|
17575
17720
|
const scenarios = diff.scenarios.filter((scenario) => scenario.kind === kind);
|
|
17576
17721
|
if (scenarios.length === 0) continue;
|
|
17577
17722
|
lines.push(`## ${formatStatus(kind)} (${scenarios.length})`);
|
|
@@ -18830,14 +18975,14 @@ ${result.errors.join("\n")}`);
|
|
|
18830
18975
|
}
|
|
18831
18976
|
|
|
18832
18977
|
// src/coverage-index.ts
|
|
18833
|
-
function normalizePath(
|
|
18834
|
-
return
|
|
18978
|
+
function normalizePath(path13) {
|
|
18979
|
+
return path13.replace(/^\.\//, "");
|
|
18835
18980
|
}
|
|
18836
18981
|
function scenariosCoveringPaths(index, paths) {
|
|
18837
18982
|
const queries = paths.map(normalizePath);
|
|
18838
18983
|
return index.scenarios.filter(
|
|
18839
18984
|
(scenario) => scenario.covers.some(
|
|
18840
|
-
(glob) => queries.some((
|
|
18985
|
+
(glob) => queries.some((path13) => matchesPattern(normalizePath(glob), path13))
|
|
18841
18986
|
)
|
|
18842
18987
|
);
|
|
18843
18988
|
}
|
|
@@ -18913,7 +19058,7 @@ function toRun(data, inputType, synthesize) {
|
|
|
18913
19058
|
if (synthesize) raw = synthesizeStories(raw);
|
|
18914
19059
|
return canonicalizeRun(raw);
|
|
18915
19060
|
}
|
|
18916
|
-
async function
|
|
19061
|
+
async function regenerateRun(options, deps = {}) {
|
|
18917
19062
|
const read = deps.readFile ?? ((filePath) => fs6.readFileSync(filePath, "utf8"));
|
|
18918
19063
|
const data = JSON.parse(read(path7.resolve(options.input)));
|
|
18919
19064
|
const run = toRun(data, options.inputType ?? "raw", options.synthesize !== false);
|
|
@@ -18923,7 +19068,10 @@ async function regenerateArtifacts(options, deps = {}) {
|
|
|
18923
19068
|
outputName: options.outputName
|
|
18924
19069
|
});
|
|
18925
19070
|
const result = await generator.generate(run);
|
|
18926
|
-
return [...result.values()].flat();
|
|
19071
|
+
return { files: [...result.values()].flat(), run };
|
|
19072
|
+
}
|
|
19073
|
+
async function regenerateArtifacts(options, deps = {}) {
|
|
19074
|
+
return (await regenerateRun(options, deps)).files;
|
|
18927
19075
|
}
|
|
18928
19076
|
function startWatch(options, deps = {}) {
|
|
18929
19077
|
const log = deps.log ?? ((message) => console.log(message));
|
|
@@ -18966,6 +19114,203 @@ function startWatch(options, deps = {}) {
|
|
|
18966
19114
|
};
|
|
18967
19115
|
}
|
|
18968
19116
|
|
|
19117
|
+
// src/serve.ts
|
|
19118
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
19119
|
+
var http = __toESM(require("http"), 1);
|
|
19120
|
+
var path8 = __toESM(require("path"), 1);
|
|
19121
|
+
function advanceState(prev, run) {
|
|
19122
|
+
if (prev.sessionBaseline === null) {
|
|
19123
|
+
return { sessionBaseline: run, previous: null, current: run, runCount: 1 };
|
|
19124
|
+
}
|
|
19125
|
+
return {
|
|
19126
|
+
sessionBaseline: prev.sessionBaseline,
|
|
19127
|
+
previous: prev.current,
|
|
19128
|
+
current: run,
|
|
19129
|
+
runCount: prev.runCount + 1
|
|
19130
|
+
};
|
|
19131
|
+
}
|
|
19132
|
+
function computeDeltas(state) {
|
|
19133
|
+
if (state.current === null || state.sessionBaseline === null || state.runCount <= 1) {
|
|
19134
|
+
return { session: null, iteration: null };
|
|
19135
|
+
}
|
|
19136
|
+
return {
|
|
19137
|
+
session: diffRuns(state.sessionBaseline, state.current),
|
|
19138
|
+
iteration: state.previous ? diffRuns(state.previous, state.current) : null
|
|
19139
|
+
};
|
|
19140
|
+
}
|
|
19141
|
+
function pluralize(n, word) {
|
|
19142
|
+
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
19143
|
+
}
|
|
19144
|
+
function summarizeDiff(diff) {
|
|
19145
|
+
const s = diff.summary;
|
|
19146
|
+
const parts = [];
|
|
19147
|
+
if (s.fixed > 0) parts.push(`+${pluralize(s.fixed, "passing")}`);
|
|
19148
|
+
if (s.regressed > 0) parts.push(`${pluralize(s.regressed, "regressed")}`);
|
|
19149
|
+
if (s.added > 0) parts.push(`${pluralize(s.added, "new behaviour")}`);
|
|
19150
|
+
if (s.removed > 0) parts.push(`${pluralize(s.removed, "removed")}`);
|
|
19151
|
+
const moved = s.renamed + s.moved;
|
|
19152
|
+
if (moved > 0) parts.push(`${pluralize(moved, "renamed")}`);
|
|
19153
|
+
if (s.changed > 0) parts.push(`${pluralize(s.changed, "changed")}`);
|
|
19154
|
+
return parts.length > 0 ? parts.join(", ") : null;
|
|
19155
|
+
}
|
|
19156
|
+
function escapeHtml3(text2) {
|
|
19157
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
19158
|
+
}
|
|
19159
|
+
function renderDeltaStrip(state) {
|
|
19160
|
+
if (state.current === null) return "";
|
|
19161
|
+
const { session, iteration } = computeDeltas(state);
|
|
19162
|
+
if (session === null) {
|
|
19163
|
+
const label = `Run #${state.runCount} captured \u2014 baseline pinned. Watching for changes\u2026`;
|
|
19164
|
+
return `<div data-es-live="strip"><strong>Live</strong> \xB7 ${escapeHtml3(label)}</div>`;
|
|
19165
|
+
}
|
|
19166
|
+
const sessionLine = summarizeDiff(session) ?? "no change yet";
|
|
19167
|
+
let detail = "";
|
|
19168
|
+
if (iteration) {
|
|
19169
|
+
const iterationLine = summarizeDiff(iteration);
|
|
19170
|
+
if (iterationLine) detail = ` \xB7 <span data-es-live="iteration">this iteration: ${escapeHtml3(iterationLine)}</span>`;
|
|
19171
|
+
}
|
|
19172
|
+
return [
|
|
19173
|
+
`<div data-es-live="strip">`,
|
|
19174
|
+
`<strong>Live</strong> \xB7 run #${state.runCount} \xB7 `,
|
|
19175
|
+
`<span data-es-live="session">since you started: ${escapeHtml3(sessionLine)}</span>`,
|
|
19176
|
+
detail,
|
|
19177
|
+
`</div>`
|
|
19178
|
+
].join("");
|
|
19179
|
+
}
|
|
19180
|
+
var RELOAD_CLIENT = `<script data-es-live="client">
|
|
19181
|
+
(function () {
|
|
19182
|
+
try {
|
|
19183
|
+
var es = new EventSource("/__es_reload");
|
|
19184
|
+
es.onmessage = function (e) { if (e.data === "reload") location.reload(); };
|
|
19185
|
+
} catch (err) { /* SSE unavailable: stay static */ }
|
|
19186
|
+
})();
|
|
19187
|
+
</script>`;
|
|
19188
|
+
var STRIP_STYLE = `<style data-es-live="style">
|
|
19189
|
+
[data-es-live="strip"]{position:sticky;top:0;z-index:9999;font:14px/1.5 system-ui,sans-serif;
|
|
19190
|
+
padding:8px 16px;background:#0b1021;color:#e6e9f5;border-bottom:1px solid #2a3052}
|
|
19191
|
+
[data-es-live="strip"] strong{color:#7dd3fc}
|
|
19192
|
+
</style>`;
|
|
19193
|
+
function injectLiveBits(html, stripHtml) {
|
|
19194
|
+
let out = html;
|
|
19195
|
+
const bodyOpen = out.match(/<body[^>]*>/i);
|
|
19196
|
+
if (bodyOpen) {
|
|
19197
|
+
const at = bodyOpen.index + bodyOpen[0].length;
|
|
19198
|
+
out = out.slice(0, at) + stripHtml + out.slice(at);
|
|
19199
|
+
} else {
|
|
19200
|
+
out = stripHtml + out;
|
|
19201
|
+
}
|
|
19202
|
+
const tail = STRIP_STYLE + RELOAD_CLIENT;
|
|
19203
|
+
if (/<\/body>/i.test(out)) {
|
|
19204
|
+
out = out.replace(/<\/body>/i, tail + "</body>");
|
|
19205
|
+
} else {
|
|
19206
|
+
out += tail;
|
|
19207
|
+
}
|
|
19208
|
+
return out;
|
|
19209
|
+
}
|
|
19210
|
+
var CONTENT_TYPES = {
|
|
19211
|
+
".html": "text/html; charset=utf-8",
|
|
19212
|
+
".css": "text/css; charset=utf-8",
|
|
19213
|
+
".js": "text/javascript; charset=utf-8",
|
|
19214
|
+
".json": "application/json; charset=utf-8",
|
|
19215
|
+
".svg": "image/svg+xml",
|
|
19216
|
+
".png": "image/png"
|
|
19217
|
+
};
|
|
19218
|
+
function watchInputDir(filePath, listener) {
|
|
19219
|
+
const dir = path8.dirname(filePath);
|
|
19220
|
+
const base = path8.basename(filePath);
|
|
19221
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
19222
|
+
const watcher = fs7.watch(dir, (_event, changed) => {
|
|
19223
|
+
if (!changed || changed === base) listener();
|
|
19224
|
+
});
|
|
19225
|
+
return { close: () => watcher.close() };
|
|
19226
|
+
}
|
|
19227
|
+
function plainText(html) {
|
|
19228
|
+
return html.replace(/<[^>]+>/g, "").trim();
|
|
19229
|
+
}
|
|
19230
|
+
function startServe(options, deps = {}) {
|
|
19231
|
+
const log = deps.log ?? ((message) => console.log(message));
|
|
19232
|
+
const read = deps.readFile ?? ((filePath) => fs7.readFileSync(filePath, "utf8"));
|
|
19233
|
+
const port = options.port ?? 4321;
|
|
19234
|
+
const host = options.host ?? "127.0.0.1";
|
|
19235
|
+
let state = { sessionBaseline: null, previous: null, current: null, runCount: 0 };
|
|
19236
|
+
let htmlPath = null;
|
|
19237
|
+
let stripHtml = renderDeltaStrip(state);
|
|
19238
|
+
const clients = /* @__PURE__ */ new Set();
|
|
19239
|
+
const pushReload = () => {
|
|
19240
|
+
for (const res of clients) res.write("data: reload\n\n");
|
|
19241
|
+
};
|
|
19242
|
+
const handler = (req, res) => {
|
|
19243
|
+
const url = (req.url ?? "/").split("?")[0];
|
|
19244
|
+
if (url === "/__es_reload") {
|
|
19245
|
+
res.writeHead(200, {
|
|
19246
|
+
"Content-Type": "text/event-stream",
|
|
19247
|
+
"Cache-Control": "no-cache",
|
|
19248
|
+
Connection: "keep-alive"
|
|
19249
|
+
});
|
|
19250
|
+
res.write("retry: 1000\n\n");
|
|
19251
|
+
clients.add(res);
|
|
19252
|
+
req.on("close", () => clients.delete(res));
|
|
19253
|
+
return;
|
|
19254
|
+
}
|
|
19255
|
+
if (url === "/" || url === "/index.html") {
|
|
19256
|
+
const html = htmlPath ? read(htmlPath) : "<!doctype html><html><body><h1>executable-stories</h1></body></html>";
|
|
19257
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
19258
|
+
res.end(injectLiveBits(html, stripHtml));
|
|
19259
|
+
return;
|
|
19260
|
+
}
|
|
19261
|
+
const safe = path8.normalize(url).replace(/^(\.\.[/\\])+/, "");
|
|
19262
|
+
const filePath = path8.join(path8.resolve(options.outputDir), safe);
|
|
19263
|
+
if (filePath.startsWith(path8.resolve(options.outputDir)) && fs7.existsSync(filePath)) {
|
|
19264
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
19265
|
+
res.writeHead(200, { "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream" });
|
|
19266
|
+
res.end(read(filePath));
|
|
19267
|
+
return;
|
|
19268
|
+
}
|
|
19269
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
19270
|
+
res.end("Not found");
|
|
19271
|
+
};
|
|
19272
|
+
const server = deps.createServer ? deps.createServer(handler) : http.createServer(handler);
|
|
19273
|
+
const watchOptions = {
|
|
19274
|
+
input: options.input,
|
|
19275
|
+
outputDir: options.outputDir,
|
|
19276
|
+
outputName: options.outputName,
|
|
19277
|
+
formats: options.formats,
|
|
19278
|
+
inputType: options.inputType ?? "raw",
|
|
19279
|
+
synthesize: options.synthesize !== false,
|
|
19280
|
+
debounceMs: options.debounceMs
|
|
19281
|
+
};
|
|
19282
|
+
const watchHandle = startWatch(watchOptions, {
|
|
19283
|
+
readFile: read,
|
|
19284
|
+
watch: deps.watch ?? watchInputDir,
|
|
19285
|
+
log: () => {
|
|
19286
|
+
},
|
|
19287
|
+
// serve emits its own per-run line below
|
|
19288
|
+
regenerate: async (input) => {
|
|
19289
|
+
if (!fs7.existsSync(path8.resolve(input))) return [];
|
|
19290
|
+
const { files, run } = await regenerateRun({ ...watchOptions, input }, { readFile: read });
|
|
19291
|
+
htmlPath = files.find((f) => f.endsWith(".html")) ?? htmlPath;
|
|
19292
|
+
state = advanceState(state, run);
|
|
19293
|
+
stripHtml = renderDeltaStrip(state);
|
|
19294
|
+
log(`Run #${state.runCount}: ${plainText(stripHtml)}`);
|
|
19295
|
+
pushReload();
|
|
19296
|
+
return files;
|
|
19297
|
+
}
|
|
19298
|
+
});
|
|
19299
|
+
server.listen(port, host);
|
|
19300
|
+
const address = server.address();
|
|
19301
|
+
const boundPort = typeof address === "object" && address ? address.port : port;
|
|
19302
|
+
log(`Live docs: http://${host}:${boundPort} (Ctrl+C to stop)`);
|
|
19303
|
+
return {
|
|
19304
|
+
port: boundPort,
|
|
19305
|
+
close: () => {
|
|
19306
|
+
watchHandle.close();
|
|
19307
|
+
for (const res of clients) res.end();
|
|
19308
|
+
clients.clear();
|
|
19309
|
+
server.close();
|
|
19310
|
+
}
|
|
19311
|
+
};
|
|
19312
|
+
}
|
|
19313
|
+
|
|
18969
19314
|
// src/behavior-diff.ts
|
|
18970
19315
|
function classifyStatusChange(baseline, current) {
|
|
18971
19316
|
if (baseline === void 0) return "added";
|
|
@@ -19003,7 +19348,10 @@ function diffStoryReports(baseline, current) {
|
|
|
19003
19348
|
};
|
|
19004
19349
|
});
|
|
19005
19350
|
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
19006
|
-
for (const s of scenarios)
|
|
19351
|
+
for (const s of scenarios) {
|
|
19352
|
+
if (s.kind === "renamed" || s.kind === "moved") continue;
|
|
19353
|
+
summary[s.kind] += 1;
|
|
19354
|
+
}
|
|
19007
19355
|
return { schemaVersion: "1.0", summary, scenarios };
|
|
19008
19356
|
}
|
|
19009
19357
|
|
|
@@ -19602,27 +19950,27 @@ function pickleStepArgumentToDocs(ps) {
|
|
|
19602
19950
|
}
|
|
19603
19951
|
|
|
19604
19952
|
// src/utils/git-info.ts
|
|
19605
|
-
var
|
|
19606
|
-
var
|
|
19953
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
19954
|
+
var path9 = __toESM(require("path"), 1);
|
|
19607
19955
|
function readGitSha(cwd = process.cwd()) {
|
|
19608
19956
|
const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
|
|
19609
19957
|
if (envSha) return envSha;
|
|
19610
19958
|
const gitDir = findGitDir(cwd);
|
|
19611
19959
|
if (!gitDir) return void 0;
|
|
19612
19960
|
try {
|
|
19613
|
-
const headPath =
|
|
19614
|
-
const head =
|
|
19961
|
+
const headPath = path9.join(gitDir, "HEAD");
|
|
19962
|
+
const head = fs8.readFileSync(headPath, "utf8").trim();
|
|
19615
19963
|
if (!head.startsWith("ref:")) {
|
|
19616
19964
|
return head;
|
|
19617
19965
|
}
|
|
19618
19966
|
const refPath = head.replace("ref:", "").trim();
|
|
19619
|
-
const refFile =
|
|
19620
|
-
if (
|
|
19621
|
-
return
|
|
19967
|
+
const refFile = path9.join(gitDir, refPath);
|
|
19968
|
+
if (fs8.existsSync(refFile)) {
|
|
19969
|
+
return fs8.readFileSync(refFile, "utf8").trim();
|
|
19622
19970
|
}
|
|
19623
|
-
const packedRefs =
|
|
19624
|
-
if (
|
|
19625
|
-
const content =
|
|
19971
|
+
const packedRefs = path9.join(gitDir, "packed-refs");
|
|
19972
|
+
if (fs8.existsSync(packedRefs)) {
|
|
19973
|
+
const content = fs8.readFileSync(packedRefs, "utf8");
|
|
19626
19974
|
for (const line of content.split("\n")) {
|
|
19627
19975
|
if (!line || line.startsWith("#") || line.startsWith("^")) continue;
|
|
19628
19976
|
const [sha, ref] = line.split(" ");
|
|
@@ -19637,19 +19985,19 @@ function readGitSha(cwd = process.cwd()) {
|
|
|
19637
19985
|
function findGitDir(start) {
|
|
19638
19986
|
let current = start;
|
|
19639
19987
|
while (true) {
|
|
19640
|
-
const candidate =
|
|
19641
|
-
if (
|
|
19642
|
-
const stat =
|
|
19988
|
+
const candidate = path9.join(current, ".git");
|
|
19989
|
+
if (fs8.existsSync(candidate)) {
|
|
19990
|
+
const stat = fs8.statSync(candidate);
|
|
19643
19991
|
if (stat.isFile()) {
|
|
19644
|
-
const content =
|
|
19992
|
+
const content = fs8.readFileSync(candidate, "utf8").trim();
|
|
19645
19993
|
const match = content.match(/^gitdir: (.+)$/);
|
|
19646
19994
|
if (match) {
|
|
19647
|
-
return
|
|
19995
|
+
return path9.resolve(current, match[1]);
|
|
19648
19996
|
}
|
|
19649
19997
|
}
|
|
19650
19998
|
return candidate;
|
|
19651
19999
|
}
|
|
19652
|
-
const parent =
|
|
20000
|
+
const parent = path9.dirname(current);
|
|
19653
20001
|
if (parent === current) return void 0;
|
|
19654
20002
|
current = parent;
|
|
19655
20003
|
}
|
|
@@ -19660,8 +20008,8 @@ function readBranchName(cwd = process.cwd()) {
|
|
|
19660
20008
|
const gitDir = findGitDir(cwd);
|
|
19661
20009
|
if (!gitDir) return void 0;
|
|
19662
20010
|
try {
|
|
19663
|
-
const headPath =
|
|
19664
|
-
const head =
|
|
20011
|
+
const headPath = path9.join(gitDir, "HEAD");
|
|
20012
|
+
const head = fs8.readFileSync(headPath, "utf8").trim();
|
|
19665
20013
|
if (head.startsWith("ref:")) {
|
|
19666
20014
|
const refPath = head.replace("ref:", "").trim();
|
|
19667
20015
|
const match = refPath.match(/^refs\/heads\/(.+)$/);
|
|
@@ -19698,8 +20046,8 @@ function nanosecondsToMs(ns) {
|
|
|
19698
20046
|
}
|
|
19699
20047
|
|
|
19700
20048
|
// src/utils/metadata.ts
|
|
19701
|
-
var
|
|
19702
|
-
var
|
|
20049
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
20050
|
+
var path10 = __toESM(require("path"), 1);
|
|
19703
20051
|
var versionCache = /* @__PURE__ */ new Map();
|
|
19704
20052
|
function readPackageVersion(root) {
|
|
19705
20053
|
if (versionCache.has(root)) {
|
|
@@ -19710,18 +20058,18 @@ function readPackageVersion(root) {
|
|
|
19710
20058
|
return version;
|
|
19711
20059
|
}
|
|
19712
20060
|
function findPackageVersion(startDir) {
|
|
19713
|
-
let current =
|
|
20061
|
+
let current = path10.resolve(startDir);
|
|
19714
20062
|
while (true) {
|
|
19715
|
-
const pkgPath =
|
|
20063
|
+
const pkgPath = path10.join(current, "package.json");
|
|
19716
20064
|
try {
|
|
19717
|
-
if (
|
|
19718
|
-
const raw =
|
|
20065
|
+
if (fs9.existsSync(pkgPath)) {
|
|
20066
|
+
const raw = fs9.readFileSync(pkgPath, "utf8");
|
|
19719
20067
|
const parsed = JSON.parse(raw);
|
|
19720
20068
|
return parsed.version;
|
|
19721
20069
|
}
|
|
19722
20070
|
} catch {
|
|
19723
20071
|
}
|
|
19724
|
-
const parent =
|
|
20072
|
+
const parent = path10.dirname(current);
|
|
19725
20073
|
if (parent === current) {
|
|
19726
20074
|
return void 0;
|
|
19727
20075
|
}
|
|
@@ -21050,18 +21398,18 @@ function deriveChangeType(tags) {
|
|
|
21050
21398
|
}
|
|
21051
21399
|
return "unknown";
|
|
21052
21400
|
}
|
|
21053
|
-
function extensionOf(
|
|
21054
|
-
const base =
|
|
21401
|
+
function extensionOf(path13) {
|
|
21402
|
+
const base = path13.split("/").pop() ?? path13;
|
|
21055
21403
|
const dot = base.lastIndexOf(".");
|
|
21056
21404
|
return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
21057
21405
|
}
|
|
21058
|
-
function isTestFile(
|
|
21059
|
-
return TEST_INFIX.test(
|
|
21406
|
+
function isTestFile(path13) {
|
|
21407
|
+
return TEST_INFIX.test(path13);
|
|
21060
21408
|
}
|
|
21061
|
-
function isReviewableSource(
|
|
21062
|
-
if (isTestFile(
|
|
21063
|
-
if (
|
|
21064
|
-
return CODE_EXTENSIONS.has(extensionOf(
|
|
21409
|
+
function isReviewableSource(path13) {
|
|
21410
|
+
if (isTestFile(path13)) return false;
|
|
21411
|
+
if (path13.endsWith(".d.ts")) return false;
|
|
21412
|
+
return CODE_EXTENSIONS.has(extensionOf(path13));
|
|
21065
21413
|
}
|
|
21066
21414
|
function testBaseKey(testFile) {
|
|
21067
21415
|
return testFile.replace(TEST_INFIX, "");
|
|
@@ -21165,7 +21513,7 @@ function toClaim(testCase, changedSourcePaths) {
|
|
|
21165
21513
|
const { strength, reasons } = gradeEvidence(testCase, audience);
|
|
21166
21514
|
const key = testBaseKey(testCase.sourceFile);
|
|
21167
21515
|
const coversFiles = changedSourcePaths.filter(
|
|
21168
|
-
(
|
|
21516
|
+
(path13) => sourceBaseKey(path13) === key
|
|
21169
21517
|
);
|
|
21170
21518
|
return {
|
|
21171
21519
|
id: testCase.id,
|
|
@@ -21414,7 +21762,7 @@ var ReviewMarkdownFormatter = class {
|
|
|
21414
21762
|
};
|
|
21415
21763
|
|
|
21416
21764
|
// src/formatters/review-html.ts
|
|
21417
|
-
function
|
|
21765
|
+
function escapeHtml4(value) {
|
|
21418
21766
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
21419
21767
|
}
|
|
21420
21768
|
var STRENGTH_LABEL = {
|
|
@@ -21436,22 +21784,22 @@ function statusIcon3(status) {
|
|
|
21436
21784
|
}
|
|
21437
21785
|
}
|
|
21438
21786
|
function formatStep3(step) {
|
|
21439
|
-
return `<li><strong>${
|
|
21787
|
+
return `<li><strong>${escapeHtml4(step.keyword)}</strong> ${escapeHtml4(step.text)}</li>`;
|
|
21440
21788
|
}
|
|
21441
21789
|
function inlineDoc(doc) {
|
|
21442
21790
|
switch (doc.kind) {
|
|
21443
21791
|
case "note":
|
|
21444
|
-
return
|
|
21792
|
+
return escapeHtml4(doc.text);
|
|
21445
21793
|
case "section":
|
|
21446
|
-
return `<strong>${
|
|
21794
|
+
return `<strong>${escapeHtml4(doc.title)}</strong>: ${escapeHtml4(doc.markdown)}`;
|
|
21447
21795
|
case "kv":
|
|
21448
|
-
return `${
|
|
21796
|
+
return `${escapeHtml4(doc.label)}: ${escapeHtml4(String(doc.value))}`;
|
|
21449
21797
|
case "code":
|
|
21450
|
-
return `${
|
|
21798
|
+
return `${escapeHtml4(doc.label)}: <code>${escapeHtml4(doc.content)}</code>`;
|
|
21451
21799
|
case "link":
|
|
21452
|
-
return `${
|
|
21800
|
+
return `${escapeHtml4(doc.label)}: ${escapeHtml4(doc.url)}`;
|
|
21453
21801
|
default:
|
|
21454
|
-
return
|
|
21802
|
+
return escapeHtml4(doc.kind);
|
|
21455
21803
|
}
|
|
21456
21804
|
}
|
|
21457
21805
|
function renderEvidenceArtifacts(testCase) {
|
|
@@ -21459,7 +21807,7 @@ function renderEvidenceArtifacts(testCase) {
|
|
|
21459
21807
|
for (const att of testCase.attachments) {
|
|
21460
21808
|
if (att.mediaType.startsWith("image/") && att.contentEncoding === "BASE64") {
|
|
21461
21809
|
parts.push(
|
|
21462
|
-
`<img class="shot" alt="${
|
|
21810
|
+
`<img class="shot" alt="${escapeHtml4(att.name)}" src="data:${escapeHtml4(att.mediaType)};base64,${att.body}" />`
|
|
21463
21811
|
);
|
|
21464
21812
|
}
|
|
21465
21813
|
}
|
|
@@ -21474,22 +21822,22 @@ function renderTicketPills(claim) {
|
|
|
21474
21822
|
const tickets = claim.testCase.story.tickets ?? [];
|
|
21475
21823
|
if (tickets.length === 0) return "";
|
|
21476
21824
|
return `<div class="ticket-row">${tickets.map((ticket) => {
|
|
21477
|
-
const label =
|
|
21825
|
+
const label = escapeHtml4(ticket.id);
|
|
21478
21826
|
if (ticket.url) {
|
|
21479
|
-
return `<a class="ticket-pill" href="${
|
|
21827
|
+
return `<a class="ticket-pill" href="${escapeHtml4(ticket.url)}" target="_blank" rel="noopener noreferrer">${label}</a>`;
|
|
21480
21828
|
}
|
|
21481
21829
|
return `<span class="ticket-pill">${label}</span>`;
|
|
21482
21830
|
}).join("")}</div>`;
|
|
21483
21831
|
}
|
|
21484
21832
|
function renderClaimCard(claim) {
|
|
21485
21833
|
const ticketSearch = (claim.testCase.story.tickets ?? []).map((ticket) => ticket.id).join(" ");
|
|
21486
|
-
const search =
|
|
21834
|
+
const search = escapeHtml4(
|
|
21487
21835
|
`${claim.scenario} ${claim.sourceFile} ${claim.changeType} ${claim.audience} ${claim.strength} ${ticketSearch}`
|
|
21488
21836
|
).toLowerCase();
|
|
21489
21837
|
const steps = claim.testCase.story.steps.length > 0 ? `<ul class="step-list">${claim.testCase.story.steps.map(formatStep3).join("")}</ul>` : "";
|
|
21490
|
-
const reasons = `<ul class="reasons">${claim.strengthReasons.map((r) => `<li>${
|
|
21491
|
-
const intent = claim.intent !== void 0 ? `<div class="intent"><span class="intent-label">Why</span> ${
|
|
21492
|
-
const covers = claim.coversFiles.length > 0 ? `<p class="covers">Covers ${claim.coversFiles.map((f) => `<code>${
|
|
21838
|
+
const reasons = `<ul class="reasons">${claim.strengthReasons.map((r) => `<li>${escapeHtml4(r)}</li>`).join("")}</ul>`;
|
|
21839
|
+
const intent = claim.intent !== void 0 ? `<div class="intent"><span class="intent-label">Why</span> ${escapeHtml4(claim.intent)}</div>` : "";
|
|
21840
|
+
const covers = claim.coversFiles.length > 0 ? `<p class="covers">Covers ${claim.coversFiles.map((f) => `<code>${escapeHtml4(f)}</code>`).join(", ")}</p>` : "";
|
|
21493
21841
|
const docs = (claim.testCase.story.docs ?? []).filter(
|
|
21494
21842
|
(d) => d.kind === "section" || d.kind === "note"
|
|
21495
21843
|
);
|
|
@@ -21499,9 +21847,9 @@ function renderClaimCard(claim) {
|
|
|
21499
21847
|
<header class="claim-header">
|
|
21500
21848
|
<div>
|
|
21501
21849
|
<span class="strength-badge strength-${claim.strength}">${STRENGTH_LABEL[claim.strength]}</span>
|
|
21502
|
-
${claim.changeType !== "unknown" ? `<span class="change-pill">${
|
|
21503
|
-
<h3>${statusIcon3(claim.status)} ${
|
|
21504
|
-
<p class="source">${
|
|
21850
|
+
${claim.changeType !== "unknown" ? `<span class="change-pill">${escapeHtml4(claim.changeType)}</span>` : ""}
|
|
21851
|
+
<h3>${statusIcon3(claim.status)} ${escapeHtml4(claim.scenario)}</h3>
|
|
21852
|
+
<p class="source">${escapeHtml4(`${claim.sourceFile}:${claim.sourceLine}`)}</p>
|
|
21505
21853
|
${renderTicketPills(claim)}
|
|
21506
21854
|
</div>
|
|
21507
21855
|
</header>
|
|
@@ -21516,18 +21864,18 @@ function renderClaimCard(claim) {
|
|
|
21516
21864
|
</article>`;
|
|
21517
21865
|
}
|
|
21518
21866
|
function renderChangedFileRow(file) {
|
|
21519
|
-
const claims = file.claims.length > 0 ? file.claims.map((c) => `${
|
|
21867
|
+
const claims = file.claims.length > 0 ? file.claims.map((c) => `${escapeHtml4(c.scenario)} <em>(${c.strength})</em>`).join(", ") : "\u2014";
|
|
21520
21868
|
return `<tr data-band="${file.band}">
|
|
21521
21869
|
<td><span class="band-dot band-${file.band}"></span></td>
|
|
21522
|
-
<td><code>${
|
|
21523
|
-
<td>${
|
|
21870
|
+
<td><code>${escapeHtml4(file.path)}</code></td>
|
|
21871
|
+
<td>${escapeHtml4(file.changeKind)}</td>
|
|
21524
21872
|
<td>${claims}</td>
|
|
21525
21873
|
</tr>`;
|
|
21526
21874
|
}
|
|
21527
21875
|
function renderAudienceSection2(title, claims) {
|
|
21528
21876
|
if (claims.length === 0) return "";
|
|
21529
21877
|
return `<section class="audience-section">
|
|
21530
|
-
<h2>${
|
|
21878
|
+
<h2>${escapeHtml4(title)} <span class="count">${claims.length}</span></h2>
|
|
21531
21879
|
<div class="claim-list">${claims.map(renderClaimCard).join("\n")}</div>
|
|
21532
21880
|
</section>`;
|
|
21533
21881
|
}
|
|
@@ -21617,13 +21965,13 @@ var ReviewHtmlFormatter = class {
|
|
|
21617
21965
|
const themeInitJs = this.darkMode ? `${JS_THEME_TOGGLE2}
|
|
21618
21966
|
applyTheme(getEffectiveTheme());` : "";
|
|
21619
21967
|
const themeAttr = this.darkMode ? ' data-theme="light"' : "";
|
|
21620
|
-
const refsLine = context.baseRef || context.headRef ? `<p class="subtle">Comparing ${
|
|
21968
|
+
const refsLine = context.baseRef || context.headRef ? `<p class="subtle">Comparing ${escapeHtml4(context.baseRef ?? "base")} \u2192 ${escapeHtml4(context.headRef ?? "head")}</p>` : "";
|
|
21621
21969
|
return `<!doctype html>
|
|
21622
21970
|
<html lang="en"${themeAttr}>
|
|
21623
21971
|
<head>
|
|
21624
21972
|
<meta charset="utf-8" />
|
|
21625
21973
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
21626
|
-
<title>${
|
|
21974
|
+
<title>${escapeHtml4(this.title)}</title>
|
|
21627
21975
|
<style>
|
|
21628
21976
|
${this.theme.css}
|
|
21629
21977
|
${REVIEW_CSS}
|
|
@@ -21633,7 +21981,7 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
21633
21981
|
<main>
|
|
21634
21982
|
<div class="hero-card card">
|
|
21635
21983
|
<div class="review-header">
|
|
21636
|
-
<h1>${
|
|
21984
|
+
<h1>${escapeHtml4(this.title)}</h1>
|
|
21637
21985
|
${themeToggleHtml}
|
|
21638
21986
|
</div>
|
|
21639
21987
|
${refsLine}
|
|
@@ -21648,7 +21996,7 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
21648
21996
|
</section>
|
|
21649
21997
|
<section class="card priority-banner">
|
|
21650
21998
|
<h2>Review priority</h2>
|
|
21651
|
-
<p class="subtle">${
|
|
21999
|
+
<p class="subtle">${escapeHtml4(priority)}</p>
|
|
21652
22000
|
</section>
|
|
21653
22001
|
${changedFilesPanel}
|
|
21654
22002
|
<section class="toolbar">
|
|
@@ -21696,8 +22044,8 @@ applyTheme(getEffectiveTheme());` : "";
|
|
|
21696
22044
|
};
|
|
21697
22045
|
|
|
21698
22046
|
// src/deploy/ledger.ts
|
|
21699
|
-
var
|
|
21700
|
-
var
|
|
22047
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
22048
|
+
var path11 = __toESM(require("path"), 1);
|
|
21701
22049
|
function createEmptyLedger() {
|
|
21702
22050
|
return {
|
|
21703
22051
|
deployments: [],
|
|
@@ -21705,12 +22053,12 @@ function createEmptyLedger() {
|
|
|
21705
22053
|
};
|
|
21706
22054
|
}
|
|
21707
22055
|
function loadLedger(ledgerPath) {
|
|
21708
|
-
const resolved =
|
|
21709
|
-
if (!
|
|
22056
|
+
const resolved = path11.resolve(ledgerPath);
|
|
22057
|
+
if (!fs10.existsSync(resolved)) {
|
|
21710
22058
|
return createEmptyLedger();
|
|
21711
22059
|
}
|
|
21712
22060
|
try {
|
|
21713
|
-
const raw = JSON.parse(
|
|
22061
|
+
const raw = JSON.parse(fs10.readFileSync(resolved, "utf8"));
|
|
21714
22062
|
if (raw.schemaVersion !== 1) {
|
|
21715
22063
|
throw new Error(`Unsupported ledger schemaVersion: ${raw.schemaVersion}`);
|
|
21716
22064
|
}
|
|
@@ -21721,10 +22069,10 @@ function loadLedger(ledgerPath) {
|
|
|
21721
22069
|
}
|
|
21722
22070
|
}
|
|
21723
22071
|
function saveLedger(ledger, ledgerPath) {
|
|
21724
|
-
const resolved =
|
|
21725
|
-
const dir =
|
|
21726
|
-
|
|
21727
|
-
|
|
22072
|
+
const resolved = path11.resolve(ledgerPath);
|
|
22073
|
+
const dir = path11.dirname(resolved);
|
|
22074
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
22075
|
+
fs10.writeFileSync(resolved, JSON.stringify(ledger, null, 2), "utf8");
|
|
21728
22076
|
}
|
|
21729
22077
|
function getLatestDeployment(ledger, environment) {
|
|
21730
22078
|
return [...ledger.deployments].reverse().find((d) => d.environment === environment);
|
|
@@ -21844,11 +22192,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
21844
22192
|
const ext = FORMAT_EXTENSIONS[format];
|
|
21845
22193
|
const effectiveName = outputName + (outputNameSuffix ?? "");
|
|
21846
22194
|
if (mode === "aggregated") {
|
|
21847
|
-
return toPosix(
|
|
22195
|
+
return toPosix(path12.join(baseOutputDir, joinNameAndExt(effectiveName, ext)));
|
|
21848
22196
|
}
|
|
21849
22197
|
const normalizedSource = toPosix(sourceFile);
|
|
21850
|
-
const dirOfSource =
|
|
21851
|
-
let baseName =
|
|
22198
|
+
const dirOfSource = path12.posix.dirname(normalizedSource);
|
|
22199
|
+
let baseName = path12.posix.basename(normalizedSource);
|
|
21852
22200
|
for (const testExt of TEST_EXTENSIONS) {
|
|
21853
22201
|
if (baseName.endsWith(testExt)) {
|
|
21854
22202
|
baseName = baseName.slice(0, -testExt.length);
|
|
@@ -21857,12 +22205,12 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
|
|
|
21857
22205
|
}
|
|
21858
22206
|
const fileName = `${baseName}.${effectiveName}${ext}`;
|
|
21859
22207
|
if (colocatedStyle === "adjacent") {
|
|
21860
|
-
return toPosix(
|
|
22208
|
+
return toPosix(path12.posix.join(dirOfSource, fileName));
|
|
21861
22209
|
}
|
|
21862
22210
|
if (colocatedStyle === "flat") {
|
|
21863
|
-
return toPosix(
|
|
22211
|
+
return toPosix(path12.posix.join(baseOutputDir, `${cleanTestStem(normalizedSource)}${ext}`));
|
|
21864
22212
|
}
|
|
21865
|
-
return toPosix(
|
|
22213
|
+
return toPosix(path12.posix.join(baseOutputDir, dirOfSource, fileName));
|
|
21866
22214
|
}
|
|
21867
22215
|
function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
|
|
21868
22216
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -22075,8 +22423,8 @@ var ReportGenerator = class {
|
|
|
22075
22423
|
if (astroPaths) {
|
|
22076
22424
|
for (const mdPath of astroPaths) {
|
|
22077
22425
|
const content = await fsPromises.readFile(mdPath, "utf8");
|
|
22078
|
-
const mdDir =
|
|
22079
|
-
const assetsDir =
|
|
22426
|
+
const mdDir = path12.dirname(mdPath);
|
|
22427
|
+
const assetsDir = path12.resolve(this.options.astro.assetsDir);
|
|
22080
22428
|
const result = copyMarkdownAssets({
|
|
22081
22429
|
markdown: content,
|
|
22082
22430
|
markdownDir: mdDir,
|
|
@@ -22107,9 +22455,9 @@ var ReportGenerator = class {
|
|
|
22107
22455
|
if (groups.size === 0 && this.options.output.mode === "aggregated") {
|
|
22108
22456
|
const ext = FORMAT_EXTENSIONS[format];
|
|
22109
22457
|
const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
|
|
22110
|
-
const outputPath = toPosix(
|
|
22458
|
+
const outputPath = toPosix(path12.join(this.options.outputDir, joinNameAndExt(effectiveName, ext)));
|
|
22111
22459
|
const content = await this.formatContent(run, format);
|
|
22112
|
-
const dir =
|
|
22460
|
+
const dir = path12.dirname(outputPath);
|
|
22113
22461
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
22114
22462
|
await this.deps.writeFile(outputPath, content);
|
|
22115
22463
|
return [outputPath];
|
|
@@ -22121,7 +22469,7 @@ var ReportGenerator = class {
|
|
|
22121
22469
|
testCases
|
|
22122
22470
|
};
|
|
22123
22471
|
const content = await this.formatContent(groupRun, format);
|
|
22124
|
-
const dir =
|
|
22472
|
+
const dir = path12.dirname(outputPath);
|
|
22125
22473
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
22126
22474
|
await this.deps.writeFile(outputPath, content);
|
|
22127
22475
|
writtenPaths.push(outputPath);
|
|
@@ -22273,7 +22621,7 @@ async function generateRunComparison(args) {
|
|
|
22273
22621
|
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
22274
22622
|
for (const format of args.formats) {
|
|
22275
22623
|
const ext = format === "html" ? ".html" : ".md";
|
|
22276
|
-
const outputPath = toPosix(
|
|
22624
|
+
const outputPath = toPosix(path12.join(outputDir, `${outputName}${ext}`));
|
|
22277
22625
|
const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
|
|
22278
22626
|
await fsPromises.writeFile(outputPath, content, "utf8");
|
|
22279
22627
|
files.push(outputPath);
|
|
@@ -22323,6 +22671,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
22323
22671
|
adaptJestRun,
|
|
22324
22672
|
adaptPlaywrightRun,
|
|
22325
22673
|
adaptVitestRun,
|
|
22674
|
+
advanceState,
|
|
22326
22675
|
assertValidRun,
|
|
22327
22676
|
buildCheck,
|
|
22328
22677
|
buildGoal,
|
|
@@ -22335,6 +22684,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
22335
22684
|
canonicalizeRun,
|
|
22336
22685
|
classifyStatusChange,
|
|
22337
22686
|
clearVersionCache,
|
|
22687
|
+
computeDeltas,
|
|
22338
22688
|
computeTestMetrics,
|
|
22339
22689
|
copyMarkdownAssets,
|
|
22340
22690
|
createPrCommentSummary,
|
|
@@ -22357,6 +22707,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
22357
22707
|
getEnvironmentDrift,
|
|
22358
22708
|
gradeEvidence,
|
|
22359
22709
|
hasSufficientHistory,
|
|
22710
|
+
injectLiveBits,
|
|
22360
22711
|
isReviewableSource,
|
|
22361
22712
|
isTestFile,
|
|
22362
22713
|
joinNameAndExt,
|
|
@@ -22378,7 +22729,9 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
22378
22729
|
readPackageVersion,
|
|
22379
22730
|
recordDeployment,
|
|
22380
22731
|
regenerateArtifacts,
|
|
22732
|
+
regenerateRun,
|
|
22381
22733
|
renderCheck,
|
|
22734
|
+
renderDeltaStrip,
|
|
22382
22735
|
renderGoal,
|
|
22383
22736
|
renderTriage,
|
|
22384
22737
|
resolveAttachment,
|
|
@@ -22394,6 +22747,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
22394
22747
|
sendWebhookNotification,
|
|
22395
22748
|
signBody,
|
|
22396
22749
|
slugify,
|
|
22750
|
+
startServe,
|
|
22397
22751
|
startWatch,
|
|
22398
22752
|
stripAnsi,
|
|
22399
22753
|
toBehaviorManifest,
|