@specific.dev/spectest 0.119.3 → 0.119.5

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.
@@ -64,6 +64,7 @@ export function coverageHostDir(workspace: string, service: string): string {
64
64
  export interface CoverageReport {
65
65
  name: string;
66
66
  content: string;
67
+ sourceOptions?: import("../coverage.js").CoverageSourceOptions;
67
68
  }
68
69
 
69
70
  /** One service's capture: every report in its directory, verbatim. */
@@ -78,6 +79,8 @@ export interface ServiceCoverageCapture {
78
79
  root?: string;
79
80
  /** Repository root of the service build/bundler workspace, when explicit. */
80
81
  sourceRoot?: string;
82
+ /** SDK-declared source exclusions, preserved for server-side derivation. */
83
+ excludeSources?: readonly string[];
81
84
  /** Set when the capture failed: the command failed, the directory was
82
85
  * empty, no file held an `SF:` record, or the reports were too large.
83
86
  * A case with an error here fails — a hole in the coverage data must
@@ -471,7 +474,7 @@ export function applyCoverageDelta(
471
474
  const prev = prevByName.get(r.name) ?? new Map<string, LcovRecordCounts>();
472
475
  prevByName.set(r.name, now);
473
476
  const restarted: string[] = [];
474
- reports.push({ name: r.name, content: renderLcovCounts(subtractLcovCounts(now, prev, restarted)) });
477
+ reports.push({ ...r, content: renderLcovCounts(subtractLcovCounts(now, prev, restarted)) });
475
478
  if (restarted.length > 0) {
476
479
  console.warn(
477
480
  `[coverage] service "${capture.service}" report ${r.name}: counters dropped in ${restarted.length} file(s) (${restarted.slice(0, 3).join(", ")}) — a process restarted; those records count whole`,
package/src/index.ts CHANGED
@@ -176,6 +176,8 @@ export {
176
176
  type CoverageAdapter,
177
177
  type CoverageCaptureContext,
178
178
  type CoverageConfigureInfo,
179
+ type CoverageSourceOptions,
180
+ type CoverageSourceContext,
179
181
  type CoverageLoadContext,
180
182
  type ServiceCoverage,
181
183
  } from "./coverage.js";
package/src/next.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ /** Keep each traced script's map alongside it in standalone output. Turbopack
5
+ * versions that do not apply outputFileTracingIncludes still emit these traces
6
+ * before runAfterProductionCompile. No source files or generated code change. */
7
+ async function includeTracedMaps(distDir: string): Promise<void> {
8
+ async function walk(dir: string): Promise<void> {
9
+ for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
10
+ const file = path.join(dir, entry.name);
11
+ if (entry.isDirectory() && entry.name !== "cache" && entry.name !== "standalone") await walk(file);
12
+ else if (entry.isFile() && entry.name.endsWith(".nft.json")) {
13
+ const trace = JSON.parse(await fs.readFile(file, "utf8"));
14
+ if (!Array.isArray(trace.files)) throw new Error(`Invalid Next trace: ${file}`);
15
+ const scripts = [entry.name.slice(0, -".nft.json".length), ...trace.files];
16
+ const maps: string[] = [];
17
+ for (const script of scripts) {
18
+ if (typeof script !== "string" || !script.endsWith(".js")) continue;
19
+ const map = script + ".map";
20
+ try { if ((await fs.stat(path.resolve(dir, map))).isFile()) maps.push(map); }
21
+ catch (e) { if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e; }
22
+ }
23
+ trace.files = [...new Set([...trace.files, ...maps])];
24
+ await fs.writeFile(file, JSON.stringify(trace));
25
+ }
26
+ }
27
+ }
28
+ await walk(distDir);
29
+ }
30
+
31
+ /** Build-time companion for coverage.next(). Import this lightweight module in
32
+ * next.config; it does not load the Spectest harness or require Next at runtime.
33
+ * Apply only in the test build if production should not expose source maps. */
34
+ export function withCoverage<T extends object>(config: T): T {
35
+ const c = config as Record<string, any>;
36
+ const distDir = c.distDir ?? ".next";
37
+ const includes = c.outputFileTracingIncludes ?? {};
38
+ return {
39
+ ...config,
40
+ productionBrowserSourceMaps: true,
41
+ turbopack: c.turbopack ?? {},
42
+ webpack(webpackConfig: any, context: any) {
43
+ const configured = c.webpack ? c.webpack(webpackConfig, context) : webpackConfig;
44
+ configured.plugins ??= [];
45
+ configured.plugins.push({
46
+ apply(compiler: any) {
47
+ compiler.hooks.compilation.tap("SpectestCoverage", (compilation: any) => {
48
+ compiler.webpack.NormalModule.getCompilationHooks(compilation).loader.tap(
49
+ "SpectestCoverage", (loader: any, module: any) => {
50
+ // Keep dependencies identifiable as dependencies. Chaining a
51
+ // package's published maps can replace node_modules provenance
52
+ // with ambiguous paths such as ../../src/client/router.ts.
53
+ if (/[\\/]node_modules[\\/]/.test(module.resource ?? "")) loader.sourceMap = false;
54
+ },
55
+ );
56
+ });
57
+ },
58
+ });
59
+ return configured;
60
+ },
61
+ experimental: { ...c.experimental, serverSourceMaps: true },
62
+ compiler: {
63
+ ...c.compiler,
64
+ async runAfterProductionCompile(metadata: { projectDir: string; distDir: string }) {
65
+ await c.compiler?.runAfterProductionCompile?.(metadata);
66
+ await includeTracedMaps(metadata.distDir);
67
+ },
68
+ },
69
+ outputFileTracingIncludes: {
70
+ ...includes,
71
+ "/**": [...(includes["/**"] ?? []), `./${distDir}/server/**/*.map`],
72
+ },
73
+ };
74
+ }