executable-stories-formatters 0.11.0 → 0.11.2

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/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(src_exports, {
44
44
  MIN_METRIC_SAMPLES: () => MIN_METRIC_SAMPLES,
45
45
  MIN_PERF_SAMPLES: () => MIN_PERF_SAMPLES,
46
46
  MarkdownFormatter: () => MarkdownFormatter,
47
+ ReleaseManifestFormatter: () => ReleaseManifestFormatter,
47
48
  ReportGenerator: () => ReportGenerator,
48
49
  ReviewHtmlFormatter: () => ReviewHtmlFormatter,
49
50
  ReviewMarkdownFormatter: () => ReviewMarkdownFormatter,
@@ -83,6 +84,8 @@ __export(src_exports, {
83
84
  generateTestCaseId: () => generateTestCaseId,
84
85
  getAvailableThemes: () => getAvailableThemes,
85
86
  getCssOnlyThemes: () => getCssOnlyThemes,
87
+ getDeploymentStatus: () => getDeploymentStatus,
88
+ getEnvironmentDrift: () => getEnvironmentDrift,
86
89
  gradeEvidence: () => gradeEvidence,
87
90
  hasSufficientHistory: () => hasSufficientHistory,
88
91
  isReviewableSource: () => isReviewableSource,
@@ -104,6 +107,7 @@ __export(src_exports, {
104
107
  readBranchName: () => readBranchName,
105
108
  readGitSha: () => readGitSha,
106
109
  readPackageVersion: () => readPackageVersion,
110
+ recordDeployment: () => recordDeployment,
107
111
  regenerateArtifacts: () => regenerateArtifacts,
108
112
  resolveAttachment: () => resolveAttachment,
109
113
  resolveAttachments: () => resolveAttachments,
@@ -123,6 +127,7 @@ __export(src_exports, {
123
127
  toBehaviorManifest: () => toBehaviorManifest,
124
128
  toCIInfo: () => toCIInfo,
125
129
  toRawCIInfo: () => toRawCIInfo,
130
+ toReleaseManifest: () => toReleaseManifest,
126
131
  toScenarioIndex: () => toScenarioIndex,
127
132
  toStoryReport: () => toStoryReport,
128
133
  tryGetActiveOtelContext: () => tryGetActiveOtelContext,
@@ -130,8 +135,8 @@ __export(src_exports, {
130
135
  validateCanonicalRun: () => validateCanonicalRun
131
136
  });
132
137
  module.exports = __toCommonJS(src_exports);
133
- var fs9 = require("fs");
134
- var path10 = __toESM(require("path"), 1);
138
+ var fs10 = require("fs");
139
+ var path11 = __toESM(require("path"), 1);
135
140
  var fsPromises = __toESM(require("fs/promises"), 1);
136
141
 
137
142
  // src/converters/acl/status.ts
@@ -1020,23 +1025,23 @@ function buildFeature(relSourceFile, group) {
1020
1025
  function ensureUniqueFeatureIds(features) {
1021
1026
  const seen = /* @__PURE__ */ new Map();
1022
1027
  for (const f of features) {
1023
- const count = seen.get(f.id) ?? 0;
1024
- if (count > 0) f.id = `${f.id}-${count + 1}`;
1025
- seen.set(f.id, count + 1);
1028
+ const count2 = seen.get(f.id) ?? 0;
1029
+ if (count2 > 0) f.id = `${f.id}-${count2 + 1}`;
1030
+ seen.set(f.id, count2 + 1);
1026
1031
  }
1027
1032
  }
1028
1033
  function ensureUniqueScenarioIds(feature) {
1029
1034
  const seen = /* @__PURE__ */ new Map();
1030
1035
  for (const s of feature.scenarios) {
1031
- const count = seen.get(s.id) ?? 0;
1032
- if (count > 0) {
1033
- const newId = `${s.id}-${count + 1}`;
1036
+ const count2 = seen.get(s.id) ?? 0;
1037
+ if (count2 > 0) {
1038
+ const newId = `${s.id}-${count2 + 1}`;
1034
1039
  for (const step of s.steps) {
1035
1040
  step.id = step.id.replace(s.id, newId);
1036
1041
  }
1037
1042
  s.id = newId;
1038
1043
  }
1039
- seen.set(s.id, count + 1);
1044
+ seen.set(s.id, count2 + 1);
1040
1045
  }
1041
1046
  }
1042
1047
  function toStoryReport(run) {
@@ -15568,6 +15573,88 @@ function groupBy6(items, keyFn) {
15568
15573
  return map;
15569
15574
  }
15570
15575
 
15576
+ // src/formatters/release-manifest.ts
15577
+ var import_node_crypto2 = require("crypto");
15578
+ var ReleaseManifestFormatter = class {
15579
+ format(run) {
15580
+ const manifest = toReleaseManifest(run);
15581
+ const lines = [];
15582
+ lines.push("# Release Manifest");
15583
+ lines.push("");
15584
+ lines.push(`Generated: ${manifest.generatedAt}`);
15585
+ lines.push(`Run: ${manifest.run.startedAt} to ${manifest.run.finishedAt}`);
15586
+ if (manifest.run.branch) lines.push(`Branch: ${manifest.run.branch}`);
15587
+ if (manifest.run.gitSha) lines.push(`Commit: ${manifest.run.gitSha}`);
15588
+ lines.push(`Tested-together hash: \`${manifest.testedTogetherHash}\``);
15589
+ lines.push("");
15590
+ lines.push("| Scenarios | Passed | Failed | Skipped | Pending |");
15591
+ lines.push("| ---: | ---: | ---: | ---: | ---: |");
15592
+ lines.push(`| ${manifest.run.total} | ${manifest.run.passed} | ${manifest.run.failed} | ${manifest.run.skipped} | ${manifest.run.pending} |`);
15593
+ lines.push("");
15594
+ lines.push("## Scenarios");
15595
+ lines.push("");
15596
+ lines.push("| Status | Scenario | Source | Tags |");
15597
+ lines.push("| --- | --- | --- | --- |");
15598
+ for (const scenario of manifest.scenarios) {
15599
+ const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
15600
+ const tags = scenario.tags.length > 0 ? scenario.tags.map((tag) => `\`${tag}\``).join(", ") : "";
15601
+ lines.push(`| ${renderStatus(scenario.status)} | ${escapePipe(scenario.title)} | \`${source}\` | ${tags} |`);
15602
+ }
15603
+ return lines.join("\n");
15604
+ }
15605
+ };
15606
+ function toReleaseManifest(run) {
15607
+ const scenarios = [...run.testCases].sort((a, b) => a.id.localeCompare(b.id)).map((tc) => ({
15608
+ id: tc.id,
15609
+ title: tc.story.scenario,
15610
+ status: tc.status,
15611
+ sourceFile: tc.sourceFile,
15612
+ sourceLine: tc.sourceLine,
15613
+ tags: tc.tags
15614
+ }));
15615
+ const fingerprint = scenarios.map((scenario) => `${scenario.id}:${scenario.status}`).join("\n");
15616
+ return {
15617
+ schemaVersion: "1.0",
15618
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15619
+ run: {
15620
+ startedAt: new Date(run.startedAtMs).toISOString(),
15621
+ finishedAt: new Date(run.finishedAtMs).toISOString(),
15622
+ gitSha: run.gitSha,
15623
+ branch: getBranch(run),
15624
+ total: run.testCases.length,
15625
+ passed: count(run.testCases, "passed"),
15626
+ failed: count(run.testCases, "failed"),
15627
+ skipped: count(run.testCases, "skipped"),
15628
+ pending: count(run.testCases, "pending")
15629
+ },
15630
+ testedTogetherHash: (0, import_node_crypto2.createHash)("sha256").update(fingerprint).digest("hex"),
15631
+ scenarios
15632
+ };
15633
+ }
15634
+ function getBranch(run) {
15635
+ return run.ci?.branch;
15636
+ }
15637
+ function count(testCases, status) {
15638
+ return testCases.filter((tc) => tc.status === status).length;
15639
+ }
15640
+ function renderStatus(status) {
15641
+ switch (status) {
15642
+ case "passed":
15643
+ return "passed";
15644
+ case "failed":
15645
+ return "failed";
15646
+ case "skipped":
15647
+ return "skipped";
15648
+ case "pending":
15649
+ return "pending";
15650
+ default:
15651
+ return status;
15652
+ }
15653
+ }
15654
+ function escapePipe(value) {
15655
+ return value.replace(/\|/g, "\\|");
15656
+ }
15657
+
15571
15658
  // src/formatters/cucumber-messages/synthesize-feature.ts
15572
15659
  function extractFeatureName(testCases, uri) {
15573
15660
  for (const tc of testCases) {
@@ -15634,7 +15721,7 @@ function synthesizeFeature(uri, testCases) {
15634
15721
  }
15635
15722
 
15636
15723
  // src/utils/cucumber-messages.ts
15637
- var import_node_crypto2 = require("crypto");
15724
+ var import_node_crypto3 = require("crypto");
15638
15725
  function msToTimestamp(ms) {
15639
15726
  const seconds = Math.floor(ms / 1e3);
15640
15727
  const nanos = Math.round(ms % 1e3 * 1e6);
@@ -15700,7 +15787,7 @@ function statusToCucumberStatus(status) {
15700
15787
  }
15701
15788
  function deterministicId(kind, salt, ...parts) {
15702
15789
  const input = [salt, kind, ...parts].join("::");
15703
- return (0, import_node_crypto2.createHash)("sha1").update(input).digest("hex").slice(0, 36);
15790
+ return (0, import_node_crypto3.createHash)("sha1").update(input).digest("hex").slice(0, 36);
15704
15791
  }
15705
15792
 
15706
15793
  // src/formatters/cucumber-messages/build-gherkin-document.ts
@@ -16188,8 +16275,8 @@ function extractDocAttachments(step) {
16188
16275
  }
16189
16276
  return attachments;
16190
16277
  }
16191
- function guessMediaType(path11) {
16192
- const lower = path11.toLowerCase();
16278
+ function guessMediaType(path12) {
16279
+ const lower = path12.toLowerCase();
16193
16280
  if (lower.endsWith(".png")) return "image/png";
16194
16281
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
16195
16282
  if (lower.endsWith(".gif")) return "image/gif";
@@ -16330,11 +16417,11 @@ var CucumberHtmlFormatter = class {
16330
16417
  for (const envelope of envelopes) {
16331
16418
  const accepted = htmlStream.write(envelope);
16332
16419
  if (!accepted) {
16333
- await new Promise((resolve8) => htmlStream.once("drain", resolve8));
16420
+ await new Promise((resolve9) => htmlStream.once("drain", resolve9));
16334
16421
  }
16335
16422
  }
16336
- await new Promise((resolve8, reject) => {
16337
- collector.on("finish", resolve8);
16423
+ await new Promise((resolve9, reject) => {
16424
+ collector.on("finish", resolve9);
16338
16425
  collector.on("error", reject);
16339
16426
  htmlStream.end();
16340
16427
  });
@@ -17398,8 +17485,8 @@ ${body}`;
17398
17485
  }
17399
17486
  buildFrontmatter(run) {
17400
17487
  const badge = _AstroFormatter.computeBadge(run.testCases);
17401
- const count = run.testCases.length;
17402
- const description = `${count} scenario${count !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
17488
+ const count2 = run.testCases.length;
17489
+ const description = `${count2} scenario${count2 !== 1 ? "s" : ""} \u2014 ${badge.text.toLowerCase()}`;
17403
17490
  const lines = [
17404
17491
  "---",
17405
17492
  `title: ${yamlScalar(this.deriveTitle(run))}`,
@@ -18397,14 +18484,14 @@ ${result.errors.join("\n")}`);
18397
18484
  }
18398
18485
 
18399
18486
  // src/coverage-index.ts
18400
- function normalizePath(path11) {
18401
- return path11.replace(/^\.\//, "");
18487
+ function normalizePath(path12) {
18488
+ return path12.replace(/^\.\//, "");
18402
18489
  }
18403
18490
  function scenariosCoveringPaths(index, paths) {
18404
18491
  const queries = paths.map(normalizePath);
18405
18492
  return index.scenarios.filter(
18406
18493
  (scenario) => scenario.covers.some(
18407
- (glob) => queries.some((path11) => matchesPattern(normalizePath(glob), path11))
18494
+ (glob) => queries.some((path12) => matchesPattern(normalizePath(glob), path12))
18408
18495
  )
18409
18496
  );
18410
18497
  }
@@ -19716,7 +19803,7 @@ async function sendTeamsNotification(args, deps) {
19716
19803
  }
19717
19804
 
19718
19805
  // src/notifiers/hmac.ts
19719
- var import_node_crypto3 = require("crypto");
19806
+ var import_node_crypto4 = require("crypto");
19720
19807
  function signBody(args) {
19721
19808
  let input;
19722
19809
  let timestamp;
@@ -19726,7 +19813,7 @@ function signBody(args) {
19726
19813
  } else {
19727
19814
  input = args.body;
19728
19815
  }
19729
- const hex = (0, import_node_crypto3.createHmac)("sha256", args.secret).update(input, "utf8").digest("hex");
19816
+ const hex = (0, import_node_crypto4.createHmac)("sha256", args.secret).update(input, "utf8").digest("hex");
19730
19817
  return {
19731
19818
  signature: `sha256=${hex}`,
19732
19819
  timestamp
@@ -20322,18 +20409,18 @@ function deriveChangeType(tags) {
20322
20409
  }
20323
20410
  return "unknown";
20324
20411
  }
20325
- function extensionOf(path11) {
20326
- const base = path11.split("/").pop() ?? path11;
20412
+ function extensionOf(path12) {
20413
+ const base = path12.split("/").pop() ?? path12;
20327
20414
  const dot = base.lastIndexOf(".");
20328
20415
  return dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
20329
20416
  }
20330
- function isTestFile(path11) {
20331
- return TEST_INFIX.test(path11);
20417
+ function isTestFile(path12) {
20418
+ return TEST_INFIX.test(path12);
20332
20419
  }
20333
- function isReviewableSource(path11) {
20334
- if (isTestFile(path11)) return false;
20335
- if (path11.endsWith(".d.ts")) return false;
20336
- return CODE_EXTENSIONS.has(extensionOf(path11));
20420
+ function isReviewableSource(path12) {
20421
+ if (isTestFile(path12)) return false;
20422
+ if (path12.endsWith(".d.ts")) return false;
20423
+ return CODE_EXTENSIONS.has(extensionOf(path12));
20337
20424
  }
20338
20425
  function testBaseKey(testFile) {
20339
20426
  return testFile.replace(TEST_INFIX, "");
@@ -20437,7 +20524,7 @@ function toClaim(testCase, changedSourcePaths) {
20437
20524
  const { strength, reasons } = gradeEvidence(testCase, audience);
20438
20525
  const key = testBaseKey(testCase.sourceFile);
20439
20526
  const coversFiles = changedSourcePaths.filter(
20440
- (path11) => sourceBaseKey(path11) === key
20527
+ (path12) => sourceBaseKey(path12) === key
20441
20528
  );
20442
20529
  return {
20443
20530
  id: testCase.id,
@@ -20967,11 +21054,116 @@ applyTheme(getEffectiveTheme());` : "";
20967
21054
  }
20968
21055
  };
20969
21056
 
21057
+ // src/deploy/ledger.ts
21058
+ var fs9 = __toESM(require("fs"), 1);
21059
+ var path10 = __toESM(require("path"), 1);
21060
+ function createEmptyLedger() {
21061
+ return {
21062
+ deployments: [],
21063
+ schemaVersion: 1
21064
+ };
21065
+ }
21066
+ function loadLedger(ledgerPath) {
21067
+ const resolved = path10.resolve(ledgerPath);
21068
+ if (!fs9.existsSync(resolved)) {
21069
+ return createEmptyLedger();
21070
+ }
21071
+ try {
21072
+ const raw = JSON.parse(fs9.readFileSync(resolved, "utf8"));
21073
+ if (raw.schemaVersion !== 1) {
21074
+ throw new Error(`Unsupported ledger schemaVersion: ${raw.schemaVersion}`);
21075
+ }
21076
+ return raw;
21077
+ } catch (err) {
21078
+ const msg = err instanceof Error ? err.message : String(err);
21079
+ throw new Error(`Failed to load deployment ledger at ${resolved}: ${msg}`);
21080
+ }
21081
+ }
21082
+ function saveLedger(ledger, ledgerPath) {
21083
+ const resolved = path10.resolve(ledgerPath);
21084
+ const dir = path10.dirname(resolved);
21085
+ fs9.mkdirSync(dir, { recursive: true });
21086
+ fs9.writeFileSync(resolved, JSON.stringify(ledger, null, 2), "utf8");
21087
+ }
21088
+ function getLatestDeployment(ledger, environment) {
21089
+ return [...ledger.deployments].reverse().find((d) => d.environment === environment);
21090
+ }
21091
+
21092
+ // src/deploy/index.ts
21093
+ function recordDeployment(args) {
21094
+ const ledger = loadLedger(args.ledgerPath);
21095
+ const previous = getLatestDeployment(ledger, args.environment);
21096
+ const entry = {
21097
+ environment: args.environment,
21098
+ tag: args.tag,
21099
+ sha: args.run.gitSha,
21100
+ runFile: args.runFilePath,
21101
+ scenarioIds: args.run.testCases.map((tc) => tc.id),
21102
+ scenarioStatuses: Object.fromEntries(args.run.testCases.map((tc) => [tc.id, tc.status])),
21103
+ timestamp: new Date(args.run.finishedAtMs).toISOString(),
21104
+ summary: countStatuses(args.run)
21105
+ };
21106
+ ledger.deployments.push(entry);
21107
+ if (previous) {
21108
+ const previousIds = new Set(previous.scenarioIds);
21109
+ const added = entry.scenarioIds.filter((id) => !previousIds.has(id)).length;
21110
+ const removed = previous.scenarioIds.filter((id) => !entry.scenarioIds.includes(id)).length;
21111
+ if (added > 0 || removed > 0) {
21112
+ }
21113
+ }
21114
+ saveLedger(ledger, args.ledgerPath);
21115
+ return { entry, ledgerPath: args.ledgerPath };
21116
+ }
21117
+ function getDeploymentStatus(ledgerPath) {
21118
+ const ledger = loadLedger(ledgerPath);
21119
+ const environments = {};
21120
+ for (const entry of ledger.deployments) {
21121
+ environments[entry.environment] = {
21122
+ latest: entry,
21123
+ previousDeployment: environments[entry.environment]?.latest
21124
+ };
21125
+ }
21126
+ return { environments, ledgerPath };
21127
+ }
21128
+ function getEnvironmentDrift(ledgerPath, envA, envB) {
21129
+ const ledger = loadLedger(ledgerPath);
21130
+ const aEntry = getLatestDeployment(ledger, envA);
21131
+ const bEntry = getLatestDeployment(ledger, envB);
21132
+ if (!aEntry) {
21133
+ throw new Error(`No deployment found for environment "${envA}"`);
21134
+ }
21135
+ if (!bEntry) {
21136
+ throw new Error(`No deployment found for environment "${envB}"`);
21137
+ }
21138
+ const aIds = new Set(aEntry.scenarioIds);
21139
+ const bIds = new Set(bEntry.scenarioIds);
21140
+ const onlyInA = aEntry.scenarioIds.filter((id) => !bIds.has(id));
21141
+ const onlyInB = bEntry.scenarioIds.filter((id) => !aIds.has(id));
21142
+ const inBoth = aEntry.scenarioIds.filter((id) => bIds.has(id));
21143
+ const statusChanged = inBoth.map((id) => ({
21144
+ id,
21145
+ statusA: aEntry.scenarioStatuses?.[id] ?? "unknown",
21146
+ statusB: bEntry.scenarioStatuses?.[id] ?? "unknown"
21147
+ })).filter((item) => item.statusA !== item.statusB);
21148
+ return { environmentA: envA, environmentB: envB, onlyInA, onlyInB, inBoth, statusChanged, aEntry, bEntry };
21149
+ }
21150
+ function countStatuses(run) {
21151
+ const summary = { total: 0, passed: 0, failed: 0, skipped: 0, pending: 0 };
21152
+ for (const tc of run.testCases) {
21153
+ summary.total++;
21154
+ if (tc.status in summary) {
21155
+ summary[tc.status]++;
21156
+ }
21157
+ }
21158
+ return summary;
21159
+ }
21160
+
20970
21161
  // src/index.ts
20971
21162
  var FORMAT_EXTENSIONS = {
20972
21163
  astro: ".md",
20973
21164
  "behavior-manifest-json": ".behavior-manifest.json",
20974
21165
  markdown: ".md",
21166
+ "release-manifest": ".release-manifest.md",
20975
21167
  html: ".html",
20976
21168
  "cucumber-html": ".cucumber.html",
20977
21169
  junit: ".junit.xml",
@@ -21010,11 +21202,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
21010
21202
  const ext = FORMAT_EXTENSIONS[format];
21011
21203
  const effectiveName = outputName + (outputNameSuffix ?? "");
21012
21204
  if (mode === "aggregated") {
21013
- return toPosix(path10.join(baseOutputDir, joinNameAndExt(effectiveName, ext)));
21205
+ return toPosix(path11.join(baseOutputDir, joinNameAndExt(effectiveName, ext)));
21014
21206
  }
21015
21207
  const normalizedSource = toPosix(sourceFile);
21016
- const dirOfSource = path10.posix.dirname(normalizedSource);
21017
- let baseName = path10.posix.basename(normalizedSource);
21208
+ const dirOfSource = path11.posix.dirname(normalizedSource);
21209
+ let baseName = path11.posix.basename(normalizedSource);
21018
21210
  for (const testExt of TEST_EXTENSIONS) {
21019
21211
  if (baseName.endsWith(testExt)) {
21020
21212
  baseName = baseName.slice(0, -testExt.length);
@@ -21023,12 +21215,12 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
21023
21215
  }
21024
21216
  const fileName = `${baseName}.${effectiveName}${ext}`;
21025
21217
  if (colocatedStyle === "adjacent") {
21026
- return toPosix(path10.posix.join(dirOfSource, fileName));
21218
+ return toPosix(path11.posix.join(dirOfSource, fileName));
21027
21219
  }
21028
21220
  if (colocatedStyle === "flat") {
21029
- return toPosix(path10.posix.join(baseOutputDir, `${cleanTestStem(normalizedSource)}${ext}`));
21221
+ return toPosix(path11.posix.join(baseOutputDir, `${cleanTestStem(normalizedSource)}${ext}`));
21030
21222
  }
21031
- return toPosix(path10.posix.join(baseOutputDir, dirOfSource, fileName));
21223
+ return toPosix(path11.posix.join(baseOutputDir, dirOfSource, fileName));
21032
21224
  }
21033
21225
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
21034
21226
  const groups = /* @__PURE__ */ new Map();
@@ -21235,8 +21427,8 @@ var ReportGenerator = class {
21235
21427
  if (astroPaths) {
21236
21428
  for (const mdPath of astroPaths) {
21237
21429
  const content = await fsPromises.readFile(mdPath, "utf8");
21238
- const mdDir = path10.dirname(mdPath);
21239
- const assetsDir = path10.resolve(this.options.astro.assetsDir);
21430
+ const mdDir = path11.dirname(mdPath);
21431
+ const assetsDir = path11.resolve(this.options.astro.assetsDir);
21240
21432
  const result = copyMarkdownAssets({
21241
21433
  markdown: content,
21242
21434
  markdownDir: mdDir,
@@ -21267,9 +21459,9 @@ var ReportGenerator = class {
21267
21459
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
21268
21460
  const ext = FORMAT_EXTENSIONS[format];
21269
21461
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
21270
- const outputPath = toPosix(path10.join(this.options.outputDir, joinNameAndExt(effectiveName, ext)));
21462
+ const outputPath = toPosix(path11.join(this.options.outputDir, joinNameAndExt(effectiveName, ext)));
21271
21463
  const content = await this.formatContent(run, format);
21272
- const dir = path10.dirname(outputPath);
21464
+ const dir = path11.dirname(outputPath);
21273
21465
  await fsPromises.mkdir(dir, { recursive: true });
21274
21466
  await this.deps.writeFile(outputPath, content);
21275
21467
  return [outputPath];
@@ -21281,7 +21473,7 @@ var ReportGenerator = class {
21281
21473
  testCases
21282
21474
  };
21283
21475
  const content = await this.formatContent(groupRun, format);
21284
- const dir = path10.dirname(outputPath);
21476
+ const dir = path11.dirname(outputPath);
21285
21477
  await fsPromises.mkdir(dir, { recursive: true });
21286
21478
  await this.deps.writeFile(outputPath, content);
21287
21479
  writtenPaths.push(outputPath);
@@ -21390,6 +21582,10 @@ var ReportGenerator = class {
21390
21582
  });
21391
21583
  return formatter.format(run);
21392
21584
  }
21585
+ case "release-manifest": {
21586
+ const formatter = new ReleaseManifestFormatter();
21587
+ return formatter.format(run);
21588
+ }
21393
21589
  case "story-report-json": {
21394
21590
  const formatter = new StoryReportJsonFormatter({
21395
21591
  pretty: this.options.storyReportJson.pretty
@@ -21424,7 +21620,7 @@ async function generateRunComparison(args) {
21424
21620
  await fsPromises.mkdir(outputDir, { recursive: true });
21425
21621
  for (const format of args.formats) {
21426
21622
  const ext = format === "html" ? ".html" : ".md";
21427
- const outputPath = toPosix(path10.join(outputDir, `${outputName}${ext}`));
21623
+ const outputPath = toPosix(path11.join(outputDir, `${outputName}${ext}`));
21428
21624
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
21429
21625
  await fsPromises.writeFile(outputPath, content, "utf8");
21430
21626
  files.push(outputPath);
@@ -21459,6 +21655,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21459
21655
  MIN_METRIC_SAMPLES,
21460
21656
  MIN_PERF_SAMPLES,
21461
21657
  MarkdownFormatter,
21658
+ ReleaseManifestFormatter,
21462
21659
  ReportGenerator,
21463
21660
  ReviewHtmlFormatter,
21464
21661
  ReviewMarkdownFormatter,
@@ -21498,6 +21695,8 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21498
21695
  generateTestCaseId,
21499
21696
  getAvailableThemes,
21500
21697
  getCssOnlyThemes,
21698
+ getDeploymentStatus,
21699
+ getEnvironmentDrift,
21501
21700
  gradeEvidence,
21502
21701
  hasSufficientHistory,
21503
21702
  isReviewableSource,
@@ -21519,6 +21718,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21519
21718
  readBranchName,
21520
21719
  readGitSha,
21521
21720
  readPackageVersion,
21721
+ recordDeployment,
21522
21722
  regenerateArtifacts,
21523
21723
  resolveAttachment,
21524
21724
  resolveAttachments,
@@ -21538,6 +21738,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21538
21738
  toBehaviorManifest,
21539
21739
  toCIInfo,
21540
21740
  toRawCIInfo,
21741
+ toReleaseManifest,
21541
21742
  toScenarioIndex,
21542
21743
  toStoryReport,
21543
21744
  tryGetActiveOtelContext,