@interfere/next 9.0.2 → 10.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.
Files changed (166) hide show
  1. package/README.md +33 -5
  2. package/dist/config.d.mts +24 -5
  3. package/dist/config.d.mts.map +1 -1
  4. package/dist/config.mjs +38 -28
  5. package/dist/config.mjs.map +1 -1
  6. package/dist/instrument-client.d.mts +14 -3
  7. package/dist/instrument-client.d.mts.map +1 -1
  8. package/dist/instrument-client.mjs +7 -9
  9. package/dist/instrument-client.mjs.map +1 -1
  10. package/dist/instrumentation-client.d.mts +1 -0
  11. package/dist/instrumentation-client.mjs +22 -0
  12. package/dist/instrumentation-client.mjs.map +1 -0
  13. package/dist/instrumentation.d.mts +134 -0
  14. package/dist/instrumentation.d.mts.map +1 -0
  15. package/dist/instrumentation.edge.d.mts +35 -0
  16. package/dist/instrumentation.edge.d.mts.map +1 -0
  17. package/dist/instrumentation.edge.mjs +34 -0
  18. package/dist/instrumentation.edge.mjs.map +1 -0
  19. package/dist/instrumentation.mjs +165 -0
  20. package/dist/instrumentation.mjs.map +1 -0
  21. package/dist/internal/build/configure-build.d.mts +1 -2
  22. package/dist/internal/build/configure-build.d.mts.map +1 -1
  23. package/dist/internal/build/configure-build.mjs +10 -2
  24. package/dist/internal/build/configure-build.mjs.map +1 -1
  25. package/dist/internal/build/detect-bundler.d.mts +6 -0
  26. package/dist/internal/build/detect-bundler.d.mts.map +1 -0
  27. package/dist/internal/build/detect-bundler.mjs +9 -0
  28. package/dist/internal/build/detect-bundler.mjs.map +1 -0
  29. package/dist/internal/build/pipeline.d.mts +14 -1
  30. package/dist/internal/build/pipeline.d.mts.map +1 -1
  31. package/dist/internal/build/pipeline.mjs +26 -10
  32. package/dist/internal/build/pipeline.mjs.map +1 -1
  33. package/dist/internal/build/release/destinations/index.d.mts +14 -0
  34. package/dist/internal/build/release/destinations/index.d.mts.map +1 -0
  35. package/dist/internal/build/release/destinations/index.mjs +13 -0
  36. package/dist/internal/build/release/destinations/index.mjs.map +1 -0
  37. package/dist/internal/build/release/destinations/vercel.mjs.map +1 -1
  38. package/dist/internal/build/release/git.d.mts +13 -0
  39. package/dist/internal/build/release/git.d.mts.map +1 -1
  40. package/dist/internal/build/release/git.mjs +13 -2
  41. package/dist/internal/build/release/git.mjs.map +1 -1
  42. package/dist/internal/build/release/index.d.mts +2 -1
  43. package/dist/internal/build/release/index.d.mts.map +1 -1
  44. package/dist/internal/build/release/index.mjs +4 -5
  45. package/dist/internal/build/release/index.mjs.map +1 -1
  46. package/dist/internal/build/release/sources/github.mjs.map +1 -1
  47. package/dist/internal/build/release/sources/index.d.mts +21 -0
  48. package/dist/internal/build/release/sources/index.d.mts.map +1 -0
  49. package/dist/internal/build/release/sources/index.mjs +20 -0
  50. package/dist/internal/build/release/sources/index.mjs.map +1 -0
  51. package/dist/internal/build/source-maps/discover-turbopack.d.mts +32 -0
  52. package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +1 -0
  53. package/dist/internal/build/source-maps/discover-turbopack.mjs +68 -0
  54. package/dist/internal/build/source-maps/discover-turbopack.mjs.map +1 -0
  55. package/dist/internal/build/source-maps/discover-webpack.d.mts +53 -0
  56. package/dist/internal/build/source-maps/discover-webpack.d.mts.map +1 -0
  57. package/dist/internal/build/source-maps/discover-webpack.mjs +112 -0
  58. package/dist/internal/build/source-maps/discover-webpack.mjs.map +1 -0
  59. package/dist/internal/build/source-maps/discover.d.mts +28 -10
  60. package/dist/internal/build/source-maps/discover.d.mts.map +1 -1
  61. package/dist/internal/build/source-maps/discover.mjs +22 -83
  62. package/dist/internal/build/source-maps/discover.mjs.map +1 -1
  63. package/dist/internal/build/source-maps/index.d.mts +2 -24
  64. package/dist/internal/build/source-maps/index.d.mts.map +1 -1
  65. package/dist/internal/build/source-maps/index.mjs +13 -23
  66. package/dist/internal/build/source-maps/index.mjs.map +1 -1
  67. package/dist/internal/build/source-maps/paths.d.mts +28 -0
  68. package/dist/internal/build/source-maps/paths.d.mts.map +1 -0
  69. package/dist/internal/build/source-maps/paths.mjs +49 -0
  70. package/dist/internal/build/source-maps/paths.mjs.map +1 -0
  71. package/dist/internal/build/source-maps/upload.d.mts +46 -0
  72. package/dist/internal/build/source-maps/upload.d.mts.map +1 -0
  73. package/dist/internal/build/source-maps/upload.mjs +134 -0
  74. package/dist/internal/build/source-maps/upload.mjs.map +1 -0
  75. package/dist/internal/build/value-injection-loader.mjs.map +1 -1
  76. package/dist/internal/env.d.mts +11 -2
  77. package/dist/internal/env.d.mts.map +1 -1
  78. package/dist/internal/env.mjs +12 -3
  79. package/dist/internal/env.mjs.map +1 -1
  80. package/dist/internal/logger.d.mts +9 -1
  81. package/dist/internal/logger.d.mts.map +1 -1
  82. package/dist/internal/logger.mjs +10 -2
  83. package/dist/internal/logger.mjs.map +1 -1
  84. package/dist/internal/release-slug.d.mts +25 -0
  85. package/dist/internal/release-slug.d.mts.map +1 -0
  86. package/dist/internal/release-slug.mjs +32 -0
  87. package/dist/internal/release-slug.mjs.map +1 -0
  88. package/dist/internal/route/handle-get.d.mts +14 -1
  89. package/dist/internal/route/handle-get.d.mts.map +1 -1
  90. package/dist/internal/route/handle-get.mjs +35 -14
  91. package/dist/internal/route/handle-get.mjs.map +1 -1
  92. package/dist/internal/route/handle-post.d.mts +11 -0
  93. package/dist/internal/route/handle-post.d.mts.map +1 -1
  94. package/dist/internal/route/handle-post.mjs +11 -50
  95. package/dist/internal/route/handle-post.mjs.map +1 -1
  96. package/dist/internal/route/proxy.d.mts +21 -1
  97. package/dist/internal/route/proxy.d.mts.map +1 -1
  98. package/dist/internal/route/proxy.mjs +61 -16
  99. package/dist/internal/route/proxy.mjs.map +1 -1
  100. package/dist/internal/server/capture.d.mts +2 -2
  101. package/dist/internal/server/capture.d.mts.map +1 -1
  102. package/dist/internal/server/capture.mjs +71 -37
  103. package/dist/internal/server/capture.mjs.map +1 -1
  104. package/dist/internal/server/console-bridge.d.mts +19 -0
  105. package/dist/internal/server/console-bridge.d.mts.map +1 -0
  106. package/dist/internal/server/console-bridge.mjs +112 -0
  107. package/dist/internal/server/console-bridge.mjs.map +1 -0
  108. package/dist/internal/server/id-generator.d.mts +38 -0
  109. package/dist/internal/server/id-generator.d.mts.map +1 -0
  110. package/dist/internal/server/id-generator.mjs +68 -0
  111. package/dist/internal/server/id-generator.mjs.map +1 -0
  112. package/dist/internal/server/instrumentation-options.d.mts +86 -0
  113. package/dist/internal/server/instrumentation-options.d.mts.map +1 -0
  114. package/dist/internal/server/instrumentation-options.mjs +1 -0
  115. package/dist/internal/server/remote-config.mjs +2 -2
  116. package/dist/internal/server/remote-config.mjs.map +1 -1
  117. package/dist/internal/server/trace-meta.d.mts +34 -0
  118. package/dist/internal/server/trace-meta.d.mts.map +1 -0
  119. package/dist/internal/server/trace-meta.mjs +41 -0
  120. package/dist/internal/server/trace-meta.mjs.map +1 -0
  121. package/dist/internal/server/traceparent.d.mts +16 -0
  122. package/dist/internal/server/traceparent.d.mts.map +1 -0
  123. package/dist/internal/server/traceparent.mjs +26 -0
  124. package/dist/internal/server/traceparent.mjs.map +1 -0
  125. package/dist/internal/server/types.d.mts +1 -7
  126. package/dist/internal/server/types.d.mts.map +1 -1
  127. package/dist/internal/setup-warnings.d.mts +17 -0
  128. package/dist/internal/setup-warnings.d.mts.map +1 -0
  129. package/dist/internal/setup-warnings.mjs +45 -0
  130. package/dist/internal/setup-warnings.mjs.map +1 -0
  131. package/dist/package.mjs +1 -1
  132. package/dist/provider.d.mts +23 -2
  133. package/dist/provider.d.mts.map +1 -0
  134. package/dist/provider.mjs +23 -1
  135. package/dist/provider.mjs.map +1 -0
  136. package/dist/route-handler.d.mts +7 -2
  137. package/dist/route-handler.d.mts.map +1 -1
  138. package/dist/route-handler.mjs +11 -9
  139. package/dist/route-handler.mjs.map +1 -1
  140. package/dist/server.d.mts +2 -2
  141. package/dist/server.mjs +2 -2
  142. package/package.json +73 -20
  143. package/dist/internal/route/sw-script.d.mts +0 -4
  144. package/dist/internal/route/sw-script.d.mts.map +0 -1
  145. package/dist/internal/route/sw-script.mjs +0 -38
  146. package/dist/internal/route/sw-script.mjs.map +0 -1
  147. package/dist/internal/server/dedupe.d.mts +0 -5
  148. package/dist/internal/server/dedupe.d.mts.map +0 -1
  149. package/dist/internal/server/dedupe.mjs +0 -11
  150. package/dist/internal/server/dedupe.mjs.map +0 -1
  151. package/dist/internal/server/envelope.d.mts +0 -14
  152. package/dist/internal/server/envelope.d.mts.map +0 -1
  153. package/dist/internal/server/envelope.mjs +0 -59
  154. package/dist/internal/server/envelope.mjs.map +0 -1
  155. package/dist/internal/server/normalize-request.d.mts +0 -7
  156. package/dist/internal/server/normalize-request.d.mts.map +0 -1
  157. package/dist/internal/server/normalize-request.mjs +0 -50
  158. package/dist/internal/server/normalize-request.mjs.map +0 -1
  159. package/dist/internal/server/runtime.d.mts +0 -14
  160. package/dist/internal/server/runtime.d.mts.map +0 -1
  161. package/dist/internal/server/runtime.mjs +0 -18
  162. package/dist/internal/server/runtime.mjs.map +0 -1
  163. package/dist/internal/server/transport.d.mts +0 -12
  164. package/dist/internal/server/transport.d.mts.map +0 -1
  165. package/dist/internal/server/transport.mjs +0 -26
  166. package/dist/internal/server/transport.mjs.map +0 -1
@@ -0,0 +1,165 @@
1
+ import { readInterfereEnv } from "./internal/env.mjs";
2
+ import { resolveReleaseSlug } from "./internal/release-slug.mjs";
3
+ import { PRODUCER_VERSION } from "./internal/version.mjs";
4
+ import { bridgeConsoleToOtel } from "./internal/server/console-bridge.mjs";
5
+ import { PrerenderSafeIdGenerator } from "./internal/server/id-generator.mjs";
6
+ import { fetchAndCacheRemoteConfig } from "./internal/server/remote-config.mjs";
7
+ import { metrics, propagation } from "@opentelemetry/api";
8
+ import { BaggageSpanProcessor } from "@opentelemetry/baggage-span-processor";
9
+ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
10
+ import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from "@opentelemetry/core";
11
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
12
+ import { AggregationTemporalityPreference, OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
13
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
14
+ import { registerInstrumentations } from "@opentelemetry/instrumentation";
15
+ import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
16
+ import { resourceFromAttributes } from "@opentelemetry/resources";
17
+ import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
18
+ import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
19
+ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
20
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
21
+ //#region src/instrumentation.ts
22
+ const DEFAULT_SERVICE_NAME = "interfere-sdk-next-server";
23
+ const SERVICE_NAMESPACE = "interfere";
24
+ let registered = false;
25
+ function matchesAny(url, patterns) {
26
+ for (const pattern of patterns) if (pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern)) return true;
27
+ return false;
28
+ }
29
+ /**
30
+ * Pure factory — no global registration, no provider construction. Returns
31
+ * the OTel primitives the SDK contributes so a host bootstrap (Vercel,
32
+ * DataDog, custom) can compose them in.
33
+ *
34
+ * Returns `null` when `INTERFERE_API_KEY` isn't set, so callers can
35
+ * unconditionally spread `kit?.spanProcessors ?? []` in dev where the
36
+ * SDK isn't configured.
37
+ */
38
+ function buildInterfereOtelKit(opts = {}) {
39
+ const env = readInterfereEnv();
40
+ if (!env.apiKey) return null;
41
+ const { slug: releaseSlug } = resolveReleaseSlug();
42
+ if (!releaseSlug) console.warn("[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env.");
43
+ const sinkUrl = `${env.apiUrl}/v2/sink`;
44
+ const ignoreUrls = [sinkUrl, ...opts.ignoreUrls ?? []];
45
+ const propagateContextUrls = opts.propagateContextUrls ?? [];
46
+ const exporterHeaders = {
47
+ "x-api-key": env.apiKey,
48
+ "x-interfere-producer-version": PRODUCER_VERSION
49
+ };
50
+ return {
51
+ resourceAttributes: {
52
+ "service.name": opts.serviceName ?? DEFAULT_SERVICE_NAME,
53
+ "service.namespace": SERVICE_NAMESPACE,
54
+ "deployment.environment.name": env.nodeEnvironment ?? "unknown",
55
+ "telemetry.sdk.language": "nodejs",
56
+ "interfere.sdk.name": "@interfere/next",
57
+ "interfere.sdk.version": PRODUCER_VERSION,
58
+ ...releaseSlug ? { "release.slug": releaseSlug } : {}
59
+ },
60
+ spanProcessors: [
61
+ new BaggageSpanProcessor(() => true),
62
+ new BatchSpanProcessor(new OTLPTraceExporter({
63
+ url: sinkUrl,
64
+ headers: exporterHeaders
65
+ })),
66
+ ...opts._internalAdditionalSpanProcessors ?? []
67
+ ],
68
+ logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter({
69
+ url: sinkUrl,
70
+ headers: exporterHeaders
71
+ })), ...opts._internalAdditionalLogRecordProcessors ?? []],
72
+ metricReaders: [new PeriodicExportingMetricReader({
73
+ exporter: new OTLPMetricExporter({
74
+ url: sinkUrl,
75
+ headers: exporterHeaders,
76
+ temporalityPreference: AggregationTemporalityPreference.DELTA
77
+ }),
78
+ exportIntervalMillis: 3e4
79
+ }), ...opts._internalAdditionalMetricReaders ?? []],
80
+ propagators: [new W3CBaggagePropagator()],
81
+ instrumentations: [new UndiciInstrumentation({
82
+ ignoreRequestHook: (req) => {
83
+ return matchesAny(`${req.origin}${req.path}`, ignoreUrls);
84
+ },
85
+ requestHook: (span, req) => {
86
+ if (matchesAny(`${req.origin}${req.path}`, propagateContextUrls)) span.setAttribute("interfere.propagated", true);
87
+ }
88
+ })],
89
+ enableConsoleBridge: () => bridgeConsoleToOtel(),
90
+ fetchRemoteConfig: () => fetchAndCacheRemoteConfig()
91
+ };
92
+ }
93
+ /**
94
+ * One-line server-side bootstrap for Next.js. Customers' `instrumentation.ts`
95
+ * becomes:
96
+ *
97
+ * ```ts
98
+ * export { register } from "@interfere/next/instrumentation";
99
+ * ```
100
+ *
101
+ * Or, with overrides:
102
+ *
103
+ * ```ts
104
+ * import { register as base } from "@interfere/next/instrumentation";
105
+ * export const register = () => base({ serviceName: "@my-org/api" });
106
+ * ```
107
+ *
108
+ * Customers who already run another OTel bootstrap (`@vercel/otel`,
109
+ * DataDog `dd-trace-js`, custom `NodeTracerProvider`) should use
110
+ * `buildInterfereOtelKit({...})` instead and compose the returned
111
+ * processors / readers / propagators into their existing setup —
112
+ * see the kit's JSDoc for examples.
113
+ *
114
+ * Constructs private `NodeTracerProvider` / `LoggerProvider` /
115
+ * `MeterProvider` and registers them on the global slots. Last writer
116
+ * wins on the globals, so customers running another OTel bootstrap
117
+ * alongside this one will end up with whichever booted last; the kit
118
+ * path avoids that fight entirely.
119
+ *
120
+ * Wired against the Node OTel SDK (`NodeTracerProvider`,
121
+ * `AsyncLocalStorageContextManager`) — so this entry only ships into the
122
+ * `nodejs` runtime bundle. The Edge runtime gets the no-op stub at
123
+ * `instrumentation.edge.ts` via `package.json`'s `edge-light` /
124
+ * `workerd` export conditions.
125
+ *
126
+ * Idempotent — repeat calls (e.g. HMR) short-circuit on the
127
+ * module-scoped `registered` flag.
128
+ *
129
+ * Bails silently when `INTERFERE_API_KEY` is unset so dev runs without
130
+ * the SDK configured don't crash.
131
+ */
132
+ async function register(opts = {}) {
133
+ if (registered) return;
134
+ const kit = buildInterfereOtelKit(opts);
135
+ if (!kit) return;
136
+ const resource = resourceFromAttributes(kit.resourceAttributes);
137
+ const tracerProvider = new NodeTracerProvider({
138
+ resource,
139
+ spanProcessors: kit.spanProcessors,
140
+ idGenerator: new PrerenderSafeIdGenerator()
141
+ });
142
+ if (propagation.fields().length === 0) propagation.setGlobalPropagator(new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), ...kit.propagators] }));
143
+ tracerProvider.register({ contextManager: new AsyncLocalStorageContextManager().enable() });
144
+ const loggerProvider = new LoggerProvider({
145
+ resource,
146
+ processors: kit.logRecordProcessors
147
+ });
148
+ const { logs: logsApi } = await import("@opentelemetry/api-logs");
149
+ logsApi.setGlobalLoggerProvider(loggerProvider);
150
+ const meterProvider = new MeterProvider({
151
+ resource,
152
+ readers: kit.metricReaders
153
+ });
154
+ metrics.setGlobalMeterProvider(meterProvider);
155
+ registerInstrumentations({
156
+ tracerProvider,
157
+ meterProvider,
158
+ instrumentations: kit.instrumentations
159
+ });
160
+ if (opts.consoleBridge !== false) kit.enableConsoleBridge();
161
+ registered = true;
162
+ await kit.fetchRemoteConfig();
163
+ }
164
+ //#endregion
165
+ export { PrerenderSafeIdGenerator, buildInterfereOtelKit, register };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.mjs","names":[],"sources":["../src/instrumentation.ts"],"sourcesContent":["import type {\n AttributeValue,\n Span,\n TextMapPropagator,\n} from \"@opentelemetry/api\";\nimport { metrics, propagation } from \"@opentelemetry/api\";\nimport { BaggageSpanProcessor } from \"@opentelemetry/baggage-span-processor\";\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport {\n CompositePropagator,\n W3CBaggagePropagator,\n W3CTraceContextPropagator,\n} from \"@opentelemetry/core\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\nimport {\n AggregationTemporalityPreference,\n OTLPMetricExporter,\n} from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n type Instrumentation,\n registerInstrumentations,\n} from \"@opentelemetry/instrumentation\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n type LogRecordProcessor,\n} from \"@opentelemetry/sdk-logs\";\nimport {\n MeterProvider,\n type MetricReader,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\n\nimport { readInterfereEnv } from \"./internal/env.js\";\nimport { resolveReleaseSlug } from \"./internal/release-slug.js\";\nimport { bridgeConsoleToOtel } from \"./internal/server/console-bridge.js\";\nimport { PrerenderSafeIdGenerator } from \"./internal/server/id-generator.js\";\nimport type { ServerInstrumentationOptions } from \"./internal/server/instrumentation-options.js\";\nimport { fetchAndCacheRemoteConfig } from \"./internal/server/remote-config.js\";\nimport { PRODUCER_VERSION } from \"./internal/version.js\";\n\nexport { PrerenderSafeIdGenerator } from \"./internal/server/id-generator.js\";\nexport type { ServerInstrumentationOptions } from \"./internal/server/instrumentation-options.js\";\n\nconst DEFAULT_SERVICE_NAME = \"interfere-sdk-next-server\";\nconst SERVICE_NAMESPACE = \"interfere\";\n\nlet registered = false;\n\nfunction matchesAny(url: string, patterns: (string | RegExp)[]): boolean {\n for (const pattern of patterns) {\n if (pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Composable building blocks the Interfere SDK contributes to OTel —\n * span / log / metric processors, baggage propagator, undici\n * instrumentation, plus the resource attributes and side-effect\n * installers (console bridge + remote-config fetch).\n *\n * Use this when you want to plug Interfere into an existing OTel\n * bootstrap (e.g. `@vercel/otel`'s `registerOTel({...})`, DataDog's\n * `dd-trace-js`, or any custom `NodeTracerProvider` setup) instead of\n * the standalone `register()` path. The kit holds no global state and\n * touches no global slots — composition is the consumer's job.\n *\n * The standalone `register()` builds the kit internally and wires it\n * into private providers, so customer DX stays a one-liner export.\n *\n * Returns `null` when `INTERFERE_API_KEY` is unset — callers can spread\n * `kit?.spanProcessors ?? []` without a no-op guard.\n */\nexport interface InterfereOtelKit {\n /**\n * Patches `console.{debug,log,info,warn,error}` to also emit OTel\n * `LogRecord`s via the global `LoggerProvider`. Caller is responsible\n * for ensuring a `LoggerProvider` is registered globally before\n * calling — `register()` handles this; for composed setups, call\n * after your bootstrap (`registerOTel({...})` etc.) has run. Returns\n * a disposer that restores the original `console.*` methods.\n */\n enableConsoleBridge: () => () => void;\n /**\n * Fetches the SDK's remote plugin config and caches it for the\n * runtime. Customer code's `isPluginEnabled(...)` checks read from\n * the cache. Safe to call repeatedly.\n */\n fetchRemoteConfig: () => Promise<void>;\n /**\n * Instrumentations the SDK ships. Currently `UndiciInstrumentation`\n * pre-configured with `propagateContextUrls` / `ignoreUrls` semantics\n * from `ServerInstrumentationOptions`.\n *\n * **Composition with `@vercel/otel`**: `instrumentations: [\"auto\"]`\n * registers Vercel's `globalThis.fetch` wrapper, which only\n * auto-propagates `traceparent` to Vercel deployment URLs — outbound\n * fetches to a different origin (e.g. `in.interfere.com`) get a\n * span but no header injection unless\n * `instrumentationConfig.fetch.propagateContextUrls` is set, and\n * even then it doesn't cover the lower-level undici dispatcher path\n * Next.js route handlers use. Spread `kit.instrumentations` after\n * `\"auto\"` to layer this UndiciInstrumentation on top — propagation\n * extends to every outbound undici call (filtered via `ignoreUrls`).\n * Risk of double span emission on overlapping call sites is bounded:\n * Vercel's fetch wraps `globalThis.fetch`, this hooks the undici\n * dispatcher one layer lower, so duplicates only appear when both\n * paths actually fire on the same call.\n */\n instrumentations: Instrumentation[];\n logRecordProcessors: LogRecordProcessor[];\n metricReaders: MetricReader[];\n /**\n * W3C baggage propagator. Trace context is intentionally *not*\n * included — `@vercel/otel`'s `propagators: [\"auto\"]` already\n * registers it, and standalone `register()` adds it itself when\n * the global slot is empty. Composing this on top extends rather\n * than overrides.\n */\n propagators: TextMapPropagator[];\n /**\n * Pre-built resource attribute set. Pass into your provider's\n * `Resource` directly, or into `@vercel/otel`'s `attributes` field\n * (which merges with Vercel's own auto-detected `vercel.*` attrs).\n */\n resourceAttributes: Record<string, AttributeValue>;\n spanProcessors: SpanProcessor[];\n}\n\n/**\n * Pure factory — no global registration, no provider construction. Returns\n * the OTel primitives the SDK contributes so a host bootstrap (Vercel,\n * DataDog, custom) can compose them in.\n *\n * Returns `null` when `INTERFERE_API_KEY` isn't set, so callers can\n * unconditionally spread `kit?.spanProcessors ?? []` in dev where the\n * SDK isn't configured.\n */\nexport function buildInterfereOtelKit(\n opts: ServerInstrumentationOptions = {}\n): InterfereOtelKit | null {\n const env = readInterfereEnv();\n if (!env.apiKey) {\n return null;\n }\n\n const { slug: releaseSlug } = resolveReleaseSlug();\n if (!releaseSlug) {\n console.warn(\n \"[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env.\"\n );\n }\n\n const sinkUrl = `${env.apiUrl}/v2/sink`;\n // Always-on ignore: the OTLP exporter posting to /v2/sink would\n // otherwise be re-traced by UndiciInstrumentation in an infinite\n // loop.\n const ignoreUrls = [sinkUrl, ...(opts.ignoreUrls ?? [])];\n const propagateContextUrls = opts.propagateContextUrls ?? [];\n\n const exporterHeaders: Record<string, string> = {\n \"x-api-key\": env.apiKey,\n \"x-interfere-producer-version\": PRODUCER_VERSION,\n };\n\n const resourceAttributes: Record<string, AttributeValue> = {\n \"service.name\": opts.serviceName ?? DEFAULT_SERVICE_NAME,\n \"service.namespace\": SERVICE_NAMESPACE,\n \"deployment.environment.name\": env.nodeEnvironment ?? \"unknown\",\n \"telemetry.sdk.language\": \"nodejs\",\n \"interfere.sdk.name\": \"@interfere/next\",\n \"interfere.sdk.version\": PRODUCER_VERSION,\n ...(releaseSlug ? { \"release.slug\": releaseSlug } : {}),\n };\n\n // `BaggageSpanProcessor` runs at span start (before the batch\n // processor schedules export) so every span carries the full baggage\n // attribute set when batched. Default `keyFilter` accepts everything;\n // browser SDK only writes `interfere.*`-prefixed entries via baggage\n // propagation, so the server side stays scoped without an explicit\n // filter.\n const spanProcessors: SpanProcessor[] = [\n new BaggageSpanProcessor(() => true),\n new BatchSpanProcessor(\n new OTLPTraceExporter({ url: sinkUrl, headers: exporterHeaders })\n ),\n ...(opts._internalAdditionalSpanProcessors ?? []),\n ];\n\n const logRecordProcessors: LogRecordProcessor[] = [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({ url: sinkUrl, headers: exporterHeaders })\n ),\n ...(opts._internalAdditionalLogRecordProcessors ?? []),\n ];\n\n // `DELTA` temporality matches what `@vercel/otel`'s default\n // `metricReader: \"auto\"` used to install (see vercel/otel\n // `defaultMetricReader`) so the wire shape downstream consumers\n // (ClickHouse via Tinybird, the OTel collector's BetterStack export)\n // receive is unchanged across the migration. The 30s interval matches\n // the browser SDK's primary metric reader so server + client\n // datapoints land at the same cadence.\n const metricReaders: MetricReader[] = [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: sinkUrl,\n headers: exporterHeaders,\n temporalityPreference: AggregationTemporalityPreference.DELTA,\n }),\n exportIntervalMillis: 30_000,\n }),\n ...(opts._internalAdditionalMetricReaders ?? []),\n ];\n\n // Just W3C baggage. Trace context is contributed by the host\n // bootstrap (`@vercel/otel`'s `propagators: [\"auto\"]`, or the\n // CompositePropagator standalone `register()` constructs). Composing\n // this on top extends rather than overrides.\n const propagators: TextMapPropagator[] = [new W3CBaggagePropagator()];\n\n // Standalone-path instrumentations. Conflicts with `@vercel/otel`'s\n // `instrumentations: [\"auto\"]` (Vercel ships its own runtime-aware\n // fetch instrumentation) — see the JSDoc on `InterfereOtelKit`.\n const instrumentations: Instrumentation[] = [\n new UndiciInstrumentation({\n ignoreRequestHook: (req: { origin: string; path: string }) => {\n const url = `${req.origin}${req.path}`;\n return matchesAny(url, ignoreUrls);\n },\n requestHook: (span: Span, req: { origin: string; path: string }) => {\n const url = `${req.origin}${req.path}`;\n if (matchesAny(url, propagateContextUrls)) {\n span.setAttribute(\"interfere.propagated\", true);\n }\n },\n }),\n ];\n\n return {\n resourceAttributes,\n spanProcessors,\n logRecordProcessors,\n metricReaders,\n propagators,\n instrumentations,\n enableConsoleBridge: () => bridgeConsoleToOtel(),\n fetchRemoteConfig: () => fetchAndCacheRemoteConfig(),\n };\n}\n\n/**\n * One-line server-side bootstrap for Next.js. Customers' `instrumentation.ts`\n * becomes:\n *\n * ```ts\n * export { register } from \"@interfere/next/instrumentation\";\n * ```\n *\n * Or, with overrides:\n *\n * ```ts\n * import { register as base } from \"@interfere/next/instrumentation\";\n * export const register = () => base({ serviceName: \"@my-org/api\" });\n * ```\n *\n * Customers who already run another OTel bootstrap (`@vercel/otel`,\n * DataDog `dd-trace-js`, custom `NodeTracerProvider`) should use\n * `buildInterfereOtelKit({...})` instead and compose the returned\n * processors / readers / propagators into their existing setup —\n * see the kit's JSDoc for examples.\n *\n * Constructs private `NodeTracerProvider` / `LoggerProvider` /\n * `MeterProvider` and registers them on the global slots. Last writer\n * wins on the globals, so customers running another OTel bootstrap\n * alongside this one will end up with whichever booted last; the kit\n * path avoids that fight entirely.\n *\n * Wired against the Node OTel SDK (`NodeTracerProvider`,\n * `AsyncLocalStorageContextManager`) — so this entry only ships into the\n * `nodejs` runtime bundle. The Edge runtime gets the no-op stub at\n * `instrumentation.edge.ts` via `package.json`'s `edge-light` /\n * `workerd` export conditions.\n *\n * Idempotent — repeat calls (e.g. HMR) short-circuit on the\n * module-scoped `registered` flag.\n *\n * Bails silently when `INTERFERE_API_KEY` is unset so dev runs without\n * the SDK configured don't crash.\n */\nexport async function register(\n opts: ServerInstrumentationOptions = {}\n): Promise<void> {\n if (registered) {\n return;\n }\n\n const kit = buildInterfereOtelKit(opts);\n if (!kit) {\n return;\n }\n\n const resource = resourceFromAttributes(kit.resourceAttributes);\n\n // Next 16's prerender extension throws on `Math.random()` /\n // `crypto.getRandomValues()` calls inside a Server Component that hasn't\n // awaited Request data first. OTel's default `RandomIdGenerator` calls\n // `Math.random()` for every span ID, so any span Next opens around a\n // prerendered route render blows up the build with\n // `next-prerender-random`. The counter-based generator below seeds its\n // randomness once at register-time (outside any prerender ALS frame) and\n // keeps span minting deterministic afterwards.\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors: kit.spanProcessors,\n idGenerator: new PrerenderSafeIdGenerator(),\n });\n\n // Composed W3C trace context + baggage. `UndiciInstrumentation`\n // injects both `traceparent` and `baggage` on outgoing requests\n // matching `propagateContextUrls`, so cross-process correlation +\n // customer baggage propagation work without per-call code.\n //\n // Only set the global propagator when none is registered — customers\n // who installed their own propagator (B3, Jaeger, composite, ...) are\n // preserved. Mirrors the browser-side guard in\n // `react/internal/otel/provider.ts`.\n if (propagation.fields().length === 0) {\n propagation.setGlobalPropagator(\n new CompositePropagator({\n propagators: [new W3CTraceContextPropagator(), ...kit.propagators],\n })\n );\n }\n\n tracerProvider.register({\n contextManager: new AsyncLocalStorageContextManager().enable(),\n });\n\n const loggerProvider = new LoggerProvider({\n resource,\n processors: kit.logRecordProcessors,\n });\n // `logs.setGlobalLoggerProvider` is the single global slot OTel\n // exposes. Customers with their own LoggerProvider boot last and\n // win; we accept the trade-off — same constraint we live with on\n // the global propagator and context manager.\n const { logs: logsApi } = await import(\"@opentelemetry/api-logs\");\n logsApi.setGlobalLoggerProvider(loggerProvider);\n\n const meterProvider = new MeterProvider({\n resource,\n readers: kit.metricReaders,\n });\n // `MeterProvider` has no `register()` method — wire it into the\n // single global slot manually. Same trade-off as\n // `setGlobalLoggerProvider`: customers with their own MeterProvider\n // boot last and win.\n metrics.setGlobalMeterProvider(meterProvider);\n\n // Pass the meter provider explicitly so `UndiciInstrumentation`\n // records `http.client.request.duration` histograms against it.\n registerInstrumentations({\n tracerProvider,\n meterProvider,\n instrumentations: kit.instrumentations,\n });\n\n if (opts.consoleBridge !== false) {\n kit.enableConsoleBridge();\n }\n\n registered = true;\n\n // Preserve the existing `register` semantics from `@interfere/next/server` —\n // remote config gates plugin enable/disable. Customers migrating to this\n // subpath shouldn't lose that behaviour.\n await kit.fetchRemoteConfig();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoDA,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAE1B,IAAI,aAAa;AAEjB,SAAS,WAAW,KAAa,UAAwC;CACvE,KAAK,MAAM,WAAW,UACpB,IAAI,mBAAmB,SAAS,QAAQ,KAAK,IAAI,GAAG,IAAI,SAAS,QAAQ,EACvE,OAAO;CAGX,OAAO;;;;;;;;;;;AAsFT,SAAgB,sBACd,OAAqC,EAAE,EACd;CACzB,MAAM,MAAM,kBAAkB;CAC9B,IAAI,CAAC,IAAI,QACP,OAAO;CAGT,MAAM,EAAE,MAAM,gBAAgB,oBAAoB;CAClD,IAAI,CAAC,aACH,QAAQ,KACN,sLACD;CAGH,MAAM,UAAU,GAAG,IAAI,OAAO;CAI9B,MAAM,aAAa,CAAC,SAAS,GAAI,KAAK,cAAc,EAAE,CAAE;CACxD,MAAM,uBAAuB,KAAK,wBAAwB,EAAE;CAE5D,MAAM,kBAA0C;EAC9C,aAAa,IAAI;EACjB,gCAAgC;EACjC;CA4ED,OAAO;EACL,oBAAA;GA1EA,gBAAgB,KAAK,eAAe;GACpC,qBAAqB;GACrB,+BAA+B,IAAI,mBAAmB;GACtD,0BAA0B;GAC1B,sBAAsB;GACtB,yBAAyB;GACzB,GAAI,cAAc,EAAE,gBAAgB,aAAa,GAAG,EAAE;GAoEpC;EAClB,gBAAA;GA3DA,IAAI,2BAA2B,KAAK;GACpC,IAAI,mBACF,IAAI,kBAAkB;IAAE,KAAK;IAAS,SAAS;IAAiB,CAAC,CAClE;GACD,GAAI,KAAK,qCAAqC,EAAE;GAuDlC;EACd,qBAAA,CApDA,IAAI,wBACF,IAAI,gBAAgB;GAAE,KAAK;GAAS,SAAS;GAAiB,CAAC,CAChE,EACD,GAAI,KAAK,0CAA0C,EAAE,CAiDlC;EACnB,eAAA,CAvCA,IAAI,8BAA8B;GAChC,UAAU,IAAI,mBAAmB;IAC/B,KAAK;IACL,SAAS;IACT,uBAAuB,iCAAiC;IACzD,CAAC;GACF,sBAAsB;GACvB,CAAC,EACF,GAAI,KAAK,oCAAoC,EAAE,CA+BlC;EACb,aAAA,CAzBwC,IAAI,sBAAsB,CAyBvD;EACX,kBAAA,CApBA,IAAI,sBAAsB;GACxB,oBAAoB,QAA0C;IAE5D,OAAO,WAAW,GADH,IAAI,SAAS,IAAI,QACT,WAAW;;GAEpC,cAAc,MAAY,QAA0C;IAElE,IAAI,WAAW,GADA,IAAI,SAAS,IAAI,QACZ,qBAAqB,EACvC,KAAK,aAAa,wBAAwB,KAAK;;GAGpD,CAAC,CASc;EAChB,2BAA2B,qBAAqB;EAChD,yBAAyB,2BAA2B;EACrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CH,eAAsB,SACpB,OAAqC,EAAE,EACxB;CACf,IAAI,YACF;CAGF,MAAM,MAAM,sBAAsB,KAAK;CACvC,IAAI,CAAC,KACH;CAGF,MAAM,WAAW,uBAAuB,IAAI,mBAAmB;CAU/D,MAAM,iBAAiB,IAAI,mBAAmB;EAC5C;EACA,gBAAgB,IAAI;EACpB,aAAa,IAAI,0BAA0B;EAC5C,CAAC;CAWF,IAAI,YAAY,QAAQ,CAAC,WAAW,GAClC,YAAY,oBACV,IAAI,oBAAoB,EACtB,aAAa,CAAC,IAAI,2BAA2B,EAAE,GAAG,IAAI,YAAY,EACnE,CAAC,CACH;CAGH,eAAe,SAAS,EACtB,gBAAgB,IAAI,iCAAiC,CAAC,QAAQ,EAC/D,CAAC;CAEF,MAAM,iBAAiB,IAAI,eAAe;EACxC;EACA,YAAY,IAAI;EACjB,CAAC;CAKF,MAAM,EAAE,MAAM,YAAY,MAAM,OAAO;CACvC,QAAQ,wBAAwB,eAAe;CAE/C,MAAM,gBAAgB,IAAI,cAAc;EACtC;EACA,SAAS,IAAI;EACd,CAAC;CAKF,QAAQ,uBAAuB,cAAc;CAI7C,yBAAyB;EACvB;EACA;EACA,kBAAkB,IAAI;EACvB,CAAC;CAEF,IAAI,KAAK,kBAAkB,OACzB,IAAI,qBAAqB;CAG3B,aAAa;CAKb,MAAM,IAAI,mBAAmB"}
@@ -2,9 +2,8 @@ import { NextConfig } from "next";
2
2
 
3
3
  //#region src/internal/build/configure-build.d.ts
4
4
  interface InterfereInjectedValues {
5
- readonly __INTERFERE_BUILD_ID__: string | null;
6
5
  readonly __INTERFERE_FORCE_ENABLE__?: boolean;
7
- readonly __INTERFERE_RELEASE_ID__: string | null;
6
+ readonly __INTERFERE_RELEASE_SLUG__: string | null;
8
7
  }
9
8
  interface ConfigureBuildInput {
10
9
  readonly existingTurbopack: NextConfig["turbopack"] | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"configure-build.d.mts","names":[],"sources":["../../../src/internal/build/configure-build.ts"],"mappings":";;;UAKiB,uBAAA;EAAA,SACN,sBAAA;EAAA,SACA,0BAAA;EAAA,SACA,wBAAA;AAAA;AAAA,UAqBD,mBAAA;EAAA,SACC,iBAAA,EAAmB,UAAA;EAAA,SACnB,eAAA,EAAiB,UAAA;EAAA,SACjB,UAAA;EAAA,SACA,MAAA,EAAQ,uBAAA;AAAA;AAAA,UAGT,oBAAA;EAAA,SACC,SAAA,EAAW,UAAA;EAAA,SACX,OAAA,EAAS,UAAA;AAAA;AAAA,iBAGJ,cAAA,CAAA;EACd,eAAA;EACA,iBAAA;EACA,UAAA;EACA;AAAA,GACC,mBAAA,GAAsB,oBAAA"}
1
+ {"version":3,"file":"configure-build.d.mts","names":[],"sources":["../../../src/internal/build/configure-build.ts"],"mappings":";;;UAKiB,uBAAA;EAAA,SACN,0BAAA;EAAA,SACA,0BAAA;AAAA;AAAA,UAqBD,mBAAA;EAAA,SACC,iBAAA,EAAmB,UAAA;EAAA,SACnB,eAAA,EAAiB,UAAA;EAAA,SACjB,UAAA;EAAA,SACA,MAAA,EAAQ,uBAAA;AAAA;AAAA,UAGT,oBAAA;EAAA,SACC,SAAA,EAAW,UAAA;EAAA,SACX,OAAA,EAAS,UAAA;AAAA;AAAA,iBAGJ,cAAA,CAAA;EACd,eAAA;EACA,iBAAA;EACA,UAAA;EACA;AAAA,GACC,mBAAA,GAAsB,oBAAA"}
@@ -26,7 +26,7 @@ function configureBuild({ existingWebpack, existingTurbopack, projectDir, values
26
26
  };
27
27
  }
28
28
  function hasInjectedMetadata(metadata) {
29
- return metadata.__INTERFERE_BUILD_ID__ !== null || metadata.__INTERFERE_RELEASE_ID__ !== null;
29
+ return metadata.__INTERFERE_RELEASE_SLUG__ !== null;
30
30
  }
31
31
  function buildWebpackHook(existingWebpack, instrumentationClientPath, values) {
32
32
  if (!(instrumentationClientPath && hasInjectedMetadata(values))) return existingWebpack;
@@ -49,13 +49,21 @@ function buildWebpackHook(existingWebpack, instrumentationClientPath, values) {
49
49
  };
50
50
  }
51
51
  function buildTurbopackConfig(existingTurbopack, instrumentationClientPath, values) {
52
- if (!(instrumentationClientPath && hasInjectedMetadata(values))) return existingTurbopack;
53
52
  const baseConfig = existingTurbopack ?? {};
53
+ const debugIds = baseConfig.debugIds ?? true;
54
+ if (!(instrumentationClientPath && hasInjectedMetadata(values))) {
55
+ if (existingTurbopack === void 0 && debugIds === baseConfig.debugIds) return existingTurbopack;
56
+ return {
57
+ ...baseConfig,
58
+ debugIds
59
+ };
60
+ }
54
61
  const ruleKey = `**/${basename(instrumentationClientPath)}`;
55
62
  const existingRule = baseConfig.rules?.[ruleKey] ?? {};
56
63
  const existingLoaders = Array.isArray(existingRule.loaders) ? existingRule.loaders : [];
57
64
  return {
58
65
  ...baseConfig,
66
+ debugIds,
59
67
  rules: {
60
68
  ...baseConfig.rules,
61
69
  [ruleKey]: {
@@ -1 +1 @@
1
- {"version":3,"file":"configure-build.mjs","names":[],"sources":["../../../src/internal/build/configure-build.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { basename, dirname, join, normalize, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { NextConfig } from \"next\";\n\nexport interface InterfereInjectedValues {\n readonly __INTERFERE_BUILD_ID__: string | null;\n readonly __INTERFERE_FORCE_ENABLE__?: boolean;\n readonly __INTERFERE_RELEASE_ID__: string | null;\n}\n\nconst INSTRUMENTATION_CLIENT_FILES = [\n \"instrumentation-client.ts\",\n \"instrumentation-client.tsx\",\n \"instrumentation-client.js\",\n \"instrumentation-client.jsx\",\n \"src/instrumentation-client.ts\",\n \"src/instrumentation-client.tsx\",\n \"src/instrumentation-client.js\",\n \"src/instrumentation-client.jsx\",\n] as const;\n\nconst LOADER_FILE_CANDIDATES = [\n \"value-injection-loader.ts\",\n \"value-injection-loader.js\",\n \"value-injection-loader.mjs\",\n \"value-injection-loader.cjs\",\n] as const;\n\ninterface ConfigureBuildInput {\n readonly existingTurbopack: NextConfig[\"turbopack\"] | undefined;\n readonly existingWebpack: NextConfig[\"webpack\"] | undefined;\n readonly projectDir: string;\n readonly values: InterfereInjectedValues;\n}\n\ninterface ConfigureBuildOutput {\n readonly turbopack: NextConfig[\"turbopack\"] | undefined;\n readonly webpack: NextConfig[\"webpack\"] | undefined;\n}\n\nexport function configureBuild({\n existingWebpack,\n existingTurbopack,\n projectDir,\n values,\n}: ConfigureBuildInput): ConfigureBuildOutput {\n const instrumentationClientPath =\n resolveInstrumentationClientPath(projectDir);\n\n return {\n webpack: buildWebpackHook(\n existingWebpack,\n instrumentationClientPath,\n values\n ),\n turbopack: buildTurbopackConfig(\n existingTurbopack,\n instrumentationClientPath,\n values\n ),\n };\n}\n\nfunction hasInjectedMetadata(metadata: InterfereInjectedValues): boolean {\n return (\n metadata.__INTERFERE_BUILD_ID__ !== null ||\n metadata.__INTERFERE_RELEASE_ID__ !== null\n );\n}\n\nfunction buildWebpackHook(\n existingWebpack: NextConfig[\"webpack\"] | undefined,\n instrumentationClientPath: string | null,\n values: InterfereInjectedValues\n): NextConfig[\"webpack\"] | undefined {\n if (!(instrumentationClientPath && hasInjectedMetadata(values))) {\n return existingWebpack;\n }\n\n const normalizedPath = normalize(instrumentationClientPath);\n const loaderPath = resolveLoaderPath();\n\n return (webpackConfig, options) => {\n const outputConfig = existingWebpack\n ? existingWebpack(webpackConfig, options)\n : webpackConfig;\n const mutableConfig = outputConfig as {\n module?: { rules?: unknown[] };\n };\n\n const injectionRule = {\n test: (resource: string): boolean =>\n Boolean(resource) && normalize(resource) === normalizedPath,\n use: [\n {\n loader: loaderPath,\n options: { values },\n },\n ],\n };\n\n mutableConfig.module ??= {};\n mutableConfig.module.rules ??= [];\n mutableConfig.module.rules.push(injectionRule);\n\n return outputConfig;\n };\n}\n\nfunction buildTurbopackConfig(\n existingTurbopack: NextConfig[\"turbopack\"] | undefined,\n instrumentationClientPath: string | null,\n values: InterfereInjectedValues\n): NextConfig[\"turbopack\"] | undefined {\n if (!(instrumentationClientPath && hasInjectedMetadata(values))) {\n return existingTurbopack;\n }\n\n const baseConfig = (existingTurbopack ?? {}) as {\n rules?: Record<string, unknown>;\n };\n const ruleKey = `**/${basename(instrumentationClientPath)}`;\n const existingRule = (baseConfig.rules?.[ruleKey] ?? {}) as {\n loaders?: unknown[];\n };\n const existingLoaders = Array.isArray(existingRule.loaders)\n ? existingRule.loaders\n : [];\n\n return {\n ...baseConfig,\n rules: {\n ...baseConfig.rules,\n [ruleKey]: {\n ...existingRule,\n loaders: [\n ...existingLoaders,\n {\n loader: resolveLoaderPath(),\n options: {\n serializedValues: JSON.stringify(values),\n },\n },\n ],\n },\n },\n } as NextConfig[\"turbopack\"];\n}\n\nfunction resolveInstrumentationClientPath(projectDir: string): string | null {\n for (const candidate of INSTRUMENTATION_CLIENT_FILES) {\n const filePath = resolve(projectDir, candidate);\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n return null;\n}\n\nfunction resolveLoaderPath(): string {\n const directory = resolve(dirname(fileURLToPath(import.meta.url)));\n\n for (const candidate of LOADER_FILE_CANDIDATES) {\n const filePath = join(directory, candidate);\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n return join(directory, \"value-injection-loader.js\");\n}\n"],"mappings":";;;;AAWA,MAAM,+BAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACD;AAcD,SAAgB,eAAe,EAC7B,iBACA,mBACA,YACA,UAC4C;CAC5C,MAAM,4BACJ,iCAAiC,WAAW;AAE9C,QAAO;EACL,SAAS,iBACP,iBACA,2BACA,OACD;EACD,WAAW,qBACT,mBACA,2BACA,OACD;EACF;;AAGH,SAAS,oBAAoB,UAA4C;AACvE,QACE,SAAS,2BAA2B,QACpC,SAAS,6BAA6B;;AAI1C,SAAS,iBACP,iBACA,2BACA,QACmC;AACnC,KAAI,EAAE,6BAA6B,oBAAoB,OAAO,EAC5D,QAAO;CAGT,MAAM,iBAAiB,UAAU,0BAA0B;CAC3D,MAAM,aAAa,mBAAmB;AAEtC,SAAQ,eAAe,YAAY;EACjC,MAAM,eAAe,kBACjB,gBAAgB,eAAe,QAAQ,GACvC;EACJ,MAAM,gBAAgB;EAItB,MAAM,gBAAgB;GACpB,OAAO,aACL,QAAQ,SAAS,IAAI,UAAU,SAAS,KAAK;GAC/C,KAAK,CACH;IACE,QAAQ;IACR,SAAS,EAAE,QAAQ;IACpB,CACF;GACF;AAED,gBAAc,WAAW,EAAE;AAC3B,gBAAc,OAAO,UAAU,EAAE;AACjC,gBAAc,OAAO,MAAM,KAAK,cAAc;AAE9C,SAAO;;;AAIX,SAAS,qBACP,mBACA,2BACA,QACqC;AACrC,KAAI,EAAE,6BAA6B,oBAAoB,OAAO,EAC5D,QAAO;CAGT,MAAM,aAAc,qBAAqB,EAAE;CAG3C,MAAM,UAAU,MAAM,SAAS,0BAA0B;CACzD,MAAM,eAAgB,WAAW,QAAQ,YAAY,EAAE;CAGvD,MAAM,kBAAkB,MAAM,QAAQ,aAAa,QAAQ,GACvD,aAAa,UACb,EAAE;AAEN,QAAO;EACL,GAAG;EACH,OAAO;GACL,GAAG,WAAW;IACb,UAAU;IACT,GAAG;IACH,SAAS,CACP,GAAG,iBACH;KACE,QAAQ,mBAAmB;KAC3B,SAAS,EACP,kBAAkB,KAAK,UAAU,OAAO,EACzC;KACF,CACF;IACF;GACF;EACF;;AAGH,SAAS,iCAAiC,YAAmC;AAC3E,MAAK,MAAM,aAAa,8BAA8B;EACpD,MAAM,WAAW,QAAQ,YAAY,UAAU;AAC/C,MAAI,WAAW,SAAS,CACtB,QAAO;;AAIX,QAAO;;AAGT,SAAS,oBAA4B;CACnC,MAAM,YAAY,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,CAAC;AAElE,MAAK,MAAM,aAAa,wBAAwB;EAC9C,MAAM,WAAW,KAAK,WAAW,UAAU;AAC3C,MAAI,WAAW,SAAS,CACtB,QAAO;;AAIX,QAAO,KAAK,WAAW,4BAA4B"}
1
+ {"version":3,"file":"configure-build.mjs","names":[],"sources":["../../../src/internal/build/configure-build.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { basename, dirname, join, normalize, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { NextConfig } from \"next\";\n\nexport interface InterfereInjectedValues {\n readonly __INTERFERE_FORCE_ENABLE__?: boolean;\n readonly __INTERFERE_RELEASE_SLUG__: string | null;\n}\n\nconst INSTRUMENTATION_CLIENT_FILES = [\n \"instrumentation-client.ts\",\n \"instrumentation-client.tsx\",\n \"instrumentation-client.js\",\n \"instrumentation-client.jsx\",\n \"src/instrumentation-client.ts\",\n \"src/instrumentation-client.tsx\",\n \"src/instrumentation-client.js\",\n \"src/instrumentation-client.jsx\",\n] as const;\n\nconst LOADER_FILE_CANDIDATES = [\n \"value-injection-loader.ts\",\n \"value-injection-loader.js\",\n \"value-injection-loader.mjs\",\n \"value-injection-loader.cjs\",\n] as const;\n\ninterface ConfigureBuildInput {\n readonly existingTurbopack: NextConfig[\"turbopack\"] | undefined;\n readonly existingWebpack: NextConfig[\"webpack\"] | undefined;\n readonly projectDir: string;\n readonly values: InterfereInjectedValues;\n}\n\ninterface ConfigureBuildOutput {\n readonly turbopack: NextConfig[\"turbopack\"] | undefined;\n readonly webpack: NextConfig[\"webpack\"] | undefined;\n}\n\nexport function configureBuild({\n existingWebpack,\n existingTurbopack,\n projectDir,\n values,\n}: ConfigureBuildInput): ConfigureBuildOutput {\n const instrumentationClientPath =\n resolveInstrumentationClientPath(projectDir);\n\n return {\n webpack: buildWebpackHook(\n existingWebpack,\n instrumentationClientPath,\n values\n ),\n turbopack: buildTurbopackConfig(\n existingTurbopack,\n instrumentationClientPath,\n values\n ),\n };\n}\n\nfunction hasInjectedMetadata(metadata: InterfereInjectedValues): boolean {\n return metadata.__INTERFERE_RELEASE_SLUG__ !== null;\n}\n\nfunction buildWebpackHook(\n existingWebpack: NextConfig[\"webpack\"] | undefined,\n instrumentationClientPath: string | null,\n values: InterfereInjectedValues\n): NextConfig[\"webpack\"] | undefined {\n if (!(instrumentationClientPath && hasInjectedMetadata(values))) {\n return existingWebpack;\n }\n\n const normalizedPath = normalize(instrumentationClientPath);\n const loaderPath = resolveLoaderPath();\n\n return (webpackConfig, options) => {\n const outputConfig = existingWebpack\n ? existingWebpack(webpackConfig, options)\n : webpackConfig;\n const mutableConfig = outputConfig as {\n module?: { rules?: unknown[] };\n };\n\n const injectionRule = {\n test: (resource: string): boolean =>\n Boolean(resource) && normalize(resource) === normalizedPath,\n use: [\n {\n loader: loaderPath,\n options: { values },\n },\n ],\n };\n\n mutableConfig.module ??= {};\n mutableConfig.module.rules ??= [];\n mutableConfig.module.rules.push(injectionRule);\n\n return outputConfig;\n };\n}\n\nfunction buildTurbopackConfig(\n existingTurbopack: NextConfig[\"turbopack\"] | undefined,\n instrumentationClientPath: string | null,\n values: InterfereInjectedValues\n): NextConfig[\"turbopack\"] | undefined {\n // Turbopack natively pairs JS chunks to source maps via debug IDs (it\n // injects matching `//# debugId=…` into the chunk and a `\"debugId\"` field\n // into the map). We rely on this in `discoverTurbopack`, because the\n // `//# sourceMappingURL=…` URL in the chunk is the source map's content\n // hash — not the `.js.map` filename on disk — so URL-based pairing always\n // fails for turbopack output.\n //\n // We force `debugIds: true` unless the user opted out explicitly. The\n // user keeps full control: any user-provided value (including `false`)\n // wins via `??`.\n const baseConfig = (existingTurbopack ?? {}) as {\n rules?: Record<string, unknown>;\n debugIds?: boolean;\n };\n const debugIds = baseConfig.debugIds ?? true;\n\n // No instrumentation client → only the debugIds patch is needed, and only\n // if the user hasn't already declared turbopack config (in which case we\n // leave their object untouched but layered with our default).\n if (!(instrumentationClientPath && hasInjectedMetadata(values))) {\n if (existingTurbopack === undefined && debugIds === baseConfig.debugIds) {\n return existingTurbopack;\n }\n \n return { ...baseConfig, debugIds } as NextConfig[\"turbopack\"];\n }\n\n const ruleKey = `**/${basename(instrumentationClientPath)}`;\n const existingRule = (baseConfig.rules?.[ruleKey] ?? {}) as {\n loaders?: unknown[];\n };\n const existingLoaders = Array.isArray(existingRule.loaders)\n ? existingRule.loaders\n : [];\n\n return {\n ...baseConfig,\n debugIds,\n rules: {\n ...baseConfig.rules,\n [ruleKey]: {\n ...existingRule,\n loaders: [\n ...existingLoaders,\n {\n loader: resolveLoaderPath(),\n options: {\n serializedValues: JSON.stringify(values),\n },\n },\n ],\n },\n },\n } as NextConfig[\"turbopack\"];\n}\n\nfunction resolveInstrumentationClientPath(projectDir: string): string | null {\n for (const candidate of INSTRUMENTATION_CLIENT_FILES) {\n const filePath = resolve(projectDir, candidate);\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n return null;\n}\n\nfunction resolveLoaderPath(): string {\n const directory = resolve(dirname(fileURLToPath(import.meta.url)));\n\n for (const candidate of LOADER_FILE_CANDIDATES) {\n const filePath = join(directory, candidate);\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n return join(directory, \"value-injection-loader.js\");\n}\n"],"mappings":";;;;AAUA,MAAM,+BAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACD;AAcD,SAAgB,eAAe,EAC7B,iBACA,mBACA,YACA,UAC4C;CAC5C,MAAM,4BACJ,iCAAiC,WAAW;CAE9C,OAAO;EACL,SAAS,iBACP,iBACA,2BACA,OACD;EACD,WAAW,qBACT,mBACA,2BACA,OACD;EACF;;AAGH,SAAS,oBAAoB,UAA4C;CACvE,OAAO,SAAS,+BAA+B;;AAGjD,SAAS,iBACP,iBACA,2BACA,QACmC;CACnC,IAAI,EAAE,6BAA6B,oBAAoB,OAAO,GAC5D,OAAO;CAGT,MAAM,iBAAiB,UAAU,0BAA0B;CAC3D,MAAM,aAAa,mBAAmB;CAEtC,QAAQ,eAAe,YAAY;EACjC,MAAM,eAAe,kBACjB,gBAAgB,eAAe,QAAQ,GACvC;EACJ,MAAM,gBAAgB;EAItB,MAAM,gBAAgB;GACpB,OAAO,aACL,QAAQ,SAAS,IAAI,UAAU,SAAS,KAAK;GAC/C,KAAK,CACH;IACE,QAAQ;IACR,SAAS,EAAE,QAAQ;IACpB,CACF;GACF;EAED,cAAc,WAAW,EAAE;EAC3B,cAAc,OAAO,UAAU,EAAE;EACjC,cAAc,OAAO,MAAM,KAAK,cAAc;EAE9C,OAAO;;;AAIX,SAAS,qBACP,mBACA,2BACA,QACqC;CAWrC,MAAM,aAAc,qBAAqB,EAAE;CAI3C,MAAM,WAAW,WAAW,YAAY;CAKxC,IAAI,EAAE,6BAA6B,oBAAoB,OAAO,GAAG;EAC/D,IAAI,sBAAsB,KAAA,KAAa,aAAa,WAAW,UAC7D,OAAO;EAGT,OAAO;GAAE,GAAG;GAAY;GAAU;;CAGpC,MAAM,UAAU,MAAM,SAAS,0BAA0B;CACzD,MAAM,eAAgB,WAAW,QAAQ,YAAY,EAAE;CAGvD,MAAM,kBAAkB,MAAM,QAAQ,aAAa,QAAQ,GACvD,aAAa,UACb,EAAE;CAEN,OAAO;EACL,GAAG;EACH;EACA,OAAO;GACL,GAAG,WAAW;IACb,UAAU;IACT,GAAG;IACH,SAAS,CACP,GAAG,iBACH;KACE,QAAQ,mBAAmB;KAC3B,SAAS,EACP,kBAAkB,KAAK,UAAU,OAAO,EACzC;KACF,CACF;IACF;GACF;EACF;;AAGH,SAAS,iCAAiC,YAAmC;CAC3E,KAAK,MAAM,aAAa,8BAA8B;EACpD,MAAM,WAAW,QAAQ,YAAY,UAAU;EAC/C,IAAI,WAAW,SAAS,EACtB,OAAO;;CAIX,OAAO;;AAGT,SAAS,oBAA4B;CACnC,MAAM,YAAY,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,CAAC;CAElE,KAAK,MAAM,aAAa,wBAAwB;EAC9C,MAAM,WAAW,KAAK,WAAW,UAAU;EAC3C,IAAI,WAAW,SAAS,EACtB,OAAO;;CAIX,OAAO,KAAK,WAAW,4BAA4B"}
@@ -0,0 +1,6 @@
1
+ import { ManifestBundler } from "@interfere/types/data/source-maps";
2
+
3
+ //#region src/internal/build/detect-bundler.d.ts
4
+ declare function detectActiveBundler(env?: Readonly<Record<string, string | undefined>>, argv?: readonly string[]): Exclude<ManifestBundler, "tsc">;
5
+ //#endregion
6
+ export { detectActiveBundler };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-bundler.d.mts","names":[],"sources":["../../../src/internal/build/detect-bundler.ts"],"mappings":";;;iBAIgB,mBAAA,CACd,GAAA,GAAK,QAAA,CAAS,MAAA,+BACd,IAAA,uBACC,OAAA,CAAQ,eAAA"}
@@ -0,0 +1,9 @@
1
+ //#region src/internal/build/detect-bundler.ts
2
+ const TURBOPACK_FLAGS = ["--turbo", "--turbopack"];
3
+ function detectActiveBundler(env = process.env, argv = process.argv) {
4
+ if (env["TURBOPACK"] === "1") return "turbopack";
5
+ if (TURBOPACK_FLAGS.some((flag) => argv.includes(flag))) return "turbopack";
6
+ return "webpack";
7
+ }
8
+ //#endregion
9
+ export { detectActiveBundler };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-bundler.mjs","names":[],"sources":["../../../src/internal/build/detect-bundler.ts"],"sourcesContent":["import type { ManifestBundler } from \"@interfere/types/data/source-maps\";\n\nconst TURBOPACK_FLAGS = [\"--turbo\", \"--turbopack\"] as const;\n\nexport function detectActiveBundler(\n env: Readonly<Record<string, string | undefined>> = process.env,\n argv: readonly string[] = process.argv\n): Exclude<ManifestBundler, \"tsc\"> {\n if (env[\"TURBOPACK\"] === \"1\") {\n return \"turbopack\";\n }\n if (TURBOPACK_FLAGS.some((flag) => argv.includes(flag))) {\n return \"turbopack\";\n }\n return \"webpack\";\n}\n"],"mappings":";AAEA,MAAM,kBAAkB,CAAC,WAAW,cAAc;AAElD,SAAgB,oBACd,MAAoD,QAAQ,KAC5D,OAA0B,QAAQ,MACD;CACjC,IAAI,IAAI,iBAAiB,KACvB,OAAO;CAET,IAAI,gBAAgB,MAAM,SAAS,KAAK,SAAS,KAAK,CAAC,EACrD,OAAO;CAET,OAAO"}
@@ -1,5 +1,7 @@
1
1
  import { ProductionCompileContext, ResolvedBuildConfig } from "../../config.mjs";
2
+ import { ReleaseSlug } from "@interfere/types/releases/slug";
2
3
  import * as _$_interfere_sdk_models0 from "@interfere/sdk/models";
4
+ import * as _$zod from "zod";
3
5
  import { ReleasesConfigResponse } from "@interfere/sdk/models/releases-config-response.js";
4
6
  import { CreateReleaseResponse } from "@interfere/types/releases/definition";
5
7
 
@@ -29,6 +31,7 @@ type PipelineResult = {
29
31
  declare function runBuildPipeline(context: ProductionCompileContext, metadata: ResolvedBuildConfig & {
30
32
  apiKey: string;
31
33
  buildId: string;
34
+ releaseSlug: ReleaseSlug;
32
35
  }): Promise<{
33
36
  ready: boolean;
34
37
  reason: string;
@@ -41,7 +44,17 @@ declare function runBuildPipeline(context: ProductionCompileContext, metadata: R
41
44
  reason?: never;
42
45
  ready: true;
43
46
  fileCount: number;
44
- release: _$_interfere_sdk_models0.ReleasesCreateResponse;
47
+ release: {
48
+ org: _$_interfere_sdk_models0.OrgRef;
49
+ surface: _$_interfere_sdk_models0.SurfaceRef;
50
+ source: _$_interfere_sdk_models0.ReleaseSourceRef;
51
+ build: _$_interfere_sdk_models0.ReleaseBuildRef;
52
+ destination: {
53
+ id: string;
54
+ environment: string | null;
55
+ slug: `rel_${string}` & _$zod.$brand<"ReleaseSlug">;
56
+ };
57
+ };
45
58
  config: ReleasesConfigResponse;
46
59
  buildId: string;
47
60
  timing: BuildTiming;
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.mts","names":[],"sources":["../../../src/internal/build/pipeline.ts"],"mappings":";;;;;;UAYiB,WAAA;EACf,OAAA;EACA,aAAA;EACA,QAAA;EACA,SAAA;EACA,SAAA;EACA,KAAA;EACA,UAAA;EACA,MAAA;AAAA;AAAA,KAGU,cAAA;EACN,KAAA;EAAc,MAAA;EAA0B,SAAA;AAAA;EAExC,KAAA;EACA,SAAA;EACA,OAAA,EAAS,qBAAA;EACT,MAAA,EAAQ,sBAAA;EACR,OAAA;EACA,MAAA,EAAQ,WAAA;AAAA;AAAA,iBAUQ,gBAAA,CACpB,OAAA,EAAS,wBAAA,EACT,QAAA,EAAU,mBAAA;EAAwB,MAAA;EAAgB,OAAA;AAAA,IAAiB,OAAA;;;;;;;;;;;;WAAtC,wBAAA,CAAA,sBAAA"}
1
+ {"version":3,"file":"pipeline.d.mts","names":[],"sources":["../../../src/internal/build/pipeline.ts"],"mappings":";;;;;;;;UAciB,WAAA;EACf,OAAA;EACA,aAAA;EACA,QAAA;EACA,SAAA;EACA,SAAA;EACA,KAAA;EACA,UAAA;EACA,MAAA;AAAA;AAAA,KAGU,cAAA;EACN,KAAA;EAAc,MAAA;EAA0B,SAAA;AAAA;EAExC,KAAA;EACA,SAAA;EACA,OAAA,EAAS,qBAAA;EACT,MAAA,EAAQ,sBAAA;EACR,OAAA;EACA,MAAA,EAAQ,WAAA;AAAA;AAAA,iBAUQ,gBAAA,CACpB,OAAA,EAAS,wBAAA,EACT,QAAA,EAAU,mBAAA;EACR,MAAA;EACA,OAAA;EACA,WAAA,EAAa,WAAA;AAAA,IACd,OAAA;;;;;;;;;;;;;SADyB,wBAAA,CAAA,MAAA"}
@@ -1,6 +1,9 @@
1
+ import { detectActiveBundler } from "./detect-bundler.mjs";
1
2
  import { resolveReleaseRequest } from "./release/index.mjs";
2
- import { discover, normalizeDistDir } from "./source-maps/discover.mjs";
3
- import { buildUploadBody, cleanupSourceMaps } from "./source-maps/index.mjs";
3
+ import { discoverSourceMaps } from "./source-maps/discover.mjs";
4
+ import { uploadSourceMaps } from "./source-maps/upload.mjs";
5
+ import { cleanupSourceMaps } from "./source-maps/index.mjs";
6
+ import { releaseSlugSchema } from "@interfere/types/releases/slug";
4
7
  import { HTTPClient, Interfere } from "@interfere/sdk";
5
8
  //#region src/internal/build/pipeline.ts
6
9
  async function timed(fn) {
@@ -20,8 +23,13 @@ async function runBuildPipeline(context, metadata) {
20
23
  httpClient
21
24
  });
22
25
  const start = performance.now();
26
+ const bundler = detectActiveBundler();
23
27
  const [{ discovered, config }, discoverMs] = await timed(async () => {
24
- const [discovered, config] = await Promise.all([discover(context.projectDir, normalizeDistDir(context.distDir)), sdk.releases.getConfig()]);
28
+ const [discovered, config] = await Promise.all([discoverSourceMaps({
29
+ bundler,
30
+ projectDir: context.projectDir,
31
+ distDir: context.distDir
32
+ }), sdk.releases.getConfig()]);
25
33
  return {
26
34
  discovered,
27
35
  config
@@ -32,14 +40,16 @@ async function runBuildPipeline(context, metadata) {
32
40
  reason: "no_source_maps",
33
41
  fileCount: 0
34
42
  };
35
- const releaseRequest = resolveReleaseRequest(metadata.buildId, config);
43
+ const releaseRequest = resolveReleaseRequest(metadata.buildId, metadata.releaseSlug, config);
36
44
  const [release, createReleaseMs] = await timed(() => sdk.releases.create(releaseRequest));
37
- const releaseSlug = release.destination.slug;
45
+ const releaseSlug = releaseSlugSchema.parse(release.destination.slug);
38
46
  const buildId = release.build.hash ?? metadata.buildId;
39
- const { body, totalBytes } = buildUploadBody(discovered);
40
- const [, uploadMs] = await timed(() => sdk.sourceMaps.uploadMultipart({
41
- releaseSlug,
42
- body
47
+ const [{ totalBytes }, uploadMs] = await timed(() => uploadSourceMaps({
48
+ apiUrl,
49
+ bundler,
50
+ discovered,
51
+ httpClient,
52
+ releaseSlug
43
53
  }));
44
54
  const [, preflightMs] = await timed(() => sdk.releases.preflight({ releaseSlug }));
45
55
  const [, cleanupMs] = await timed(() => cleanupSourceMaps(discovered.files));
@@ -56,7 +66,13 @@ async function runBuildPipeline(context, metadata) {
56
66
  return {
57
67
  ready: true,
58
68
  fileCount: discovered.files.length,
59
- release,
69
+ release: {
70
+ ...release,
71
+ destination: {
72
+ ...release.destination,
73
+ slug: releaseSlug
74
+ }
75
+ },
60
76
  config,
61
77
  buildId,
62
78
  timing
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.mjs","names":[],"sources":["../../../src/internal/build/pipeline.ts"],"sourcesContent":["import { HTTPClient, Interfere } from \"@interfere/sdk\";\nimport type { ReleasesConfigResponse } from \"@interfere/sdk/models/releases-config-response.js\";\nimport type { CreateReleaseResponse } from \"@interfere/types/releases/definition\";\n\nimport type {\n ProductionCompileContext,\n ResolvedBuildConfig,\n} from \"../../config.js\";\nimport { resolveReleaseRequest } from \"./release/index.js\";\nimport { discover, normalizeDistDir } from \"./source-maps/discover.js\";\nimport { buildUploadBody, cleanupSourceMaps } from \"./source-maps/index.js\";\n\nexport interface BuildTiming {\n cleanup: number;\n createRelease: number;\n discover: number;\n fileCount: number;\n preflight: number;\n total: number;\n totalBytes: number;\n upload: number;\n}\n\nexport type PipelineResult =\n | { ready: false; reason: \"no_source_maps\"; fileCount: 0 }\n | {\n ready: true;\n fileCount: number;\n release: CreateReleaseResponse;\n config: ReleasesConfigResponse;\n buildId: string;\n timing: BuildTiming;\n };\n\nasync function timed<T>(fn: () => Promise<T>): Promise<[T, number]> {\n const start = performance.now();\n const result = await fn();\n return [result, Math.round(performance.now() - start)];\n}\n\n\nexport async function runBuildPipeline(\n context: ProductionCompileContext,\n metadata: ResolvedBuildConfig & { apiKey: string; buildId: string }\n) {\n const { apiUrl, apiKey } = metadata;\n\n const httpClient = new HTTPClient();\n\n httpClient.addHook(\"beforeRequest\", (request) => {\n const nextRequest = new Request(request);\n\n nextRequest.headers.set(\"x-api-key\", apiKey);\n\n return nextRequest;\n });\n\n const sdk = new Interfere({ serverURL: apiUrl, httpClient });\n\n const start = performance.now();\n\n const [{ discovered, config }, discoverMs] = await timed(async () => {\n const [discovered, config] = await Promise.all([\n discover(context.projectDir, normalizeDistDir(context.distDir)),\n sdk.releases.getConfig(),\n ]);\n return { discovered, config };\n });\n\n if (discovered.files.length === 0) {\n return { ready: false, reason: \"no_source_maps\", fileCount: 0 };\n }\n\n const releaseRequest = resolveReleaseRequest(metadata.buildId, config);\n\n const [release, createReleaseMs] = await timed(() =>\n sdk.releases.create(releaseRequest)\n );\n\n const releaseSlug = release.destination.slug;\n const buildId = release.build.hash ?? metadata.buildId;\n\n const { body, totalBytes } = buildUploadBody(discovered);\n\n const [, uploadMs] = await timed(() =>\n sdk.sourceMaps.uploadMultipart({ releaseSlug, body })\n );\n\n // Cleanup runs *after* preflight, not before (PRD draft had it inverted).\n // Preflight only depends on the upload having succeeded; if local cleanup\n // fails we still want the release marked confirmed and the build to fail\n // loudly via the cleanup error, rather than leaving a confirmed-but-uncleaned\n // workspace and a release that needs a redeploy to recover.\n const [, preflightMs] = await timed(() =>\n sdk.releases.preflight({ releaseSlug })\n );\n\n const [, cleanupMs] = await timed(() => cleanupSourceMaps(discovered.files));\n\n const timing: BuildTiming = {\n discover: discoverMs,\n createRelease: createReleaseMs,\n upload: uploadMs,\n preflight: preflightMs,\n cleanup: cleanupMs,\n total: Math.round(performance.now() - start),\n fileCount: discovered.files.length,\n totalBytes,\n };\n\n return {\n ready: true,\n fileCount: discovered.files.length,\n release,\n config,\n buildId,\n timing,\n } satisfies PipelineResult;\n}\n"],"mappings":";;;;;AAkCA,eAAe,MAAS,IAA4C;CAClE,MAAM,QAAQ,YAAY,KAAK;AAE/B,QAAO,CAAC,MADa,IAAI,EACT,KAAK,MAAM,YAAY,KAAK,GAAG,MAAM,CAAC;;AAIxD,eAAsB,iBACpB,SACA,UACA;CACA,MAAM,EAAE,QAAQ,WAAW;CAE3B,MAAM,aAAa,IAAI,YAAY;AAEnC,YAAW,QAAQ,kBAAkB,YAAY;EAC/C,MAAM,cAAc,IAAI,QAAQ,QAAQ;AAExC,cAAY,QAAQ,IAAI,aAAa,OAAO;AAE5C,SAAO;GACP;CAEF,MAAM,MAAM,IAAI,UAAU;EAAE,WAAW;EAAQ;EAAY,CAAC;CAE5D,MAAM,QAAQ,YAAY,KAAK;CAE/B,MAAM,CAAC,EAAE,YAAY,UAAU,cAAc,MAAM,MAAM,YAAY;EACnE,MAAM,CAAC,YAAY,UAAU,MAAM,QAAQ,IAAI,CAC7C,SAAS,QAAQ,YAAY,iBAAiB,QAAQ,QAAQ,CAAC,EAC/D,IAAI,SAAS,WAAW,CACzB,CAAC;AACF,SAAO;GAAE;GAAY;GAAQ;GAC7B;AAEF,KAAI,WAAW,MAAM,WAAW,EAC9B,QAAO;EAAE,OAAO;EAAO,QAAQ;EAAkB,WAAW;EAAG;CAGjE,MAAM,iBAAiB,sBAAsB,SAAS,SAAS,OAAO;CAEtE,MAAM,CAAC,SAAS,mBAAmB,MAAM,YACvC,IAAI,SAAS,OAAO,eAAe,CACpC;CAED,MAAM,cAAc,QAAQ,YAAY;CACxC,MAAM,UAAU,QAAQ,MAAM,QAAQ,SAAS;CAE/C,MAAM,EAAE,MAAM,eAAe,gBAAgB,WAAW;CAExD,MAAM,GAAG,YAAY,MAAM,YACzB,IAAI,WAAW,gBAAgB;EAAE;EAAa;EAAM,CAAC,CACtD;CAOD,MAAM,GAAG,eAAe,MAAM,YAC5B,IAAI,SAAS,UAAU,EAAE,aAAa,CAAC,CACxC;CAED,MAAM,GAAG,aAAa,MAAM,YAAY,kBAAkB,WAAW,MAAM,CAAC;CAE5E,MAAM,SAAsB;EAC1B,UAAU;EACV,eAAe;EACf,QAAQ;EACR,WAAW;EACX,SAAS;EACT,OAAO,KAAK,MAAM,YAAY,KAAK,GAAG,MAAM;EAC5C,WAAW,WAAW,MAAM;EAC5B;EACD;AAED,QAAO;EACL,OAAO;EACP,WAAW,WAAW,MAAM;EAC5B;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"pipeline.mjs","names":[],"sources":["../../../src/internal/build/pipeline.ts"],"sourcesContent":["import { HTTPClient, Interfere } from \"@interfere/sdk\";\nimport type { ReleasesConfigResponse } from \"@interfere/sdk/models/releases-config-response.js\";\nimport type { CreateReleaseResponse } from \"@interfere/types/releases/definition\";\nimport { type ReleaseSlug, releaseSlugSchema } from \"@interfere/types/releases/slug\";\n\nimport type {\n ProductionCompileContext,\n ResolvedBuildConfig,\n} from \"../../config.js\";\nimport { detectActiveBundler } from \"./detect-bundler.js\";\nimport { resolveReleaseRequest } from \"./release/index.js\";\nimport { discoverSourceMaps } from \"./source-maps/discover.js\";\nimport { cleanupSourceMaps, uploadSourceMaps } from \"./source-maps/index.js\";\n\nexport interface BuildTiming {\n cleanup: number;\n createRelease: number;\n discover: number;\n fileCount: number;\n preflight: number;\n total: number;\n totalBytes: number;\n upload: number;\n}\n\nexport type PipelineResult =\n | { ready: false; reason: \"no_source_maps\"; fileCount: 0 }\n | {\n ready: true;\n fileCount: number;\n release: CreateReleaseResponse;\n config: ReleasesConfigResponse;\n buildId: string;\n timing: BuildTiming;\n };\n\nasync function timed<T>(fn: () => Promise<T>): Promise<[T, number]> {\n const start = performance.now();\n const result = await fn();\n return [result, Math.round(performance.now() - start)];\n}\n\n\nexport async function runBuildPipeline(\n context: ProductionCompileContext,\n metadata: ResolvedBuildConfig & {\n apiKey: string;\n buildId: string;\n releaseSlug: ReleaseSlug;\n }\n) {\n const { apiUrl, apiKey } = metadata;\n\n const httpClient = new HTTPClient();\n\n httpClient.addHook(\"beforeRequest\", (request) => {\n const nextRequest = new Request(request);\n\n nextRequest.headers.set(\"x-api-key\", apiKey);\n\n return nextRequest;\n });\n\n const sdk = new Interfere({ serverURL: apiUrl, httpClient });\n\n const start = performance.now();\n\n const bundler = detectActiveBundler();\n\n const [{ discovered, config }, discoverMs] = await timed(async () => {\n const [discovered, config] = await Promise.all([\n discoverSourceMaps({\n bundler,\n projectDir: context.projectDir,\n distDir: context.distDir,\n }),\n sdk.releases.getConfig(),\n ]);\n return { discovered, config };\n });\n\n if (discovered.files.length === 0) {\n return { ready: false, reason: \"no_source_maps\", fileCount: 0 };\n }\n\n const releaseRequest = resolveReleaseRequest(\n metadata.buildId,\n metadata.releaseSlug,\n config\n );\n\n const [release, createReleaseMs] = await timed(() =>\n sdk.releases.create(releaseRequest)\n );\n\n // The SDK response carries the slug as a plain `string`. Brand it\n // here at the API boundary so every downstream caller (upload,\n // preflight, cleanup) reads a `ReleaseSlug`.\n const releaseSlug = releaseSlugSchema.parse(release.destination.slug);\n const buildId = release.build.hash ?? metadata.buildId;\n\n const [{ totalBytes }, uploadMs] = await timed(() =>\n uploadSourceMaps({ apiUrl, bundler, discovered, httpClient, releaseSlug: releaseSlug })\n );\n\n // Cleanup runs *after* preflight, not before (PRD draft had it inverted).\n // Preflight only depends on the upload having succeeded; if local cleanup\n // fails we still want the release marked confirmed and the build to fail\n // loudly via the cleanup error, rather than leaving a confirmed-but-uncleaned\n // workspace and a release that needs a redeploy to recover.\n const [, preflightMs] = await timed(() =>\n sdk.releases.preflight({ releaseSlug })\n );\n\n const [, cleanupMs] = await timed(() => cleanupSourceMaps(discovered.files));\n\n const timing: BuildTiming = {\n discover: discoverMs,\n createRelease: createReleaseMs,\n upload: uploadMs,\n preflight: preflightMs,\n cleanup: cleanupMs,\n total: Math.round(performance.now() - start),\n fileCount: discovered.files.length,\n totalBytes,\n };\n\n return {\n ready: true,\n fileCount: discovered.files.length,\n release: { ...release, destination: { ...release.destination, slug: releaseSlug } },\n config,\n buildId,\n timing,\n } satisfies PipelineResult;\n}\n"],"mappings":";;;;;;;;AAoCA,eAAe,MAAS,IAA4C;CAClE,MAAM,QAAQ,YAAY,KAAK;CAE/B,OAAO,CAAC,MADa,IAAI,EACT,KAAK,MAAM,YAAY,KAAK,GAAG,MAAM,CAAC;;AAIxD,eAAsB,iBACpB,SACA,UAKA;CACA,MAAM,EAAE,QAAQ,WAAW;CAE3B,MAAM,aAAa,IAAI,YAAY;CAEnC,WAAW,QAAQ,kBAAkB,YAAY;EAC/C,MAAM,cAAc,IAAI,QAAQ,QAAQ;EAExC,YAAY,QAAQ,IAAI,aAAa,OAAO;EAE5C,OAAO;GACP;CAEF,MAAM,MAAM,IAAI,UAAU;EAAE,WAAW;EAAQ;EAAY,CAAC;CAE5D,MAAM,QAAQ,YAAY,KAAK;CAE/B,MAAM,UAAU,qBAAqB;CAErC,MAAM,CAAC,EAAE,YAAY,UAAU,cAAc,MAAM,MAAM,YAAY;EACnE,MAAM,CAAC,YAAY,UAAU,MAAM,QAAQ,IAAI,CAC7C,mBAAmB;GACjB;GACA,YAAY,QAAQ;GACpB,SAAS,QAAQ;GAClB,CAAC,EACF,IAAI,SAAS,WAAW,CACzB,CAAC;EACF,OAAO;GAAE;GAAY;GAAQ;GAC7B;CAEF,IAAI,WAAW,MAAM,WAAW,GAC9B,OAAO;EAAE,OAAO;EAAO,QAAQ;EAAkB,WAAW;EAAG;CAGjE,MAAM,iBAAiB,sBACrB,SAAS,SACT,SAAS,aACT,OACD;CAED,MAAM,CAAC,SAAS,mBAAmB,MAAM,YACvC,IAAI,SAAS,OAAO,eAAe,CACpC;CAKD,MAAM,cAAc,kBAAkB,MAAM,QAAQ,YAAY,KAAK;CACrE,MAAM,UAAU,QAAQ,MAAM,QAAQ,SAAS;CAE/C,MAAM,CAAC,EAAE,cAAc,YAAY,MAAM,YACvC,iBAAiB;EAAE;EAAQ;EAAS;EAAY;EAAyB;EAAa,CAAC,CACxF;CAOD,MAAM,GAAG,eAAe,MAAM,YAC5B,IAAI,SAAS,UAAU,EAAE,aAAa,CAAC,CACxC;CAED,MAAM,GAAG,aAAa,MAAM,YAAY,kBAAkB,WAAW,MAAM,CAAC;CAE5E,MAAM,SAAsB;EAC1B,UAAU;EACV,eAAe;EACf,QAAQ;EACR,WAAW;EACX,SAAS;EACT,OAAO,KAAK,MAAM,YAAY,KAAK,GAAG,MAAM;EAC5C,WAAW,WAAW,MAAM;EAC5B;EACD;CAED,OAAO;EACL,OAAO;EACP,WAAW,WAAW,MAAM;EAC5B,SAAS;GAAE,GAAG;GAAS,aAAa;IAAE,GAAG,QAAQ;IAAa,MAAM;IAAa;GAAE;EACnF;EACA;EACA;EACD"}
@@ -0,0 +1,14 @@
1
+ import { DestinationProvider, ReleaseDestinationMetadata } from "@interfere/types/integrations";
2
+
3
+ //#region src/internal/build/release/destinations/index.d.ts
4
+ /**
5
+ * Per-provider destination metadata resolvers. Mirrors `sources/` —
6
+ * each entry knows how to read its platform's deployment env vars and
7
+ * returns a `ReleaseDestinationMetadata` blob.
8
+ *
9
+ * Adding a new destination (cloudflare-pages, fly, render, …) is one
10
+ * new file under this directory + one entry here.
11
+ */
12
+ declare const destinations: Record<DestinationProvider, () => ReleaseDestinationMetadata>;
13
+ //#endregion
14
+ export { destinations };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../src/internal/build/release/destinations/index.ts"],"mappings":";;;;;AAeA;;;;;;cAAa,YAAA,EAAc,MAAA,CACzB,mBAAA,QACM,0BAAA"}
@@ -0,0 +1,13 @@
1
+ import { resolve } from "./vercel.mjs";
2
+ //#region src/internal/build/release/destinations/index.ts
3
+ /**
4
+ * Per-provider destination metadata resolvers. Mirrors `sources/` —
5
+ * each entry knows how to read its platform's deployment env vars and
6
+ * returns a `ReleaseDestinationMetadata` blob.
7
+ *
8
+ * Adding a new destination (cloudflare-pages, fly, render, …) is one
9
+ * new file under this directory + one entry here.
10
+ */
11
+ const destinations = { vercel: resolve };
12
+ //#endregion
13
+ export { destinations };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["resolveVercel"],"sources":["../../../../../src/internal/build/release/destinations/index.ts"],"sourcesContent":["import type {\n DestinationProvider,\n ReleaseDestinationMetadata,\n} from \"@interfere/types/integrations\";\n\nimport { resolve as resolveVercel } from \"./vercel.js\";\n\n/**\n * Per-provider destination metadata resolvers. Mirrors `sources/` —\n * each entry knows how to read its platform's deployment env vars and\n * returns a `ReleaseDestinationMetadata` blob.\n *\n * Adding a new destination (cloudflare-pages, fly, render, …) is one\n * new file under this directory + one entry here.\n */\nexport const destinations: Record<\n DestinationProvider,\n () => ReleaseDestinationMetadata\n> = {\n vercel: resolveVercel,\n};\n"],"mappings":";;;;;;;;;;AAeA,MAAa,eAGT,EACF,QAAQA,SACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"vercel.mjs","names":[],"sources":["../../../../../src/internal/build/release/destinations/vercel.ts"],"sourcesContent":["import type { ReleaseDestinationMetadata } from \"@interfere/types/integrations\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\n\nexport function resolve(): ReleaseDestinationMetadata {\n const environment = parseEnvValue(\n process.env[\"VERCEL_ENV\"] ?? process.env[\"VERCEL_TARGET_ENV\"]\n );\n\n const deploymentId = parseEnvValue(process.env[\"VERCEL_DEPLOYMENT_ID\"]);\n\n return {\n provider: \"vercel\",\n destinationReleaseId: deploymentId,\n environment,\n deploymentId,\n deploymentUrl: resolveDeploymentUrl(),\n environmentName: environment,\n environmentTarget: environment,\n };\n}\n\nfunction resolveDeploymentUrl(): string | null {\n const deploymentUrl = parseEnvValue(process.env[\"VERCEL_URL\"]);\n\n if (deploymentUrl === null) {\n return null;\n }\n\n if (\n deploymentUrl.startsWith(\"https://\") ||\n deploymentUrl.startsWith(\"http://\")\n ) {\n return deploymentUrl;\n }\n\n return `https://${deploymentUrl}`;\n}\n"],"mappings":";;AAGA,SAAgB,UAAsC;CACpD,MAAM,cAAc,cAClB,QAAQ,IAAI,iBAAiB,QAAQ,IAAI,qBAC1C;CAED,MAAM,eAAe,cAAc,QAAQ,IAAI,wBAAwB;AAEvE,QAAO;EACL,UAAU;EACV,sBAAsB;EACtB;EACA;EACA,eAAe,sBAAsB;EACrC,iBAAiB;EACjB,mBAAmB;EACpB;;AAGH,SAAS,uBAAsC;CAC7C,MAAM,gBAAgB,cAAc,QAAQ,IAAI,cAAc;AAE9D,KAAI,kBAAkB,KACpB,QAAO;AAGT,KACE,cAAc,WAAW,WAAW,IACpC,cAAc,WAAW,UAAU,CAEnC,QAAO;AAGT,QAAO,WAAW"}
1
+ {"version":3,"file":"vercel.mjs","names":[],"sources":["../../../../../src/internal/build/release/destinations/vercel.ts"],"sourcesContent":["import type { ReleaseDestinationMetadata } from \"@interfere/types/integrations\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\n\nexport function resolve(): ReleaseDestinationMetadata {\n const environment = parseEnvValue(\n process.env[\"VERCEL_ENV\"] ?? process.env[\"VERCEL_TARGET_ENV\"]\n );\n\n const deploymentId = parseEnvValue(process.env[\"VERCEL_DEPLOYMENT_ID\"]);\n\n return {\n provider: \"vercel\",\n destinationReleaseId: deploymentId,\n environment,\n deploymentId,\n deploymentUrl: resolveDeploymentUrl(),\n environmentName: environment,\n environmentTarget: environment,\n };\n}\n\nfunction resolveDeploymentUrl(): string | null {\n const deploymentUrl = parseEnvValue(process.env[\"VERCEL_URL\"]);\n\n if (deploymentUrl === null) {\n return null;\n }\n\n if (\n deploymentUrl.startsWith(\"https://\") ||\n deploymentUrl.startsWith(\"http://\")\n ) {\n return deploymentUrl;\n }\n\n return `https://${deploymentUrl}`;\n}\n"],"mappings":";;AAGA,SAAgB,UAAsC;CACpD,MAAM,cAAc,cAClB,QAAQ,IAAI,iBAAiB,QAAQ,IAAI,qBAC1C;CAED,MAAM,eAAe,cAAc,QAAQ,IAAI,wBAAwB;CAEvE,OAAO;EACL,UAAU;EACV,sBAAsB;EACtB;EACA;EACA,eAAe,sBAAsB;EACrC,iBAAiB;EACjB,mBAAmB;EACpB;;AAGH,SAAS,uBAAsC;CAC7C,MAAM,gBAAgB,cAAc,QAAQ,IAAI,cAAc;CAE9D,IAAI,kBAAkB,MACpB,OAAO;CAGT,IACE,cAAc,WAAW,WAAW,IACpC,cAAc,WAAW,UAAU,EAEnC,OAAO;CAGT,OAAO,WAAW"}
@@ -1,4 +1,17 @@
1
1
  //#region src/internal/build/release/git.d.ts
2
+ /**
3
+ * Runs a git command and returns its stdout, or `null` when git isn't
4
+ * available (no `.git` checkout, git binary missing, command errored).
5
+ *
6
+ * The ignore-on-failure shape lets callers compose this with env-var
7
+ * reads as a fallback chain — env vars first (CI environments where
8
+ * git isn't checked out), git as a last resort (local dev).
9
+ *
10
+ * Build-time only. `node:child_process` isn't available in the Edge
11
+ * runtime, so this helper must never end up in `instrumentation.ts`'s
12
+ * import graph (the SDK ships that entrypoint to both Node + Edge
13
+ * runtimes via conditional exports).
14
+ */
2
15
  declare function runGitCommand(command: string): string | null;
3
16
  //#endregion
4
17
  export { runGitCommand };
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.mts","names":[],"sources":["../../../../src/internal/build/release/git.ts"],"mappings":";iBAIgB,aAAA,CAAc,OAAA"}
1
+ {"version":3,"file":"git.d.mts","names":[],"sources":["../../../../src/internal/build/release/git.ts"],"mappings":";;AAeA;;;;;;;;;;;;iBAAgB,aAAA,CAAc,OAAA"}
@@ -1,6 +1,18 @@
1
- import { log } from "../../logger.mjs";
2
1
  import { execSync } from "node:child_process";
3
2
  //#region src/internal/build/release/git.ts
3
+ /**
4
+ * Runs a git command and returns its stdout, or `null` when git isn't
5
+ * available (no `.git` checkout, git binary missing, command errored).
6
+ *
7
+ * The ignore-on-failure shape lets callers compose this with env-var
8
+ * reads as a fallback chain — env vars first (CI environments where
9
+ * git isn't checked out), git as a last resort (local dev).
10
+ *
11
+ * Build-time only. `node:child_process` isn't available in the Edge
12
+ * runtime, so this helper must never end up in `instrumentation.ts`'s
13
+ * import graph (the SDK ships that entrypoint to both Node + Edge
14
+ * runtimes via conditional exports).
15
+ */
4
16
  function runGitCommand(command) {
5
17
  try {
6
18
  const output = execSync(command, {
@@ -13,7 +25,6 @@ function runGitCommand(command) {
13
25
  }).trim();
14
26
  return output.length > 0 ? output : null;
15
27
  } catch {
16
- log.warn("Git unavailable", [`Failed: ${command}`]);
17
28
  return null;
18
29
  }
19
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"git.mjs","names":[],"sources":["../../../../src/internal/build/release/git.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\n\nimport { log } from \"../../logger.js\";\n\nexport function runGitCommand(command: string): string | null {\n try {\n const output = execSync(command, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n\n return output.length > 0 ? output : null;\n } catch {\n log.warn(\"Git unavailable\", [`Failed: ${command}`]);\n return null;\n }\n}\n"],"mappings":";;;AAIA,SAAgB,cAAc,SAAgC;AAC5D,KAAI;EACF,MAAM,SAAS,SAAS,SAAS;GAC/B,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CAAC,MAAM;AAET,SAAO,OAAO,SAAS,IAAI,SAAS;SAC9B;AACN,MAAI,KAAK,mBAAmB,CAAC,WAAW,UAAU,CAAC;AACnD,SAAO"}
1
+ {"version":3,"file":"git.mjs","names":[],"sources":["../../../../src/internal/build/release/git.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\n\n/**\n * Runs a git command and returns its stdout, or `null` when git isn't\n * available (no `.git` checkout, git binary missing, command errored).\n *\n * The ignore-on-failure shape lets callers compose this with env-var\n * reads as a fallback chain — env vars first (CI environments where\n * git isn't checked out), git as a last resort (local dev).\n *\n * Build-time only. `node:child_process` isn't available in the Edge\n * runtime, so this helper must never end up in `instrumentation.ts`'s\n * import graph (the SDK ships that entrypoint to both Node + Edge\n * runtimes via conditional exports).\n */\nexport function runGitCommand(command: string): string | null {\n try {\n const output = execSync(command, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n\n return output.length > 0 ? output : null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,cAAc,SAAgC;CAC5D,IAAI;EACF,MAAM,SAAS,SAAS,SAAS;GAC/B,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CAAC,MAAM;EAET,OAAO,OAAO,SAAS,IAAI,SAAS;SAC9B;EACN,OAAO"}
@@ -1,7 +1,8 @@
1
+ import { ReleaseSlug } from "@interfere/types/releases/slug";
1
2
  import { ReleasesConfigResponse } from "@interfere/sdk/models/releases-config-response.js";
2
3
  import { CreateReleaseRequest } from "@interfere/types/releases/definition";
3
4
 
4
5
  //#region src/internal/build/release/index.d.ts
5
- declare function resolveReleaseRequest(buildId: string, config: ReleasesConfigResponse): CreateReleaseRequest;
6
+ declare function resolveReleaseRequest(buildId: string, releaseSlug: ReleaseSlug, config: ReleasesConfigResponse): CreateReleaseRequest;
6
7
  //#endregion
7
8
  export { resolveReleaseRequest };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"mappings":";;;;iBAyBgB,qBAAA,CACd,OAAA,UACA,MAAA,EAAQ,sBAAA,GACP,oBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"mappings":";;;;;iBASgB,qBAAA,CACd,OAAA,UACA,WAAA,EAAa,WAAA,EACb,MAAA,EAAQ,sBAAA,GACP,oBAAA"}
@@ -1,17 +1,16 @@
1
1
  import { log } from "../../logger.mjs";
2
2
  import { PRODUCER_VERSION } from "../../version.mjs";
3
- import { resolve } from "./destinations/vercel.mjs";
4
- import { resolve as resolve$1 } from "./sources/github.mjs";
3
+ import { destinations } from "./destinations/index.mjs";
4
+ import { sources } from "./sources/index.mjs";
5
5
  //#region src/internal/build/release/index.ts
6
- const sources = { github: resolve$1 };
7
- const destinations = { vercel: resolve };
8
- function resolveReleaseRequest(buildId, config) {
6
+ function resolveReleaseRequest(buildId, releaseSlug, config) {
9
7
  if (!config.surface.sourceProvider) return log.fatal("Missing version control provider", ["This surface does not have a version control provider configured.", `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`]);
10
8
  if (!config.surface.destinationProvider) return log.fatal("Missing deployment target", ["This surface does not have a deployment target integration configured.", `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`]);
11
9
  return {
12
10
  source: sources[config.surface.sourceProvider](),
13
11
  destination: destinations[config.surface.destinationProvider](),
14
12
  buildId,
13
+ slug: releaseSlug,
15
14
  producerVersion: PRODUCER_VERSION
16
15
  };
17
16
  }