executable-stories-vitest 2.0.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.
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/reporter.ts
31
+ var reporter_exports = {};
32
+ __export(reporter_exports, {
33
+ StoryReporter: () => StoryReporter,
34
+ default: () => StoryReporter
35
+ });
36
+ module.exports = __toCommonJS(reporter_exports);
37
+ var fs = __toESM(require("fs"), 1);
38
+ var path = __toESM(require("path"), 1);
39
+ var import_executable_stories_formatters = require("executable-stories-formatters");
40
+ function normalizeCoveragePayload(coverage) {
41
+ if (!coverage || typeof coverage !== "object" || Array.isArray(coverage))
42
+ return void 0;
43
+ const raw = coverage;
44
+ const data = {};
45
+ for (const [filePath, file] of Object.entries(raw)) {
46
+ if (file && typeof file === "object" && "s" in file && "f" in file && "b" in file) {
47
+ data[filePath] = file;
48
+ }
49
+ }
50
+ if (Object.keys(data).length === 0) return void 0;
51
+ return data;
52
+ }
53
+ function summarizeCoverage(data) {
54
+ let statementsTotal = 0;
55
+ let statementsCovered = 0;
56
+ let functionsTotal = 0;
57
+ let functionsCovered = 0;
58
+ let branchesTotal = 0;
59
+ let branchesCovered = 0;
60
+ let linesTotal = 0;
61
+ let linesCovered = 0;
62
+ let hasLines = false;
63
+ for (const file of Object.values(data)) {
64
+ for (const count of Object.values(file.s)) {
65
+ statementsTotal += 1;
66
+ if (count > 0) statementsCovered += 1;
67
+ }
68
+ for (const count of Object.values(file.f)) {
69
+ functionsTotal += 1;
70
+ if (count > 0) functionsCovered += 1;
71
+ }
72
+ for (const counts of Object.values(file.b)) {
73
+ for (const count of counts) {
74
+ branchesTotal += 1;
75
+ if (count > 0) branchesCovered += 1;
76
+ }
77
+ }
78
+ if (file.l) {
79
+ hasLines = true;
80
+ for (const count of Object.values(file.l)) {
81
+ linesTotal += 1;
82
+ if (count > 0) linesCovered += 1;
83
+ }
84
+ }
85
+ }
86
+ if (statementsTotal === 0 && functionsTotal === 0 && branchesTotal === 0 && !hasLines) {
87
+ return void 0;
88
+ }
89
+ const metric = (covered, total) => ({
90
+ total,
91
+ covered,
92
+ pct: total === 0 ? 100 : Math.round(covered / total * 100)
93
+ });
94
+ const summary = {
95
+ statements: metric(statementsCovered, statementsTotal),
96
+ branches: metric(branchesCovered, branchesTotal),
97
+ functions: metric(functionsCovered, functionsTotal)
98
+ };
99
+ if (hasLines) {
100
+ summary.lines = metric(linesCovered, linesTotal);
101
+ }
102
+ return summary;
103
+ }
104
+ function toCoverageSummary(data) {
105
+ if (!data) return void 0;
106
+ return {
107
+ statementsPct: data.statements.pct,
108
+ branchesPct: data.branches.pct,
109
+ functionsPct: data.functions.pct,
110
+ linesPct: data.lines?.pct
111
+ };
112
+ }
113
+ function toRelativePosix(absolutePath, projectRoot) {
114
+ return path.relative(projectRoot, absolutePath).split(path.sep).join("/");
115
+ }
116
+ var StoryReporter = class {
117
+ options;
118
+ ctx;
119
+ startTime = 0;
120
+ packageVersion;
121
+ gitSha;
122
+ coverageData;
123
+ constructor(options = {}) {
124
+ this.options = options;
125
+ }
126
+ onInit(ctx) {
127
+ this.ctx = ctx;
128
+ this.startTime = Date.now();
129
+ const root = ctx.config?.root ?? process.cwd();
130
+ const includeMetadata = this.options.markdown?.includeMetadata ?? true;
131
+ if (includeMetadata) {
132
+ this.packageVersion = (0, import_executable_stories_formatters.readPackageVersion)(root);
133
+ this.gitSha = (0, import_executable_stories_formatters.readGitSha)(root);
134
+ }
135
+ }
136
+ onCoverage(coverage) {
137
+ const data = normalizeCoveragePayload(coverage);
138
+ if (data) {
139
+ this.coverageData = summarizeCoverage(data);
140
+ }
141
+ }
142
+ async onTestRunEnd(testModules, _unhandledErrors, reason) {
143
+ if (reason === "interrupted") return;
144
+ const root = this.ctx?.config?.root ?? process.cwd();
145
+ const rawTestCases = this.collectTestCases(testModules, root);
146
+ const rawRun = {
147
+ testCases: rawTestCases,
148
+ startedAtMs: this.startTime,
149
+ finishedAtMs: Date.now(),
150
+ projectRoot: root,
151
+ packageVersion: this.packageVersion,
152
+ gitSha: this.gitSha,
153
+ ci: (0, import_executable_stories_formatters.detectCI)()
154
+ };
155
+ const rawRunPath = this.options.rawRunPath;
156
+ if (rawRunPath) {
157
+ const absolutePath = path.isAbsolute(rawRunPath) ? rawRunPath : path.join(root, rawRunPath);
158
+ const dir = path.dirname(absolutePath);
159
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
160
+ const payload = { schemaVersion: 1, ...rawRun };
161
+ fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), "utf8");
162
+ }
163
+ const canonicalRun = (0, import_executable_stories_formatters.canonicalizeRun)(rawRun);
164
+ if (this.coverageData) {
165
+ canonicalRun.coverage = toCoverageSummary(this.coverageData);
166
+ }
167
+ const generator = new import_executable_stories_formatters.ReportGenerator(this.options);
168
+ try {
169
+ const results = await generator.generate(canonicalRun);
170
+ const enableGithubSummary = this.options.enableGithubActionsSummary ?? true;
171
+ if (process.env.GITHUB_ACTIONS === "true" && enableGithubSummary) {
172
+ const markdownPaths = results.get("markdown") ?? [];
173
+ if (markdownPaths.length > 0) {
174
+ const firstPath = markdownPaths[0];
175
+ const content = fs.readFileSync(firstPath, "utf8");
176
+ await this.appendGithubSummary(content).catch(() => {
177
+ });
178
+ }
179
+ }
180
+ } catch (err) {
181
+ console.error("Failed to generate reports:", err);
182
+ }
183
+ }
184
+ /**
185
+ * Collect test cases from Vitest test modules.
186
+ */
187
+ collectTestCases(testModules, root) {
188
+ const testCases = [];
189
+ for (const mod of testModules) {
190
+ const collection = mod.children;
191
+ if (!collection) continue;
192
+ const moduleId = mod.moduleId ?? mod.relativeModuleId ?? "";
193
+ const absoluteModuleId = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);
194
+ const sourceFile = toRelativePosix(absoluteModuleId, root);
195
+ for (const test of collection.allTests()) {
196
+ const meta = this.getStoryMeta(test);
197
+ if (!meta?.scenario || !Array.isArray(meta.steps)) continue;
198
+ const result = test.result?.();
199
+ const state = result?.state ?? "pending";
200
+ const durationMs = typeof result?.duration === "number" ? result.duration : 0;
201
+ let errorMessage;
202
+ let errorStack;
203
+ if (state === "failed" && result) {
204
+ const errors = result.errors;
205
+ if (errors?.length) {
206
+ const err = errors[0];
207
+ errorMessage = err.message;
208
+ errorStack = err.stack;
209
+ }
210
+ }
211
+ const statusMap = {
212
+ passed: "pass",
213
+ failed: "fail",
214
+ skipped: "skip",
215
+ pending: "pending",
216
+ todo: "pending"
217
+ };
218
+ const taskMeta = test.meta();
219
+ const scopedAttachments = taskMeta?.storyAttachments ?? [];
220
+ const attachments = scopedAttachments.map((a) => ({
221
+ name: a.name,
222
+ mediaType: a.mediaType,
223
+ path: a.path,
224
+ body: a.body,
225
+ encoding: a.encoding,
226
+ charset: a.charset,
227
+ fileName: a.fileName,
228
+ stepIndex: a.stepIndex,
229
+ stepId: a.stepId
230
+ }));
231
+ const stepEvents = meta.steps.filter((s) => s.durationMs !== void 0).map((s, i) => ({
232
+ index: i,
233
+ title: s.text,
234
+ durationMs: s.durationMs
235
+ }));
236
+ const retryCount = result?.retryCount ?? 0;
237
+ testCases.push({
238
+ title: meta.scenario,
239
+ titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],
240
+ story: meta,
241
+ sourceFile,
242
+ sourceLine: Math.max(1, meta.sourceOrder ?? 1),
243
+ status: statusMap[state] ?? "unknown",
244
+ durationMs,
245
+ error: errorMessage ? { message: errorMessage, stack: errorStack } : void 0,
246
+ attachments: attachments.length > 0 ? attachments : void 0,
247
+ stepEvents: stepEvents.length > 0 ? stepEvents : void 0,
248
+ retry: retryCount,
249
+ retries: 0
250
+ });
251
+ }
252
+ }
253
+ return testCases;
254
+ }
255
+ getStoryMeta(test) {
256
+ const meta = test.meta();
257
+ return meta?.["story"];
258
+ }
259
+ async appendGithubSummary(reportText) {
260
+ try {
261
+ const { summary } = await import("@actions/core");
262
+ summary.addRaw(reportText);
263
+ await summary.write();
264
+ } catch {
265
+ }
266
+ }
267
+ };
268
+ // Annotate the CommonJS export names for ESM import in node:
269
+ 0 && (module.exports = {
270
+ StoryReporter
271
+ });
272
+ //# sourceMappingURL=reporter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Vitest reporter that reads task.meta.story from tests and writes reports\n * using the executable-stories-formatters package.\n *\n * Do not add value imports from \"vitest\" or \"./story-api.js\" here; this entry is loaded in vitest.config\n * before Vitest is ready. Use only `import type` from those modules.\n */\nimport type {\n Reporter,\n SerializedError,\n TestModule,\n TestCase,\n TestRunEndReason,\n Vitest,\n} from \"vitest/node\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"./types\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n type CoverageSummary,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** When GITHUB_ACTIONS, append report to job summary via @actions/core. Default: true */\n enableGithubActionsSummary?: boolean;\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Coverage Types\n// ============================================================================\n\ntype CoverageMetric = { total: number; covered: number; pct: number };\ntype CoverageData = {\n statements: CoverageMetric;\n branches: CoverageMetric;\n functions: CoverageMetric;\n lines?: CoverageMetric;\n};\ntype CoverageFile = {\n s: Record<string, number>;\n f: Record<string, number>;\n b: Record<string, number[]>;\n l?: Record<string, number>;\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Normalize Vitest onCoverage payload to coverage data.\n */\nfunction normalizeCoveragePayload(\n coverage: unknown\n): Record<string, CoverageFile> | undefined {\n if (!coverage || typeof coverage !== \"object\" || Array.isArray(coverage))\n return undefined;\n const raw = coverage as Record<string, unknown>;\n const data: Record<string, CoverageFile> = {};\n for (const [filePath, file] of Object.entries(raw)) {\n if (\n file &&\n typeof file === \"object\" &&\n \"s\" in file &&\n \"f\" in file &&\n \"b\" in file\n ) {\n data[filePath] = file as CoverageFile;\n }\n }\n if (Object.keys(data).length === 0) return undefined;\n return data;\n}\n\n/**\n * Summarize coverage data.\n */\nfunction summarizeCoverage(\n data: Record<string, CoverageFile>\n): CoverageData | undefined {\n let statementsTotal = 0;\n let statementsCovered = 0;\n let functionsTotal = 0;\n let functionsCovered = 0;\n let branchesTotal = 0;\n let branchesCovered = 0;\n let linesTotal = 0;\n let linesCovered = 0;\n let hasLines = false;\n\n for (const file of Object.values(data)) {\n for (const count of Object.values(file.s)) {\n statementsTotal += 1;\n if (count > 0) statementsCovered += 1;\n }\n for (const count of Object.values(file.f)) {\n functionsTotal += 1;\n if (count > 0) functionsCovered += 1;\n }\n for (const counts of Object.values(file.b)) {\n for (const count of counts) {\n branchesTotal += 1;\n if (count > 0) branchesCovered += 1;\n }\n }\n if (file.l) {\n hasLines = true;\n for (const count of Object.values(file.l)) {\n linesTotal += 1;\n if (count > 0) linesCovered += 1;\n }\n }\n }\n\n if (\n statementsTotal === 0 &&\n functionsTotal === 0 &&\n branchesTotal === 0 &&\n !hasLines\n ) {\n return undefined;\n }\n\n const metric = (covered: number, total: number): CoverageMetric => ({\n total,\n covered,\n pct: total === 0 ? 100 : Math.round((covered / total) * 100),\n });\n\n const summary: CoverageData = {\n statements: metric(statementsCovered, statementsTotal),\n branches: metric(branchesCovered, branchesTotal),\n functions: metric(functionsCovered, functionsTotal),\n };\n if (hasLines) {\n summary.lines = metric(linesCovered, linesTotal);\n }\n return summary;\n}\n\n/**\n * Convert internal coverage data to formatters CoverageSummary.\n */\nfunction toCoverageSummary(\n data: CoverageData | undefined\n): CoverageSummary | undefined {\n if (!data) return undefined;\n return {\n statementsPct: data.statements.pct,\n branchesPct: data.branches.pct,\n functionsPct: data.functions.pct,\n linesPct: data.lines?.pct,\n };\n}\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\n/**\n * Vitest reporter that generates reports using executable-stories-formatters.\n *\n * Reads `task.meta.story` from each test and generates reports in configured formats.\n * Supports output routing (aggregated/colocated) and multiple output formats.\n */\nexport default class StoryReporter implements Reporter {\n private options: StoryReporterOptions;\n private ctx: Vitest | undefined;\n private startTime: number = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n private coverageData: CoverageData | undefined;\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onInit(ctx: Vitest): void {\n this.ctx = ctx;\n this.startTime = Date.now();\n const root = ctx.config?.root ?? process.cwd();\n\n // Read metadata if needed\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n onCoverage(coverage: unknown): void {\n const data = normalizeCoveragePayload(coverage);\n if (data) {\n this.coverageData = summarizeCoverage(data);\n }\n }\n\n async onTestRunEnd(\n testModules: ReadonlyArray<TestModule>,\n _unhandledErrors: ReadonlyArray<SerializedError>,\n reason: TestRunEndReason\n ): Promise<void> {\n if (reason === \"interrupted\") return;\n\n const root = this.ctx?.config?.root ?? process.cwd();\n\n // Collect test cases\n const rawTestCases = this.collectTestCases(testModules, root);\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Add coverage if available\n if (this.coverageData) {\n canonicalRun.coverage = toCoverageSummary(this.coverageData);\n }\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n const results = await generator.generate(canonicalRun);\n\n // Append to GitHub Actions summary if enabled\n const enableGithubSummary = this.options.enableGithubActionsSummary ?? true;\n if (process.env.GITHUB_ACTIONS === \"true\" && enableGithubSummary) {\n const markdownPaths = results.get(\"markdown\") ?? [];\n if (markdownPaths.length > 0) {\n const firstPath = markdownPaths[0];\n const content = fs.readFileSync(firstPath, \"utf8\");\n await this.appendGithubSummary(content).catch(() => {});\n }\n }\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n\n /**\n * Collect test cases from Vitest test modules.\n */\n private collectTestCases(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): RawTestCase[] {\n const testCases: RawTestCase[] = [];\n\n for (const mod of testModules) {\n const collection = mod.children;\n if (!collection) continue;\n\n const moduleId =\n mod.moduleId ??\n (mod as { relativeModuleId?: string }).relativeModuleId ??\n \"\";\n const absoluteModuleId = path.isAbsolute(moduleId)\n ? moduleId\n : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absoluteModuleId, root);\n\n for (const test of collection.allTests()) {\n const meta = this.getStoryMeta(test);\n if (!meta?.scenario || !Array.isArray(meta.steps)) continue;\n\n const result = test.result?.();\n const state = result?.state ?? \"pending\";\n const durationMs =\n typeof (result as { duration?: number } | undefined)?.duration ===\n \"number\"\n ? (result as unknown as { duration: number }).duration\n : 0;\n\n // Get error details\n let errorMessage: string | undefined;\n let errorStack: string | undefined;\n if (state === \"failed\" && result) {\n const errors = (result as { errors?: SerializedError[] }).errors;\n if (errors?.length) {\n const err = errors[0];\n errorMessage = err.message;\n errorStack = err.stack;\n }\n }\n\n // Map Vitest state to raw status\n const statusMap: Record<string, string> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n // Extract attachments from task.meta.storyAttachments\n const taskMeta = test.meta() as Record<string, unknown>;\n const scopedAttachments = (taskMeta?.storyAttachments ?? []) as Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n const attachments: RawAttachment[] = scopedAttachments.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number }, i: number) => ({\n index: i,\n title: (s as { text: string }).text,\n durationMs: s.durationMs,\n }));\n\n // Retry info from Vitest result\n const retryCount = (result as { retryCount?: number } | undefined)?.retryCount ?? 0;\n\n testCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status: (statusMap[state] ?? \"unknown\") as RawTestCase[\"status\"],\n durationMs,\n error: errorMessage\n ? { message: errorMessage, stack: errorStack }\n : undefined,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: retryCount,\n retries: 0,\n });\n }\n }\n\n return testCases;\n }\n\n private getStoryMeta(test: TestCase): StoryMeta | undefined {\n const meta = test.meta() as Record<string, unknown>;\n return meta?.[\"story\"] as StoryMeta | undefined;\n }\n\n private async appendGithubSummary(reportText: string): Promise<void> {\n try {\n const { summary } = await import(\"@actions/core\");\n summary.addRaw(reportText);\n await summary.write();\n } catch {\n // @actions/core not available or not in Actions\n }\n }\n}\n\nexport { StoryReporter };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,44 @@
1
+ import { Reporter, Vitest, TestModule, SerializedError, TestRunEndReason } from 'vitest/node';
2
+ import { FormatterOptions } from 'executable-stories-formatters';
3
+ export { ColocatedStyle, FormatterOptions, OutputFormat, OutputMode, OutputRule } from 'executable-stories-formatters';
4
+
5
+ /**
6
+ * Vitest reporter that reads task.meta.story from tests and writes reports
7
+ * using the executable-stories-formatters package.
8
+ *
9
+ * Do not add value imports from "vitest" or "./story-api.js" here; this entry is loaded in vitest.config
10
+ * before Vitest is ready. Use only `import type` from those modules.
11
+ */
12
+
13
+ interface StoryReporterOptions extends FormatterOptions {
14
+ /** When GITHUB_ACTIONS, append report to job summary via @actions/core. Default: true */
15
+ enableGithubActionsSummary?: boolean;
16
+ /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */
17
+ rawRunPath?: string;
18
+ }
19
+ /**
20
+ * Vitest reporter that generates reports using executable-stories-formatters.
21
+ *
22
+ * Reads `task.meta.story` from each test and generates reports in configured formats.
23
+ * Supports output routing (aggregated/colocated) and multiple output formats.
24
+ */
25
+ declare class StoryReporter implements Reporter {
26
+ private options;
27
+ private ctx;
28
+ private startTime;
29
+ private packageVersion;
30
+ private gitSha;
31
+ private coverageData;
32
+ constructor(options?: StoryReporterOptions);
33
+ onInit(ctx: Vitest): void;
34
+ onCoverage(coverage: unknown): void;
35
+ onTestRunEnd(testModules: ReadonlyArray<TestModule>, _unhandledErrors: ReadonlyArray<SerializedError>, reason: TestRunEndReason): Promise<void>;
36
+ /**
37
+ * Collect test cases from Vitest test modules.
38
+ */
39
+ private collectTestCases;
40
+ private getStoryMeta;
41
+ private appendGithubSummary;
42
+ }
43
+
44
+ export { StoryReporter, type StoryReporterOptions, StoryReporter as default };
@@ -0,0 +1,44 @@
1
+ import { Reporter, Vitest, TestModule, SerializedError, TestRunEndReason } from 'vitest/node';
2
+ import { FormatterOptions } from 'executable-stories-formatters';
3
+ export { ColocatedStyle, FormatterOptions, OutputFormat, OutputMode, OutputRule } from 'executable-stories-formatters';
4
+
5
+ /**
6
+ * Vitest reporter that reads task.meta.story from tests and writes reports
7
+ * using the executable-stories-formatters package.
8
+ *
9
+ * Do not add value imports from "vitest" or "./story-api.js" here; this entry is loaded in vitest.config
10
+ * before Vitest is ready. Use only `import type` from those modules.
11
+ */
12
+
13
+ interface StoryReporterOptions extends FormatterOptions {
14
+ /** When GITHUB_ACTIONS, append report to job summary via @actions/core. Default: true */
15
+ enableGithubActionsSummary?: boolean;
16
+ /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */
17
+ rawRunPath?: string;
18
+ }
19
+ /**
20
+ * Vitest reporter that generates reports using executable-stories-formatters.
21
+ *
22
+ * Reads `task.meta.story` from each test and generates reports in configured formats.
23
+ * Supports output routing (aggregated/colocated) and multiple output formats.
24
+ */
25
+ declare class StoryReporter implements Reporter {
26
+ private options;
27
+ private ctx;
28
+ private startTime;
29
+ private packageVersion;
30
+ private gitSha;
31
+ private coverageData;
32
+ constructor(options?: StoryReporterOptions);
33
+ onInit(ctx: Vitest): void;
34
+ onCoverage(coverage: unknown): void;
35
+ onTestRunEnd(testModules: ReadonlyArray<TestModule>, _unhandledErrors: ReadonlyArray<SerializedError>, reason: TestRunEndReason): Promise<void>;
36
+ /**
37
+ * Collect test cases from Vitest test modules.
38
+ */
39
+ private collectTestCases;
40
+ private getStoryMeta;
41
+ private appendGithubSummary;
42
+ }
43
+
44
+ export { StoryReporter, type StoryReporterOptions, StoryReporter as default };
@@ -0,0 +1,243 @@
1
+ // src/reporter.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import {
5
+ ReportGenerator,
6
+ canonicalizeRun,
7
+ readGitSha,
8
+ readPackageVersion,
9
+ detectCI
10
+ } from "executable-stories-formatters";
11
+ function normalizeCoveragePayload(coverage) {
12
+ if (!coverage || typeof coverage !== "object" || Array.isArray(coverage))
13
+ return void 0;
14
+ const raw = coverage;
15
+ const data = {};
16
+ for (const [filePath, file] of Object.entries(raw)) {
17
+ if (file && typeof file === "object" && "s" in file && "f" in file && "b" in file) {
18
+ data[filePath] = file;
19
+ }
20
+ }
21
+ if (Object.keys(data).length === 0) return void 0;
22
+ return data;
23
+ }
24
+ function summarizeCoverage(data) {
25
+ let statementsTotal = 0;
26
+ let statementsCovered = 0;
27
+ let functionsTotal = 0;
28
+ let functionsCovered = 0;
29
+ let branchesTotal = 0;
30
+ let branchesCovered = 0;
31
+ let linesTotal = 0;
32
+ let linesCovered = 0;
33
+ let hasLines = false;
34
+ for (const file of Object.values(data)) {
35
+ for (const count of Object.values(file.s)) {
36
+ statementsTotal += 1;
37
+ if (count > 0) statementsCovered += 1;
38
+ }
39
+ for (const count of Object.values(file.f)) {
40
+ functionsTotal += 1;
41
+ if (count > 0) functionsCovered += 1;
42
+ }
43
+ for (const counts of Object.values(file.b)) {
44
+ for (const count of counts) {
45
+ branchesTotal += 1;
46
+ if (count > 0) branchesCovered += 1;
47
+ }
48
+ }
49
+ if (file.l) {
50
+ hasLines = true;
51
+ for (const count of Object.values(file.l)) {
52
+ linesTotal += 1;
53
+ if (count > 0) linesCovered += 1;
54
+ }
55
+ }
56
+ }
57
+ if (statementsTotal === 0 && functionsTotal === 0 && branchesTotal === 0 && !hasLines) {
58
+ return void 0;
59
+ }
60
+ const metric = (covered, total) => ({
61
+ total,
62
+ covered,
63
+ pct: total === 0 ? 100 : Math.round(covered / total * 100)
64
+ });
65
+ const summary = {
66
+ statements: metric(statementsCovered, statementsTotal),
67
+ branches: metric(branchesCovered, branchesTotal),
68
+ functions: metric(functionsCovered, functionsTotal)
69
+ };
70
+ if (hasLines) {
71
+ summary.lines = metric(linesCovered, linesTotal);
72
+ }
73
+ return summary;
74
+ }
75
+ function toCoverageSummary(data) {
76
+ if (!data) return void 0;
77
+ return {
78
+ statementsPct: data.statements.pct,
79
+ branchesPct: data.branches.pct,
80
+ functionsPct: data.functions.pct,
81
+ linesPct: data.lines?.pct
82
+ };
83
+ }
84
+ function toRelativePosix(absolutePath, projectRoot) {
85
+ return path.relative(projectRoot, absolutePath).split(path.sep).join("/");
86
+ }
87
+ var StoryReporter = class {
88
+ options;
89
+ ctx;
90
+ startTime = 0;
91
+ packageVersion;
92
+ gitSha;
93
+ coverageData;
94
+ constructor(options = {}) {
95
+ this.options = options;
96
+ }
97
+ onInit(ctx) {
98
+ this.ctx = ctx;
99
+ this.startTime = Date.now();
100
+ const root = ctx.config?.root ?? process.cwd();
101
+ const includeMetadata = this.options.markdown?.includeMetadata ?? true;
102
+ if (includeMetadata) {
103
+ this.packageVersion = readPackageVersion(root);
104
+ this.gitSha = readGitSha(root);
105
+ }
106
+ }
107
+ onCoverage(coverage) {
108
+ const data = normalizeCoveragePayload(coverage);
109
+ if (data) {
110
+ this.coverageData = summarizeCoverage(data);
111
+ }
112
+ }
113
+ async onTestRunEnd(testModules, _unhandledErrors, reason) {
114
+ if (reason === "interrupted") return;
115
+ const root = this.ctx?.config?.root ?? process.cwd();
116
+ const rawTestCases = this.collectTestCases(testModules, root);
117
+ const rawRun = {
118
+ testCases: rawTestCases,
119
+ startedAtMs: this.startTime,
120
+ finishedAtMs: Date.now(),
121
+ projectRoot: root,
122
+ packageVersion: this.packageVersion,
123
+ gitSha: this.gitSha,
124
+ ci: detectCI()
125
+ };
126
+ const rawRunPath = this.options.rawRunPath;
127
+ if (rawRunPath) {
128
+ const absolutePath = path.isAbsolute(rawRunPath) ? rawRunPath : path.join(root, rawRunPath);
129
+ const dir = path.dirname(absolutePath);
130
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
131
+ const payload = { schemaVersion: 1, ...rawRun };
132
+ fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), "utf8");
133
+ }
134
+ const canonicalRun = canonicalizeRun(rawRun);
135
+ if (this.coverageData) {
136
+ canonicalRun.coverage = toCoverageSummary(this.coverageData);
137
+ }
138
+ const generator = new ReportGenerator(this.options);
139
+ try {
140
+ const results = await generator.generate(canonicalRun);
141
+ const enableGithubSummary = this.options.enableGithubActionsSummary ?? true;
142
+ if (process.env.GITHUB_ACTIONS === "true" && enableGithubSummary) {
143
+ const markdownPaths = results.get("markdown") ?? [];
144
+ if (markdownPaths.length > 0) {
145
+ const firstPath = markdownPaths[0];
146
+ const content = fs.readFileSync(firstPath, "utf8");
147
+ await this.appendGithubSummary(content).catch(() => {
148
+ });
149
+ }
150
+ }
151
+ } catch (err) {
152
+ console.error("Failed to generate reports:", err);
153
+ }
154
+ }
155
+ /**
156
+ * Collect test cases from Vitest test modules.
157
+ */
158
+ collectTestCases(testModules, root) {
159
+ const testCases = [];
160
+ for (const mod of testModules) {
161
+ const collection = mod.children;
162
+ if (!collection) continue;
163
+ const moduleId = mod.moduleId ?? mod.relativeModuleId ?? "";
164
+ const absoluteModuleId = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);
165
+ const sourceFile = toRelativePosix(absoluteModuleId, root);
166
+ for (const test of collection.allTests()) {
167
+ const meta = this.getStoryMeta(test);
168
+ if (!meta?.scenario || !Array.isArray(meta.steps)) continue;
169
+ const result = test.result?.();
170
+ const state = result?.state ?? "pending";
171
+ const durationMs = typeof result?.duration === "number" ? result.duration : 0;
172
+ let errorMessage;
173
+ let errorStack;
174
+ if (state === "failed" && result) {
175
+ const errors = result.errors;
176
+ if (errors?.length) {
177
+ const err = errors[0];
178
+ errorMessage = err.message;
179
+ errorStack = err.stack;
180
+ }
181
+ }
182
+ const statusMap = {
183
+ passed: "pass",
184
+ failed: "fail",
185
+ skipped: "skip",
186
+ pending: "pending",
187
+ todo: "pending"
188
+ };
189
+ const taskMeta = test.meta();
190
+ const scopedAttachments = taskMeta?.storyAttachments ?? [];
191
+ const attachments = scopedAttachments.map((a) => ({
192
+ name: a.name,
193
+ mediaType: a.mediaType,
194
+ path: a.path,
195
+ body: a.body,
196
+ encoding: a.encoding,
197
+ charset: a.charset,
198
+ fileName: a.fileName,
199
+ stepIndex: a.stepIndex,
200
+ stepId: a.stepId
201
+ }));
202
+ const stepEvents = meta.steps.filter((s) => s.durationMs !== void 0).map((s, i) => ({
203
+ index: i,
204
+ title: s.text,
205
+ durationMs: s.durationMs
206
+ }));
207
+ const retryCount = result?.retryCount ?? 0;
208
+ testCases.push({
209
+ title: meta.scenario,
210
+ titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],
211
+ story: meta,
212
+ sourceFile,
213
+ sourceLine: Math.max(1, meta.sourceOrder ?? 1),
214
+ status: statusMap[state] ?? "unknown",
215
+ durationMs,
216
+ error: errorMessage ? { message: errorMessage, stack: errorStack } : void 0,
217
+ attachments: attachments.length > 0 ? attachments : void 0,
218
+ stepEvents: stepEvents.length > 0 ? stepEvents : void 0,
219
+ retry: retryCount,
220
+ retries: 0
221
+ });
222
+ }
223
+ }
224
+ return testCases;
225
+ }
226
+ getStoryMeta(test) {
227
+ const meta = test.meta();
228
+ return meta?.["story"];
229
+ }
230
+ async appendGithubSummary(reportText) {
231
+ try {
232
+ const { summary } = await import("@actions/core");
233
+ summary.addRaw(reportText);
234
+ await summary.write();
235
+ } catch {
236
+ }
237
+ }
238
+ };
239
+ export {
240
+ StoryReporter,
241
+ StoryReporter as default
242
+ };
243
+ //# sourceMappingURL=reporter.js.map