@interfere/next 11.0.1 → 11.0.4

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 (43) hide show
  1. package/README.md +2 -2
  2. package/dist/config.d.mts +10 -3
  3. package/dist/config.mjs +1 -1
  4. package/dist/instrument-client.d.mts +7 -8
  5. package/dist/instrumentation.d.mts +9 -24
  6. package/dist/instrumentation.edge.d.mts +3 -2
  7. package/dist/instrumentation.edge.mjs +1 -1
  8. package/dist/instrumentation.mjs +1 -1
  9. package/dist/internal/build/configure-build.d.mts +14 -8
  10. package/dist/internal/build/configure-build.mjs +1 -1
  11. package/dist/internal/build/detect-bundler.d.mts +1 -2
  12. package/dist/internal/build/detect-bundler.mjs +1 -1
  13. package/dist/internal/build/pipeline.d.mts +0 -1
  14. package/dist/internal/build/pipeline.mjs +1 -1
  15. package/dist/internal/build/release/destinations/cli.d.mts +0 -1
  16. package/dist/internal/build/release/destinations/cloudflare.d.mts +0 -1
  17. package/dist/internal/build/release/destinations/index.d.mts +0 -1
  18. package/dist/internal/build/release/destinations/vercel.d.mts +0 -1
  19. package/dist/internal/build/release/index.d.mts +0 -1
  20. package/dist/internal/build/release/sources/github.d.mts +0 -1
  21. package/dist/internal/build/release/sources/index.d.mts +0 -1
  22. package/dist/internal/build/source-maps/discover-turbopack.d.mts +0 -1
  23. package/dist/internal/build/source-maps/discover-webpack.d.mts +0 -1
  24. package/dist/internal/build/source-maps/discover.d.mts +0 -1
  25. package/dist/internal/build/source-maps/guard.d.mts +9 -0
  26. package/dist/internal/build/source-maps/guard.mjs +1 -0
  27. package/dist/internal/build/source-maps/index.d.mts +0 -1
  28. package/dist/internal/build/source-maps/upload.d.mts +1 -8
  29. package/dist/internal/build/value-injection-loader.d.mts +6 -5
  30. package/dist/internal/build/value-injection-loader.mjs +2 -2
  31. package/dist/internal/env.mjs +1 -1
  32. package/dist/internal/release-slug.d.mts +0 -1
  33. package/dist/internal/release-slug.mjs +1 -1
  34. package/dist/internal/server/capture.d.mts +0 -1
  35. package/dist/internal/server/edge-exporter.d.mts +0 -1
  36. package/dist/internal/server/id-generator.d.mts +0 -1
  37. package/dist/internal/server/instrumentation-options.d.mts +8 -41
  38. package/dist/internal/server/remote-config.mjs +1 -1
  39. package/dist/internal/server/types.d.mts +0 -1
  40. package/dist/internal/server/wrap-proxy.d.mts +0 -1
  41. package/dist/package.mjs +1 -1
  42. package/dist/provider.d.mts +1 -7
  43. package/package.json +8 -10
package/README.md CHANGED
@@ -86,13 +86,13 @@ export default function RootLayout({
86
86
 
87
87
  | Variable | Required | Description |
88
88
  | --- | --- | --- |
89
- | `INTERFERE_PUBLIC_KEY` | Yes | Surface publishable key in the `interfere_pub_us_*` or `interfere_pub_eu_*` format. Public by design; used for ingestion. |
89
+ | `INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Public by design; used for ingestion. |
90
90
  | `INTERFERE_API_KEY` | Yes for release metadata | Interfere secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format. Server-side build credential; never expose in browser code. |
91
91
  | `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
92
92
 
93
93
  ### Migrating From 0.x
94
94
 
95
- Upgrade all `@interfere/*` SDK packages together. Keep the env var names, but replace old `int_pub_*` / `interfere_pk_*` values with the dashboard's regional publishable key, and replace old `ak_*` / `interfere_ak_*` build keys with the regional Interfere secret key.
95
+ Upgrade all `@interfere/*` SDK packages together. Keep the env var names, but replace old `int_pub_*` / `interfere_pk_*` values with the dashboard's regional public key, and replace old `ak_*` / `interfere_ak_*` build keys with the regional Interfere secret key.
96
96
 
97
97
  ## Ingest Endpoint
98
98
 
package/dist/config.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
2
  import { NextConfig } from "next";
3
-
4
3
  //#region src/config.d.ts
5
4
  interface NextConfigWithInterfere extends NextConfig {
6
5
  interfere?: {
@@ -88,8 +87,16 @@ interface ResolvedBuildConfig {
88
87
  */
89
88
  readonly releaseSlug: ReleaseSlug | null;
90
89
  }
91
- declare function withInterfere(upstream?: NextConfigWithInterfere): NextConfig;
92
- declare function withInterfere(settings: NextPluginConfig, config: NextConfigWithInterfere): NextConfig;
90
+ type InterfereConfigFn = (phase: string, context: {
91
+ defaultConfig: NextConfig;
92
+ }) => NextConfigWithInterfere | Promise<NextConfigWithInterfere> | PromiseLike<NextConfigWithInterfere>;
93
+ type InterfereConfigInput = NextConfigWithInterfere | InterfereConfigFn;
94
+ type ResolvedInterfereConfig<TConfig> = [TConfig] extends [(...args: never[]) => infer R] ? R extends Promise<unknown> ? Promise<NextConfig> : R extends PromiseLike<unknown> ? PromiseLike<NextConfig> : NextConfig : NextConfig;
95
+ type WithInterfereResult<TConfig> = (phase: string, context?: {
96
+ defaultConfig: NextConfig;
97
+ }) => ResolvedInterfereConfig<TConfig>;
98
+ declare function withInterfere<TConfig extends InterfereConfigInput>(upstream?: TConfig): WithInterfereResult<TConfig>;
99
+ declare function withInterfere<TConfig extends InterfereConfigInput>(settings: NextPluginConfig, config: TConfig): WithInterfereResult<TConfig>;
93
100
  interface ProductionCompileContext {
94
101
  readonly distDir: string;
95
102
  readonly projectDir: string;
package/dist/config.mjs CHANGED
@@ -1 +1 @@
1
- import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`};function reportReleaseDiagnostic(code,input){let diagnostic=diagnoseRelease(code,DIAGNOSTIC_CONTEXT,input),lines=[...diagnostic.details];if(diagnostic.severity===`fatal`){log.fatal(diagnostic.title,lines);return}log.warn(diagnostic.title,lines)}function withInterfere(settingsOrConfig={},maybeConfig){let[settings,nextConfig]=maybeConfig===void 0?[{},settingsOrConfig]:[settingsOrConfig,maybeConfig],{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(settings),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,projectDir,values:{__INTERFERE_RELEASE_SLUG__:forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,...config.publicKey&&{__INTERFERE_PUBLIC_KEY__:config.publicKey,...apiHost&&{__INTERFERE_API_URL__:apiHost}},...config.environment&&{__INTERFERE_ENVIRONMENT__:config.environment},...forceEnable&&{__INTERFERE_FORCE_ENABLE__:!0}}});if(config.publicKey!==null&&!build.clientInstrumentationApplies)return log.fatal(`Missing instrumentation client`,[`INTERFERE_PUBLIC_KEY is set but no instrumentation-client file was found.`,`Create an instrumentation-client.ts file in your project root or src/ directory.`]);config.publicKey!==null&&warnIfServerInstrumentationMissing(projectDir);let nextMajor=readNextMajorVersion(projectDir),experimental=interfereExperimentalConfig({existing:rest.experimental,instrumentationHook:nextMajor===null||nextMajor<=14,traceMetadata:config.publicKey!==null||forceEnable}),existingHook=compiler?.runAfterProductionCompile,browserSourceMaps=config.apiKey===null?productionBrowserSourceMaps:!0;return config.apiKey!==null&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),{...rest,...userEnv?{env:userEnv}:{},compiler:{...omitUndefined(compiler??{}),async runAfterProductionCompile(context){existingHook&&await existingHook(context);try{await runBuildReleasePipeline(context,config)}catch(error){if(error instanceof FatalError)throw error;let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere encountered an unexpected error.`,`Build will continue, but this release's data will be ingested, but ignored as finding when generating insights.`,`Error: ${message}`])}}},...omitUndefined({productionBrowserSourceMaps:browserSourceMaps,turbopack:build.turbopack,webpack:build.webpack}),...experimental?{experimental}:{}}}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing,...instrumentationHook?{instrumentationHook:!0}:{},...traceMetadata?{clientTraceMetadata:[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]}:{}};return Object.keys(merged).length>0?merged:void 0}function resolveBrowserApiHost(apiHost,apiUrl){let host=apiHost??apiUrl;if(host!==API_URL)return normalizeHost(host)}function normalizeHost(host){try{return new URL(host).origin}catch{return log.fatal(`Invalid apiHost`,[`interfere.apiHost must be a full URL, e.g. "https://in.yourapp.com".`,`Received: ${host}`])}}function resolveBuildConfig(settings){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(),environment=process.env.NEXT_PHASE===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,purgeSourceMaps:settings.sourceMaps?.purge??!0,releaseSlug:slug}}async function runBuildReleasePipeline(context,config){let{apiKey,buildId,releaseSlug}=config;if(!apiKey)return log.fatal(`API key not set`,[`withInterfere() requires an API key to publish release metadata during production builds.`,`Set the INTERFERE_API_KEY environment variable, or remove withInterfere() from your Next.js config.`,`See: https://interfere.com/docs/guides/next-js`]);if(!isInterfereApiKey(apiKey))return reportReleaseDiagnostic(`API_KEY_WRONG_PREFIX`,{apiKey});if(!(buildId&&releaseSlug))return log.fatal(`Commit SHA missing`,[`Could not resolve a commit SHA from environment variables or git.`,"Set `INTERFERE_SOURCE_ID`, ensure your CI exposes its git SHA env var (e.g. `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`), or run inside a git checkout."]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey,buildId,releaseSlug});if(!result.ready){log.warn(`Skipping`,[`No source maps found`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config?.org.slug}/surfaces/${result.config?.surface.slug}`,`Release: ${result.release?.destination.slug??`Unknown`}`,`Build: ${result.buildId}`,`Artifacts: ${result.fileCount} source maps`])}catch(error){let message=error instanceof Error?error.message:String(error);reportReleaseDiagnostic(classifyReleasePipelineError(error),{errorMessage:message});return}}export{withInterfere};
1
+ import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{API_URL_GLOBAL,ENVIRONMENT_GLOBAL,FORCE_ENABLE_GLOBAL,PUBLIC_KEY_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`};function reportReleaseDiagnostic(code,input){let diagnostic=diagnoseRelease(code,DIAGNOSTIC_CONTEXT,input),lines=[...diagnostic.details];if(diagnostic.severity===`fatal`){log.fatal(diagnostic.title,lines);return}log.warn(diagnostic.title,lines)}function isThenable(value){return typeof value==`object`&&!!value&&`then`in value&&typeof value.then==`function`}function withInterfere(settingsOrConfig={},maybeConfig){let[settings,nextConfig]=maybeConfig===void 0?[{},settingsOrConfig]:[settingsOrConfig,maybeConfig];return(phase,context)=>{let resolved=typeof nextConfig==`function`?nextConfig(phase,context??{defaultConfig:{}}):nextConfig;return isThenable(resolved)?Promise.resolve(resolved).then(config=>buildInterfereConfig(phase,settings,config)):buildInterfereConfig(phase,settings,resolved)}}function buildInterfereConfig(phase,settings,nextConfig){let{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(settings,phase),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,nextMajor,projectDir,remoteConfigFetch:resolveRemoteConfigFetch(config,phase),values:{[RELEASE_SLUG_GLOBAL]:releaseSlug,...config.publicKey&&{[PUBLIC_KEY_GLOBAL]:config.publicKey,...apiHost&&{[API_URL_GLOBAL]:apiHost}},...config.environment&&{[ENVIRONMENT_GLOBAL]:config.environment},...forceEnable&&{[FORCE_ENABLE_GLOBAL]:!0}}});if(config.publicKey!==null&&!build.clientInstrumentationApplies)return log.fatal(`Missing instrumentation client`,[`INTERFERE_PUBLIC_KEY is set but no instrumentation-client file was found.`,`Create an instrumentation-client.ts file in your project root or src/ directory.`]);config.publicKey!==null&&warnIfServerInstrumentationMissing(projectDir);let experimental=interfereExperimentalConfig({existing:rest.experimental,instrumentationHook:nextMajor===null||nextMajor<=14,traceMetadata:config.publicKey!==null||forceEnable}),existingHook=compiler?.runAfterProductionCompile,browserSourceMaps=config.apiKey!==null||productionBrowserSourceMaps;return config.apiKey!==null&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),{...rest,...userEnv?{env:userEnv}:{},compiler:{...omitUndefined(compiler??{}),async runAfterProductionCompile(context){existingHook&&await existingHook(context);try{await runBuildReleasePipeline(context,config)}catch(error){if(error instanceof FatalError)throw error;let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere encountered an unexpected error.`,`Build will continue, but this release's data will be ingested, but ignored as finding when generating insights.`,`Error: ${message}`])}}},...omitUndefined({productionBrowserSourceMaps:browserSourceMaps,turbopack:build.turbopack,webpack:build.webpack}),...experimental?{experimental}:{}}}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing,...instrumentationHook?{instrumentationHook:!0}:{},...traceMetadata?{clientTraceMetadata:[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]}:{}};return Object.keys(merged).length>0?merged:void 0}function resolveBrowserApiHost(apiHost,apiUrl){let host=apiHost??apiUrl;if(host!==API_URL)return normalizeHost(host)}function normalizeHost(host){try{return new URL(host).origin}catch{return log.fatal(`Invalid apiHost`,[`interfere.apiHost must be a full URL, e.g. "https://in.yourapp.com".`,`Received: ${host}`])}}function resolveBuildConfig(settings,phase){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(),environment=phase===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,purgeSourceMaps:settings.sourceMaps?.purge??!0,releaseSlug:slug}}function resolveRemoteConfigFetch(config,phase){if(!(phase!==PHASE_PRODUCTION_BUILD||!config.publicKey))return{apiUrl:config.apiUrl,publicKey:config.publicKey}}async function runBuildReleasePipeline(context,config){let{apiKey,buildId,releaseSlug}=config;if(!apiKey)return log.fatal(`Secret key not set`,[`withInterfere() requires a secret key to publish release metadata during production builds.`,`Set the INTERFERE_API_KEY environment variable, or remove withInterfere() from your Next.js config.`,`See: https://interfere.com/docs/sdk/next-js`]);if(!isInterfereApiKey(apiKey))return reportReleaseDiagnostic(`API_KEY_WRONG_PREFIX`,{apiKey});if(!(buildId&&releaseSlug))return log.fatal(`Commit SHA missing`,[`Could not resolve a commit SHA from environment variables or git.`,"Set `INTERFERE_SOURCE_ID`, ensure your CI exposes its git SHA env var (e.g. `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`), or run inside a git checkout."]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey,buildId,releaseSlug});if(!result.ready){log.warn(`Skipping`,[`No source maps found`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config?.org.slug}/surfaces/${result.config?.surface.slug}`,`Release: ${result.release?.destination.slug??`Unknown`}`,`Build: ${result.buildId}`,`Artifacts: ${result.fileCount} source maps`])}catch(error){let message=error instanceof Error?error.message:String(error);reportReleaseDiagnostic(classifyReleasePipelineError(error),{errorMessage:message});return}}export{withInterfere};
@@ -1,15 +1,14 @@
1
1
  import { capture, span } from "@interfere/react/api";
2
2
  import { Kernel, KernelInternalOptions, KernelOptions } from "@interfere/react/internal/kernel";
3
3
  import { ConsentCategory, ConsentState, GateableCategory } from "@interfere/types/sdk/plugins/manifest";
4
-
5
4
  //#region src/instrument-client.d.ts
6
5
  declare const close: () => Promise<void>, consent: {
7
- get(): import("@interfere/types/sdk/plugins/manifest").ConsentState | null;
8
- set(value?: import("@interfere/types/sdk/plugins/manifest").ConsentState): void;
9
- }, getKernel: () => import("@interfere/react/internal/kernel").Kernel, getKernelOrNull: () => import("@interfere/react/internal/kernel").Kernel | null, identity: {
10
- clear(): void;
11
- get(): import("@interfere/types/sdk/identify").IdentifyParams | null;
12
- set(params: import("@interfere/types/sdk/identify").IdentifyParams): Promise<void>;
13
- }, init: (opts?: import("@interfere/react/internal/kernel").KernelOptions) => Promise<import("@interfere/react/internal/kernel").Kernel | null>, subscribeToKernel: (listener: () => void) => () => void;
6
+ get(): import("@interfere/types/sdk/plugins/manifest").ConsentState | null;
7
+ set(value?: import("@interfere/types/sdk/plugins/manifest").ConsentState): void;
8
+ }, getKernel: () => import("@interfere/react/internal/kernel").Kernel, getKernelOrNull: () => import("@interfere/react/internal/kernel").Kernel | null, identity: {
9
+ clear(): void;
10
+ get(): import("@interfere/types/sdk/identify").IdentifyParams | null;
11
+ set(params: import("@interfere/types/sdk/identify").IdentifyParams): Promise<void>;
12
+ }, init: (opts?: import("@interfere/react/internal/kernel").KernelOptions) => Promise<import("@interfere/react/internal/kernel").Kernel | null>, subscribeToKernel: (listener: () => void) => () => void;
14
13
  //#endregion
15
14
  export { type ConsentCategory, type ConsentState, type GateableCategory, type Kernel, type KernelInternalOptions, type KernelOptions, capture, close, consent, getKernel, getKernelOrNull, identity, init, span, subscribeToKernel };
@@ -5,15 +5,13 @@ import { AttributeValue, TextMapPropagator } from "@opentelemetry/api";
5
5
  import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
6
6
  import { Instrumentation } from "@opentelemetry/instrumentation";
7
7
  import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
8
- import { MetricReader } from "@opentelemetry/sdk-metrics";
9
-
10
8
  //#region src/instrumentation.d.ts
11
9
  declare function flush(): Promise<void>;
12
10
  /**
13
11
  * Composable building blocks the Interfere SDK contributes to OTel —
14
- * span / log / metric processors, baggage propagator, undici
15
- * instrumentation, plus the resource attributes and side-effect
16
- * installers (console bridge + remote-config fetch).
12
+ * span / log / metric processors, baggage propagator, plus the
13
+ * resource attributes and side-effect installers (console bridge +
14
+ * remote-config fetch).
17
15
  *
18
16
  * Use this when you want to plug Interfere into an existing OTel
19
17
  * bootstrap (e.g. `@vercel/otel`'s `registerOTel({...})`, DataDog's
@@ -44,28 +42,15 @@ interface InterfereOtelKit {
44
42
  */
45
43
  fetchRemoteConfig: () => Promise<void>;
46
44
  /**
47
- * Instrumentations the SDK ships. Currently `UndiciInstrumentation`
48
- * pre-configured with `propagateContextUrls` / `ignoreUrls` semantics
49
- * from `ServerInstrumentationOptions`.
50
- *
51
- * **Composition with `@vercel/otel`**: `instrumentations: ["auto"]`
52
- * registers Vercel's `globalThis.fetch` wrapper, which only
53
- * auto-propagates `traceparent` to Vercel deployment URLs — outbound
54
- * fetches to a different origin (e.g. `in.interfere.com`) get a
55
- * span but no header injection unless
56
- * `instrumentationConfig.fetch.propagateContextUrls` is set, and
57
- * even then it doesn't cover the lower-level undici dispatcher path
58
- * Next.js route handlers use. Spread `kit.instrumentations` after
59
- * `"auto"` to layer this UndiciInstrumentation on top — propagation
60
- * extends to every outbound undici call (filtered via `ignoreUrls`).
61
- * Risk of double span emission on overlapping call sites is bounded:
62
- * Vercel's fetch wraps `globalThis.fetch`, this hooks the undici
63
- * dispatcher one layer lower, so duplicates only appear when both
64
- * paths actually fire on the same call.
45
+ * Instrumentations the SDK ships. Currently empty — outbound HTTP
46
+ * spans come from Next.js's own `patchFetch` (App Router) or
47
+ * `@vercel/otel`'s fetch wrap, so we don't layer a redundant undici
48
+ * instrumentation and stay off the request hot path. Kept in the
49
+ * kit shape for forward-compatibility with composed setups that
50
+ * spread `[...kit.instrumentations, ...]` into `registerOTel({...})`.
65
51
  */
66
52
  instrumentations: Instrumentation[];
67
53
  logRecordProcessors: LogRecordProcessor[];
68
- metricReaders: MetricReader[];
69
54
  /**
70
55
  * W3C baggage propagator. Trace context is intentionally *not*
71
56
  * included — `@vercel/otel`'s `propagators: ["auto"]` already
@@ -1,7 +1,6 @@
1
1
  import { ServerInstrumentationOptions } from "./internal/server/instrumentation-options.mjs";
2
2
  import { onRequestError } from "./internal/server/capture.mjs";
3
3
  import { InterfereOtelKit } from "./instrumentation.mjs";
4
-
5
4
  //#region src/instrumentation.edge.d.ts
6
5
  /**
7
6
  * Edge-runtime bootstrap for `@interfere/next/instrumentation`. Selected by
@@ -12,7 +11,9 @@ import { InterfereOtelKit } from "./instrumentation.mjs";
12
11
  * console bridge, `node:child_process`/`node:crypto` release-slug derivation)
13
12
  * can't run on edge, so this builds an edge-safe pipeline instead: a
14
13
  * `BasicTracerProvider` + `SimpleSpanProcessor` exporting via {@link FetchTraceExporter}.
15
- * `onRequestError` then records middleware / edge route-handler errors onto it.
14
+ * Under `serverTracing: "drain"` the exporter is omitted and traces come from
15
+ * the platform trace drain instead — the provider is still registered so
16
+ * `onRequestError` can record exception events on the active span.
16
17
  *
17
18
  * Release slug is `LOCAL_DEV_RELEASE_SLUG` under force-enable (dev); a
18
19
  * build-baked slug for production edge is a follow-up.
@@ -1 +1 @@
1
- import{readInterfereEnv}from"./internal/env.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/edge-exporter.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{withPublicKey}from"./internal/url.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{trace}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;function register(opts={}){if(registered)return Promise.resolve();let env=readInterfereEnv();if(!env.publicKey)return Promise.resolve();let releaseSlug=process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE?LOCAL_DEV_RELEASE_SLUG:null;releaseSlug||console.warn(`[interfere] edge runtime has no build-baked release.slug; the collector will reject edge spans until one is wired (production follow-up).`);let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),provider=new BasicTracerProvider({resource:resourceFromAttributes({"service.name":opts.serviceName??`interfere-sdk-next-edge`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:[new SimpleSpanProcessor(new FetchTraceExporter(sinkUrl,{"x-interfere-producer-version":PRODUCER_VERSION}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(()=>provider.forceFlush()),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/edge-exporter.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{withPublicKey}from"./internal/url.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{readServerTracingMode}from"@interfere/types/sdk/server-tracing";import{trace}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;function register(opts={}){if(registered)return Promise.resolve();let env=readInterfereEnv();if(!env.publicKey)return Promise.resolve();let releaseSlug=process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE?LOCAL_DEV_RELEASE_SLUG:null;releaseSlug||console.warn(`[interfere] edge runtime has no build-baked release.slug; the collector will reject edge spans until one is wired (production follow-up).`);let isDrainMode=readServerTracingMode()===`drain`;isDrainMode&&console.info(`[interfere] edge server tracing in drain mode; SDK trace exporter disabled — server traces come from the platform trace drain.`);let provider=new BasicTracerProvider({resource:resourceFromAttributes({"service.name":opts.serviceName??`interfere-sdk-next-edge`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:isDrainMode?[]:[new SimpleSpanProcessor(new FetchTraceExporter(withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),{"x-interfere-producer-version":PRODUCER_VERSION}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(()=>provider.forceFlush()),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
@@ -1 +1 @@
1
- import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{withPublicKey}from"./internal/url.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{metrics,propagation}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{AggregationTemporalityPreference,OTLPMetricExporter}from"@opentelemetry/exporter-metrics-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{UndiciInstrumentation}from"@opentelemetry/instrumentation-undici";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";let registered=!1,tracerProvider=null,loggerProvider=null,meterProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush(),loggerProvider?.forceFlush(),meterProvider?.forceFlush()])}function matchesAny(url,patterns){for(let pattern of patterns)if(pattern instanceof RegExp?pattern.test(url):url.includes(pattern))return!0;return!1}function buildInterfereOtelKit(opts={}){let env=readInterfereEnv();if(!env.publicKey)return null;let{slug:releaseSlug}=resolveReleaseSlug();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.");let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),ignoreUrls=[sinkUrl,...opts.ignoreUrls??[]],propagateContextUrls=opts.propagateContextUrls??[],exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION};return{resourceAttributes:{"service.name":opts.serviceName??`interfere-sdk-next-server`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}},spanProcessors:[new BaggageSpanProcessor(()=>!0),new BatchSpanProcessor(new OTLPTraceExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalSpanProcessors??[]],logRecordProcessors:[new BatchLogRecordProcessor(new OTLPLogExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalLogRecordProcessors??[]],metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter({url:sinkUrl,headers:exporterHeaders,temporalityPreference:AggregationTemporalityPreference.DELTA}),exportIntervalMillis:3e4}),...opts._internalAdditionalMetricReaders??[]],propagators:[new W3CBaggagePropagator],instrumentations:[new UndiciInstrumentation({ignoreRequestHook:req=>matchesAny(`${req.origin}${req.path}`,ignoreUrls),requestHook:(span,req)=>{matchesAny(`${req.origin}${req.path}`,propagateContextUrls)&&span.setAttribute(`interfere.propagated`,!0)}})],enableConsoleBridge:()=>bridgeConsoleToOtel(),fetchRemoteConfig:()=>fetchAndCacheRemoteConfig()}}async function register(opts={}){if(registered)return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);tracerProvider=new NodeTracerProvider({resource,spanProcessors:kit.spanProcessors,idGenerator:new PrerenderSafeIdGenerator}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,...kit.propagators]})),tracerProvider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),loggerProvider=new LoggerProvider({resource,processors:kit.logRecordProcessors});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),meterProvider=new MeterProvider({resource,readers:kit.metricReaders}),metrics.setGlobalMeterProvider(meterProvider),registerInstrumentations({tracerProvider,meterProvider,instrumentations:kit.instrumentations}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),setServerFlush(flush),registered=!0,await kit.fetchRemoteConfig()}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
1
+ import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{withPublicKey}from"./internal/url.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{readServerTracingMode}from"@interfere/types/sdk/server-tracing";import{propagation}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{readLogSourcingMode}from"@interfere/types/sdk/log-sourcing";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{CompressionAlgorithm}from"@opentelemetry/otlp-exporter-base";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";const EXPORT_TIMEOUT_MS=2e3;let registered=!1,tracerProvider=null,loggerProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush(),loggerProvider?.forceFlush()])}function buildInterfereOtelKit(opts={}){let env=readInterfereEnv();if(!env.publicKey)return null;let{slug:releaseSlug}=resolveReleaseSlug();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.");let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION},resourceAttributes={"service.name":opts.serviceName??`interfere-sdk-next-server`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}},compression=opts.compression===`gzip`?CompressionAlgorithm.GZIP:CompressionAlgorithm.NONE,isDrainMode=readServerTracingMode()===`drain`,spanProcessors=[new BaggageSpanProcessor(()=>!0),...isDrainMode?[]:[new BatchSpanProcessor(new OTLPTraceExporter({url:sinkUrl,headers:exporterHeaders,compression,timeoutMillis:EXPORT_TIMEOUT_MS}))],...opts._internalAdditionalSpanProcessors??[]],isLogDrainMode=readLogSourcingMode()===`drain`;return{resourceAttributes,spanProcessors,logRecordProcessors:[...isLogDrainMode?[]:[new BatchLogRecordProcessor(new OTLPLogExporter({url:sinkUrl,headers:exporterHeaders,compression,timeoutMillis:EXPORT_TIMEOUT_MS}))],...opts._internalAdditionalLogRecordProcessors??[]],propagators:[new W3CBaggagePropagator],instrumentations:[],enableConsoleBridge:()=>isLogDrainMode?()=>void 0:bridgeConsoleToOtel(),fetchRemoteConfig:()=>fetchAndCacheRemoteConfig()}}async function register(opts={}){if(registered)return;let kit=buildInterfereOtelKit(opts);if(!kit)return;readServerTracingMode()===`drain`&&console.info(`[interfere] server tracing in drain mode; SDK trace exporter disabled — server traces come from the platform trace drain.`);let resource=resourceFromAttributes(kit.resourceAttributes);tracerProvider=new NodeTracerProvider({resource,spanProcessors:kit.spanProcessors,idGenerator:new PrerenderSafeIdGenerator}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,...kit.propagators]})),tracerProvider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),loggerProvider=new LoggerProvider({resource,processors:kit.logRecordProcessors});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),registerInstrumentations({tracerProvider,instrumentations:kit.instrumentations}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),setServerFlush(flush),registered=!0}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
@@ -1,5 +1,4 @@
1
1
  import { NextConfig } from "next";
2
-
3
2
  //#region src/internal/build/configure-build.d.ts
4
3
  interface InterfereInjectedValues {
5
4
  readonly __INTERFERE_API_URL__?: string;
@@ -8,10 +7,22 @@ interface InterfereInjectedValues {
8
7
  readonly __INTERFERE_PUBLIC_KEY__?: string;
9
8
  readonly __INTERFERE_RELEASE_SLUG__: string | null;
10
9
  }
10
+ /**
11
+ * Inputs for the loader's build-time `/v2/config` fetch that resolves the
12
+ * surface's remote config (`serverTracing` mode + `ingestUrl`). Omitted
13
+ * outside production builds or when no public key is set — the loader then
14
+ * bakes no remote-config globals.
15
+ */
16
+ interface RemoteConfigFetch {
17
+ readonly apiUrl: string;
18
+ readonly publicKey: string;
19
+ }
11
20
  interface ConfigureBuildInput {
12
21
  readonly existingTurbopack: NextConfig["turbopack"] | undefined;
13
22
  readonly existingWebpack: NextConfig["webpack"] | undefined;
23
+ readonly nextMajor: number | null;
14
24
  readonly projectDir: string;
25
+ readonly remoteConfigFetch?: RemoteConfigFetch | undefined;
15
26
  readonly values: InterfereInjectedValues;
16
27
  }
17
28
  interface ConfigureBuildOutput {
@@ -19,11 +30,6 @@ interface ConfigureBuildOutput {
19
30
  readonly turbopack: NextConfig["turbopack"] | undefined;
20
31
  readonly webpack: NextConfig["webpack"] | undefined;
21
32
  }
22
- declare function configureBuild({
23
- existingWebpack,
24
- existingTurbopack,
25
- projectDir,
26
- values
27
- }: ConfigureBuildInput): ConfigureBuildOutput;
33
+ declare function configureBuild({ existingWebpack, existingTurbopack, nextMajor, projectDir, remoteConfigFetch, values }: ConfigureBuildInput): ConfigureBuildOutput;
28
34
  //#endregion
29
- export { InterfereInjectedValues, configureBuild };
35
+ export { RemoteConfigFetch, configureBuild };
@@ -1 +1 @@
1
- import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_CLIENT_FILES=[`instrumentation-client.ts`,`instrumentation-client.tsx`,`instrumentation-client.js`,`instrumentation-client.jsx`,`src/instrumentation-client.ts`,`src/instrumentation-client.tsx`,`src/instrumentation-client.js`,`src/instrumentation-client.jsx`],INSTRUMENTATION_SERVER_FILES=[`instrumentation.ts`,`instrumentation.tsx`,`instrumentation.js`,`instrumentation.jsx`,`src/instrumentation.ts`,`src/instrumentation.tsx`,`src/instrumentation.js`,`src/instrumentation.jsx`],LOADER_FILE_CANDIDATES=[`value-injection-loader.ts`,`value-injection-loader.js`,`value-injection-loader.mjs`,`value-injection-loader.cjs`];function configureBuild({existingWebpack,existingTurbopack,projectDir,values}){let clientPath=resolveFirstExisting(projectDir,INSTRUMENTATION_CLIENT_FILES),targets=[clientPath,resolveFirstExisting(projectDir,INSTRUMENTATION_SERVER_FILES)].filter(path=>path!==null),clientInstrumentationApplies=!!(clientPath&&hasInjectedMetadata(values));return{clientInstrumentationApplies,webpack:buildWebpackHook(existingWebpack,targets,values,clientInstrumentationApplies),turbopack:buildTurbopackConfig(existingTurbopack,targets,values,clientInstrumentationApplies)}}function hasInjectedMetadata(metadata){return metadata.__INTERFERE_RELEASE_SLUG__!==null||metadata.__INTERFERE_PUBLIC_KEY__!==void 0}function buildWebpackHook(existingWebpack,targets,values,injectionApplies){if(!injectionApplies)return existingWebpack;let normalizedTargets=new Set(targets.map(path=>normalize(path))),injectionLoaderPath=resolveLoaderPath();return(webpackConfig,options)=>{let outputConfig=existingWebpack?existingWebpack(webpackConfig,options):webpackConfig,mutableConfig=outputConfig;return mutableConfig.module??={},mutableConfig.module.rules??=[],mutableConfig.module.rules.push({test:resource=>!!resource&&normalizedTargets.has(normalize(resource)),use:[{loader:injectionLoaderPath,options:{values}}]}),outputConfig}}function buildTurbopackConfig(existingTurbopack,targets,values,injectionApplies){let baseConfig=existingTurbopack??{},debugIds=baseConfig.debugIds??!0;if(!injectionApplies)return existingTurbopack===void 0&&debugIds===baseConfig.debugIds?existingTurbopack:{...baseConfig,debugIds};let rules={...baseConfig.rules},loaderPath=resolveLoaderPath();for(let target of targets){let ruleKey=`**/${basename(target)}`;rules[ruleKey]=appendTurbopackLoader(rules[ruleKey],{loader:loaderPath,options:{serializedValues:JSON.stringify(values)}})}return{...baseConfig,debugIds,rules}}function appendTurbopackLoader(existing,loader){let rule=existing??{},loaders=Array.isArray(rule.loaders)?rule.loaders:[];return{...rule,loaders:[...loaders,loader]}}function resolveFirstExisting(projectDir,candidates){for(let candidate of candidates){let filePath=resolve(projectDir,candidate);if(existsSync(filePath))return filePath}return null}function resolveLoaderPath(){let directory=resolve(dirname(fileURLToPath(import.meta.url)));for(let candidate of LOADER_FILE_CANDIDATES){let filePath=join(directory,candidate);if(existsSync(filePath))return filePath}return join(directory,`value-injection-loader.js`)}export{configureBuild};
1
+ import{omitUndefined}from"@interfere/helpers/omit-undefined";import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_EXTENSIONS=[`ts`,`tsx`,`js`,`jsx`];function instrumentationCandidates(base){return[``,`src/`].flatMap(dir=>INSTRUMENTATION_EXTENSIONS.map(ext=>`${dir}${base}.${ext}`))}const LOADER_FILE_CANDIDATES=[`ts`,`js`,`mjs`,`cjs`].map(ext=>`value-injection-loader.${ext}`);function configureBuild({existingWebpack,existingTurbopack,nextMajor,projectDir,remoteConfigFetch,values}){let clientPath=resolveFirstExisting(projectDir,instrumentationCandidates(`instrumentation-client`)),targets=[clientPath,resolveFirstExisting(projectDir,instrumentationCandidates(`instrumentation`))].filter(path=>path!==null),hasMetadata=values.__INTERFERE_RELEASE_SLUG__!==null||values.__INTERFERE_PUBLIC_KEY__!==void 0,injectionApplies=targets.length>0&&hasMetadata;return{clientInstrumentationApplies:!!clientPath&&hasMetadata,webpack:buildWebpackHook(existingWebpack,targets,values,remoteConfigFetch,injectionApplies),turbopack:buildTurbopackConfig(existingTurbopack,targets,values,remoteConfigFetch,injectionApplies,nextMajor)}}function buildWebpackHook(existingWebpack,targets,values,remoteConfigFetch,injectionApplies){if(!injectionApplies)return existingWebpack;let normalizedTargets=new Set(targets.map(path=>normalize(path))),injectionLoaderPath=resolveLoaderPath();return(webpackConfig,options)=>{let outputConfig=existingWebpack?existingWebpack(webpackConfig,options):webpackConfig,mutableConfig=outputConfig;return mutableConfig.module??={},mutableConfig.module.rules??=[],mutableConfig.module.rules.push({test:resource=>!!resource&&normalizedTargets.has(normalize(resource)),use:[{loader:injectionLoaderPath,options:omitUndefined({serializedValues:JSON.stringify(values),serializedFetch:remoteConfigFetch?JSON.stringify(remoteConfigFetch):void 0})}]}),outputConfig}}function buildTurbopackConfig(existingTurbopack,targets,values,remoteConfigFetch,injectionApplies,nextMajor){let baseConfig=existingTurbopack??{},debugIds=nextMajor===null||nextMajor>=16?baseConfig.debugIds??!0:baseConfig.debugIds;if(!injectionApplies)return omitUndefined({...baseConfig,debugIds});let rules={...baseConfig.rules},loaderPath=resolveLoaderPath();for(let target of targets){let ruleKey=`**/${basename(target)}`;rules[ruleKey]=appendTurbopackLoader(rules[ruleKey],{loader:loaderPath,options:omitUndefined({serializedValues:JSON.stringify(values),serializedFetch:remoteConfigFetch?JSON.stringify(remoteConfigFetch):void 0})})}return omitUndefined({...baseConfig,debugIds,rules})}function appendTurbopackLoader(existing,loader){let rule=existing??{},loaders=Array.isArray(rule.loaders)?rule.loaders:[];return{...rule,loaders:[...loaders,loader]}}function resolveFirstExisting(projectDir,candidates){for(let candidate of candidates){let filePath=resolve(projectDir,candidate);if(existsSync(filePath))return filePath}return null}function resolveLoaderPath(){let directory=resolve(dirname(fileURLToPath(import.meta.url)));for(let candidate of LOADER_FILE_CANDIDATES){let filePath=join(directory,candidate);if(existsSync(filePath))return filePath}return join(directory,`value-injection-loader.js`)}export{configureBuild};
@@ -1,7 +1,6 @@
1
1
  import { ManifestBundler } from "@interfere/types/data/source-maps";
2
-
3
2
  //#region src/internal/build/detect-bundler.d.ts
4
3
  type NextBuildBundler = Extract<ManifestBundler, "webpack" | "turbopack">;
5
- declare function detectActiveBundler(env?: Readonly<Record<string, string | undefined>>, argv?: readonly string[]): NextBuildBundler;
4
+ declare function detectActiveBundler(env?: Readonly<Record<string, string | undefined>>, argv?: readonly string[], nextMajor?: number | null): NextBuildBundler;
6
5
  //#endregion
7
6
  export { NextBuildBundler, detectActiveBundler };
@@ -1 +1 @@
1
- const TURBOPACK_FLAGS=[`--turbo`,`--turbopack`];function detectActiveBundler(env=process.env,argv=process.argv){return env.TURBOPACK===`1`||TURBOPACK_FLAGS.some(flag=>argv.includes(flag))?`turbopack`:`webpack`}export{detectActiveBundler};
1
+ const TURBOPACK_FLAGS=[`--turbo`,`--turbopack`];function detectActiveBundler(env=process.env,argv=process.argv,nextMajor=null){return argv.includes(`--webpack`)?`webpack`:env.TURBOPACK===`1`||TURBOPACK_FLAGS.some(flag=>argv.includes(flag))||nextMajor!==null&&nextMajor>=16?`turbopack`:`webpack`}export{detectActiveBundler};
@@ -1,7 +1,6 @@
1
1
  import { ProductionCompileContext, ResolvedBuildConfig } from "../../config.mjs";
2
2
  import { ReleaseSlug } from "@interfere/types/releases/slug";
3
3
  import { ReleasesConfigResponse } from "@interfere/sdk/models/releases-config-response.js";
4
-
5
4
  //#region src/internal/build/pipeline.d.ts
6
5
  interface BuildTiming {
7
6
  cleanup: number;
@@ -1 +1 @@
1
- import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{uploadSourceMaps}from"./source-maps/upload.mjs";import{cleanupSourceMaps}from"./source-maps/index.mjs";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{HTTPClient,Interfere}from"@interfere/sdk";async function timed(fn){let start=performance.now();return[await fn(),Math.round(performance.now()-start)]}async function runBuildPipeline(context,metadata){let{apiUrl,apiKey}=metadata,httpClient=new HTTPClient;httpClient.addHook(`beforeRequest`,request=>{let nextRequest=new Request(request);return nextRequest.headers.set(`Authorization`,`Bearer ${apiKey}`),nextRequest});let sdk=new Interfere({serverURL:apiUrl,httpClient}),security={apiKey},start=performance.now(),bundler=detectActiveBundler(),[{discovered,config},discoverMs]=await timed(async()=>{let[discovered,config]=await Promise.all([discoverSourceMaps({bundler,projectDir:context.projectDir,distDir:context.distDir}),sdk.releases.getConfig(security)]);return{discovered,config}});if(discovered.files.length===0)return{ready:!1,reason:`no_source_maps`,fileCount:0};let releaseRequest=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config),[release,createReleaseMs]=await timed(()=>sdk.releases.create(security,releaseRequest)),releaseSlug=releaseSlugSchema.parse(release.destination.slug),buildId=release.build.hash??metadata.buildId,[{totalBytes},uploadMs]=await timed(()=>uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug})),[,preflightMs]=await timed(()=>sdk.releases.preflight(security,{releaseSlug})),[,cleanupMs]=await timed(async()=>{metadata.purgeSourceMaps&&await cleanupSourceMaps(discovered.files)}),timing={discover:discoverMs,createRelease:createReleaseMs,upload:uploadMs,preflight:preflightMs,cleanup:cleanupMs,total:Math.round(performance.now()-start),fileCount:discovered.files.length,totalBytes};return{ready:!0,fileCount:discovered.files.length,release:{...release,destination:{...release.destination,slug:releaseSlug}},config,buildId,timing}}export{runBuildPipeline};
1
+ import{readNextMajorVersion}from"../setup-warnings.mjs";import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{assertClientChunksAreMapped}from"./source-maps/guard.mjs";import{uploadSourceMaps}from"./source-maps/upload.mjs";import{cleanupSourceMaps}from"./source-maps/index.mjs";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{HTTPClient,Interfere}from"@interfere/sdk";async function timed(fn){let start=performance.now();return[await fn(),Math.round(performance.now()-start)]}async function runBuildPipeline(context,metadata){let{apiUrl,apiKey}=metadata,httpClient=new HTTPClient;httpClient.addHook(`beforeRequest`,request=>{let nextRequest=new Request(request);return nextRequest.headers.set(`Authorization`,`Bearer ${apiKey}`),nextRequest});let sdk=new Interfere({serverURL:apiUrl,httpClient}),security={apiKey},start=performance.now(),bundler=detectActiveBundler(process.env,process.argv,readNextMajorVersion(context.projectDir)),[{discovered,config},discoverMs]=await timed(async()=>{let[discovered,config]=await Promise.all([discoverSourceMaps({bundler,projectDir:context.projectDir,distDir:context.distDir}),sdk.releases.getConfig(security)]);return{discovered,config}});if(discovered.files.length===0)return{ready:!1,reason:`no_source_maps`,fileCount:0};await assertClientChunksAreMapped({projectDir:context.projectDir,distDir:context.distDir,discovered:discovered.files});let releaseRequest=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config),[release,createReleaseMs]=await timed(()=>sdk.releases.create(security,releaseRequest)),releaseSlug=releaseSlugSchema.parse(release.destination.slug),buildId=release.build.hash??metadata.buildId,[{totalBytes},uploadMs]=await timed(()=>uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug})),[,preflightMs]=await timed(()=>sdk.releases.preflight(security,{releaseSlug})),[,cleanupMs]=await timed(async()=>{metadata.purgeSourceMaps&&await cleanupSourceMaps(discovered.files)}),timing={discover:discoverMs,createRelease:createReleaseMs,upload:uploadMs,preflight:preflightMs,cleanup:cleanupMs,total:Math.round(performance.now()-start),fileCount:discovered.files.length,totalBytes};return{ready:!0,fileCount:discovered.files.length,release:{...release,destination:{...release.destination,slug:releaseSlug}},config,buildId,timing}}export{runBuildPipeline};
@@ -1,6 +1,5 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
2
  import { ReleaseDestinationMetadata } from "@interfere/types/integrations";
3
-
4
3
  //#region src/internal/build/release/destinations/cli.d.ts
5
4
  declare function resolve(releaseSlug: ReleaseSlug): ReleaseDestinationMetadata;
6
5
  //#endregion
@@ -1,5 +1,4 @@
1
1
  import { ReleaseDestinationMetadata } from "@interfere/types/integrations";
2
-
3
2
  //#region src/internal/build/release/destinations/cloudflare.d.ts
4
3
  declare function resolve(): ReleaseDestinationMetadata;
5
4
  //#endregion
@@ -1,6 +1,5 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
2
  import { ReleaseDestinationMetadata, ReleaseDestinationProvider } from "@interfere/types/integrations";
3
-
4
3
  //#region src/internal/build/release/destinations/index.d.ts
5
4
  /**
6
5
  * Per-provider destination metadata resolvers. Mirrors `sources/` —
@@ -1,5 +1,4 @@
1
1
  import { ReleaseDestinationMetadata } from "@interfere/types/integrations";
2
-
3
2
  //#region src/internal/build/release/destinations/vercel.d.ts
4
3
  declare function resolve(): ReleaseDestinationMetadata;
5
4
  //#endregion
@@ -1,7 +1,6 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
2
  import { ReleasesConfigResponse } from "@interfere/sdk/models/releases-config-response.js";
3
3
  import { CreateReleaseRequest } from "@interfere/types/releases/definition";
4
-
5
4
  //#region src/internal/build/release/index.d.ts
6
5
  declare function resolveReleaseRequest(buildId: string, environment: string | null, releaseSlug: ReleaseSlug, config: ReleasesConfigResponse): CreateReleaseRequest;
7
6
  //#endregion
@@ -1,5 +1,4 @@
1
1
  import { ReleaseSourceMetadata } from "@interfere/types/integrations";
2
-
3
2
  //#region src/internal/build/release/sources/github.d.ts
4
3
  type GithubReleaseSource = Extract<ReleaseSourceMetadata, {
5
4
  provider: "github";
@@ -1,5 +1,4 @@
1
1
  import { ReleaseSourceMetadata, SourceProvider } from "@interfere/types/integrations";
2
-
3
2
  //#region src/internal/build/release/sources/index.d.ts
4
3
  /**
5
4
  * Per-provider source metadata resolvers. Each entry knows how to read
@@ -1,5 +1,4 @@
1
1
  import { SourceMapFile } from "./discover.mjs";
2
-
3
2
  //#region src/internal/build/source-maps/discover-turbopack.d.ts
4
3
  /**
5
4
  * Turbopack-mode discovery.
@@ -1,5 +1,4 @@
1
1
  import { SourceMapFile } from "./discover.mjs";
2
-
3
2
  //#region src/internal/build/source-maps/discover-webpack.d.ts
4
3
  declare function injectDebugIdIntoMap(content: string, debugId: string): string;
5
4
  declare function discoverWebpack(opts: {
@@ -1,7 +1,6 @@
1
1
  import { NextBuildBundler } from "../detect-bundler.mjs";
2
2
  import { injectDebugIdIntoMap } from "./discover-webpack.mjs";
3
3
  import { injectDebugIdIntoJs } from "@interfere/types/data/source-maps";
4
-
5
4
  //#region src/internal/build/source-maps/discover.d.ts
6
5
  interface SourceMapFile {
7
6
  readonly absolute: string;
@@ -0,0 +1,9 @@
1
+ import { SourceMapFile } from "./discover.mjs";
2
+ //#region src/internal/build/source-maps/guard.d.ts
3
+ declare function assertClientChunksAreMapped(opts: {
4
+ projectDir: string;
5
+ distDir: string;
6
+ discovered: readonly SourceMapFile[];
7
+ }): Promise<void>;
8
+ //#endregion
9
+ export { assertClientChunksAreMapped };
@@ -0,0 +1 @@
1
+ import{log}from"../../logger.mjs";import{resolveDistDir,walkDistTrees}from"./paths.mjs";import{readFile}from"node:fs/promises";import{readDebugIdFromJs}from"@interfere/types/data/source-maps";async function assertClientChunksAreMapped(opts){if(opts.discovered.some(file=>file.chunkUrl.includes(`static/chunks`)))return;let clientChunks=(await walkDistTrees(resolveDistDir(opts.projectDir,opts.distDir),`.js`)).filter(path=>path.replaceAll(`\\`,`/`).includes(`/static/chunks/`)),stampedCount=(await Promise.all(clientChunks.map(async jsAbs=>readDebugIdFromJs(await readFile(jsAbs,`utf8`).catch(()=>``))!==null))).filter(Boolean).length;stampedCount!==0&&log.fatal(`Client source maps were not uploaded`,[`${stampedCount} client chunk(s) carry debug IDs but no source maps were uploaded — browser errors from this release won't symbolicate.`,`A coexisting build tool likely deleted them before withInterfere() ran; make withInterfere() the outer wrapper so its discovery runs first, or disable that tool's post-upload map deletion.`])}export{assertClientChunksAreMapped};
@@ -1,6 +1,5 @@
1
1
  import { SourceMapFile } from "./discover.mjs";
2
2
  import { uploadSourceMaps } from "./upload.mjs";
3
-
4
3
  //#region src/internal/build/source-maps/index.d.ts
5
4
  declare function cleanupSourceMaps(files: SourceMapFile[]): Promise<void>;
6
5
  //#endregion
@@ -2,7 +2,6 @@ import { DiscoveryResult } from "./discover.mjs";
2
2
  import { ReleaseSlug } from "@interfere/types/releases/slug";
3
3
  import { HTTPClient } from "@interfere/sdk";
4
4
  import { ManifestBundler } from "@interfere/types/data/source-maps";
5
-
6
5
  //#region src/internal/build/source-maps/upload.d.ts
7
6
  interface UploadResult {
8
7
  fileCount: number;
@@ -28,13 +27,7 @@ interface UploadParams {
28
27
  * limits, not Cloudflare worker / load-balancer body-size caps. The
29
28
  * legacy multipart endpoint is still wired for older SDKs.
30
29
  */
31
- declare function uploadSourceMaps({
32
- apiUrl,
33
- bundler,
34
- discovered,
35
- httpClient,
36
- releaseSlug
37
- }: UploadParams): Promise<UploadResult>;
30
+ declare function uploadSourceMaps({ apiUrl, bundler, discovered, httpClient, releaseSlug }: UploadParams): Promise<UploadResult>;
38
31
  interface SourceMapRichness {
39
32
  hasSourcesContent: boolean;
40
33
  hasNames: boolean;
@@ -1,13 +1,14 @@
1
- import { InterfereInjectedValues } from "./configure-build.mjs";
2
-
3
1
  //#region src/internal/build/value-injection-loader.d.ts
2
+ type BakedGlobals = Record<string, unknown>;
4
3
  interface ValueInjectionLoaderOptions {
4
+ readonly serializedFetch?: string;
5
5
  readonly serializedValues?: string;
6
- readonly values?: Partial<InterfereInjectedValues>;
7
6
  }
8
7
  interface LoaderThis<T> {
8
+ async(): (error: Error | null, content?: string) => void;
9
9
  getOptions(): T;
10
10
  }
11
- declare function valueInjectionLoader(this: LoaderThis<ValueInjectionLoaderOptions>, userCode: string): string;
11
+ declare function valueInjectionLoader(this: LoaderThis<ValueInjectionLoaderOptions>, userCode: string): void;
12
+ declare function remoteGlobals(raw: unknown): BakedGlobals;
12
13
  //#endregion
13
- export { ValueInjectionLoaderOptions, valueInjectionLoader as default };
14
+ export { ValueInjectionLoaderOptions, valueInjectionLoader as default, remoteGlobals };
@@ -1,2 +1,2 @@
1
- function valueInjectionLoader(userCode){let values=resolveValues(this.getOptions()),lines=Object.entries(values).filter(([,value])=>value!==void 0).map(([key,value])=>`globalThis["${key.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"] = ${JSON.stringify(value)};`);return lines.length===0?userCode:`${lines.join(`
2
- `)}\n${userCode}`}function resolveValues(options){if(typeof options.serializedValues==`string`)try{let parsed=JSON.parse(options.serializedValues);return isRecord(parsed)?parsed:{}}catch{return{}}return options.values??{}}function isRecord(value){return typeof value==`object`&&!!value&&!Array.isArray(value)}export{valueInjectionLoader as default};
1
+ import{omitUndefined}from"@interfere/helpers/omit-undefined";import{INGEST_URL_GLOBAL}from"@interfere/types/sdk/injected-globals";import{SERVER_TRACING_GLOBAL}from"@interfere/types/sdk/server-tracing";import{PLUGINS_GLOBAL,remoteConfigSchema}from"@interfere/types/sdk/remote-config";import{LOG_SOURCING_GLOBAL}from"@interfere/types/sdk/log-sourcing";let remoteConfigPromise=null;function valueInjectionLoader(userCode){let options=this.getOptions(),callback=this.async(),values=resolveValues(options);resolveRemoteConfig(resolveFetch(options)).then(globals=>{callback(null,render({...values,...globals},userCode))}).catch(()=>callback(null,render(values,userCode)))}function remoteGlobals(raw){if(!isRecord(raw))return{};let{serverTracing,logSourcing,ingestUrl,plugins}=raw,{shape}=remoteConfigSchema;return omitUndefined({[SERVER_TRACING_GLOBAL]:shape.serverTracing.catch(void 0).parse(serverTracing),[LOG_SOURCING_GLOBAL]:shape.logSourcing.catch(void 0).parse(logSourcing),[INGEST_URL_GLOBAL]:shape.ingestUrl.catch(void 0).parse(ingestUrl),[PLUGINS_GLOBAL]:shape.plugins.partial().optional().catch(void 0).parse(plugins)})}function render(values,userCode){let lines=Object.entries(values).map(([key,value])=>`globalThis[${JSON.stringify(key)}] = ${JSON.stringify(value)};`);return lines.length===0?userCode:`${lines.join(`
2
+ `)}\n${userCode}`}function resolveRemoteConfig(fetchOptions){return fetchOptions?(remoteConfigPromise??=fetchRemoteConfig(fetchOptions),remoteConfigPromise):Promise.resolve({})}async function fetchRemoteConfig(fetchOptions){try{let url=`${fetchOptions.apiUrl}/v2/config?pk=${encodeURIComponent(fetchOptions.publicKey)}`,response=await fetch(url,{method:`GET`,headers:{"content-type":`application/json`},signal:AbortSignal.timeout(3e3)});return response.ok?remoteGlobals(await response.json()):{}}catch{return{}}}function parseJson(text){if(typeof text==`string`)try{return JSON.parse(text)}catch{return}}function resolveFetch(options){let parsed=parseJson(options.serializedFetch);return isRemoteConfigFetch(parsed)?parsed:null}function isRemoteConfigFetch(value){return isRecord(value)&&typeof value.apiUrl==`string`&&typeof value.publicKey==`string`}function resolveValues(options){let parsed=parseJson(options.serializedValues);return isRecord(parsed)?parsed:{}}function isRecord(value){return typeof value==`object`&&!!value&&!Array.isArray(value)}export{valueInjectionLoader as default,remoteGlobals};
@@ -1 +1 @@
1
- import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return process.env.NODE_ENV===`production`?!0:!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env});return{apiKey:parseEnvValue(process.env.INTERFERE_API_KEY),apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nextRuntime:parseEnvValue(process.env.NEXT_RUNTIME),nodeEnvironment,publicKey:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID),destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID)}}}export{isEnabledOnServer,readInterfereEnv};
1
+ import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return process.env.NODE_ENV===`production`||!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env});return{apiKey:parseEnvValue(process.env.INTERFERE_API_KEY),apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nextRuntime:parseEnvValue(process.env.NEXT_RUNTIME),nodeEnvironment,publicKey:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID),destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID)}}}export{isEnabledOnServer,readInterfereEnv};
@@ -1,5 +1,4 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
-
3
2
  //#region src/internal/release-slug.d.ts
4
3
  declare function resolveReleaseSlug(): {
5
4
  commitSha: string | null;
@@ -1 +1 @@
1
- import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";function readBakedReleaseSlug(){let value=globalThis.__INTERFERE_RELEASE_SLUG__;if(typeof value!=`string`)return null;let parsed=releaseSlugSchema.safeParse(value);return parsed.success?parsed.data:null}function resolveCommitSha(){return readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
1
+ import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";function readBakedReleaseSlug(){let value=globalThis[RELEASE_SLUG_GLOBAL];if(typeof value!=`string`)return null;let parsed=releaseSlugSchema.safeParse(value);return parsed.success?parsed.data:null}function resolveCommitSha(){return readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
@@ -1,5 +1,4 @@
1
1
  import { CaptureErrorContext, OnRequestErrorContext } from "./types.mjs";
2
-
3
2
  //#region src/internal/server/capture.d.ts
4
3
  declare function captureError(error: unknown, _request?: unknown, context?: CaptureErrorContext): void;
5
4
  interface OnRequestErrorRequest {
@@ -1,6 +1,5 @@
1
1
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
2
2
  import { ExportResult } from "@opentelemetry/core";
3
-
4
3
  //#region src/internal/server/edge-exporter.d.ts
5
4
  declare class FetchTraceExporter implements SpanExporter {
6
5
  private readonly url;
@@ -1,5 +1,4 @@
1
1
  import { IdGenerator } from "@opentelemetry/sdk-trace-base";
2
-
3
2
  //#region src/internal/server/id-generator.d.ts
4
3
  /**
5
4
  * IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever
@@ -1,7 +1,5 @@
1
1
  import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
2
  import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
3
- import { MetricReader } from "@opentelemetry/sdk-metrics";
4
-
5
3
  //#region src/internal/server/instrumentation-options.d.ts
6
4
  /**
7
5
  * Per-instrumentation tuning for the server-side `register()`. Mirrors
@@ -17,36 +15,15 @@ import { MetricReader } from "@opentelemetry/sdk-metrics";
17
15
  * compatible so customer code compiles unchanged when bundled for either
18
16
  * runtime.
19
17
  *
20
- * `LogRecordProcessor`, `MetricReader`, and `SpanProcessor` come from
21
- * the OTel "base" packages (`@opentelemetry/sdk-logs`,
22
- * `@opentelemetry/sdk-metrics`, `@opentelemetry/sdk-trace-base`),
18
+ * `LogRecordProcessor` and `SpanProcessor` come from the OTel "base"
19
+ * packages (`@opentelemetry/sdk-logs`, `@opentelemetry/sdk-trace-base`),
23
20
  * which are edge-safe at the import level. They're also `import type` here
24
21
  * so they're erased before bundling — the Edge runtime bundle never sees
25
22
  * them at all.
26
23
  */
27
24
  interface ServerInstrumentationOptions {
28
- /**
29
- * @internal
30
- *
31
- * Extra log-record processors fanned into the LoggerProvider's
32
- * processor list. Used by `@interfere/observability` for internal-only
33
- * dual-write to BetterStack from `interfere/homepage` +
34
- * `interfere/dashboard`. Customers don't get a fan-out hook on the
35
- * SDK surface; this is a private bridge for our own dogfood apps.
36
- */
25
+ /** @internal */
37
26
  _internalAdditionalLogRecordProcessors?: LogRecordProcessor[];
38
- /**
39
- * @internal
40
- *
41
- * Extra metric readers fanned into the MeterProvider's reader list.
42
- * See `_internalAdditionalLogRecordProcessors`. Used to keep
43
- * `http.client.request.duration` (from `UndiciInstrumentation`) and
44
- * any first-party `metrics.getMeter(...)` counters landing on the
45
- * BetterStack-fronting OTel collector after the `@vercel/otel` →
46
- * `@interfere/next` migration removed the default metric reader
47
- * `registerOTel({ metricReader: "auto" })` used to install.
48
- */
49
- _internalAdditionalMetricReaders?: MetricReader[];
50
27
  /**
51
28
  * @internal
52
29
  *
@@ -68,6 +45,11 @@ interface ServerInstrumentationOptions {
68
45
  * to run your own handler.
69
46
  */
70
47
  captureUnhandledRejection?: boolean;
48
+ /**
49
+ * OTLP payload compression. Default `"none"`. `"gzip"` shrinks each
50
+ * `/v2/sink` POST body.
51
+ */
52
+ compression?: "gzip" | "none";
71
53
  /**
72
54
  * `false` disables the `console.*` → OTel `LogRecord` bridge.
73
55
  * Defaults to enabled. Customers running their own structured
@@ -75,21 +57,6 @@ interface ServerInstrumentationOptions {
75
57
  * opt out to avoid double-emit.
76
58
  */
77
59
  consoleBridge?: boolean;
78
- /**
79
- * URL patterns the undici fetch instrumentation skips (no spans
80
- * created for these). The collector OTLP endpoint is always
81
- * skipped automatically — without this the SDK would trace its
82
- * own export requests in an infinite loop.
83
- */
84
- ignoreUrls?: (string | RegExp)[];
85
- /**
86
- * URL patterns the undici fetch instrumentation injects W3C
87
- * `traceparent` + `baggage` headers on for cross-process trace
88
- * correlation. Same-origin requests aren't a concept on the server
89
- * the way they are in a browser, so the allowlist is the only
90
- * propagation control.
91
- */
92
- propagateContextUrls?: (string | RegExp)[];
93
60
  /**
94
61
  * Override the OTel `service.name` resource attribute. Defaults to
95
62
  * `"interfere-sdk-next-server"`.
@@ -1 +1 @@
1
- import{isEnabledOnServer,readInterfereEnv}from"../env.mjs";import{withPublicKey}from"../url.mjs";import{API_PATHS}from"@interfere/constants/api";let cachedConfig=null;async function fetchAndCacheRemoteConfig(){if(!isEnabledOnServer())return;let env=readInterfereEnv();if(env.publicKey!==null)try{let url=withPublicKey(`${env.apiUrl}${API_PATHS.CONFIG}`,env.publicKey),response=await fetch(url,{method:`GET`,headers:{"content-type":`application/json`},signal:AbortSignal.timeout(1e4)});if(!response.ok)return;let config=await response.json();config?.plugins&&(cachedConfig=config.plugins)}catch{return}}function isPluginEnabled(plugin){return cachedConfig?cachedConfig[plugin]!==!1:!0}export{fetchAndCacheRemoteConfig,isPluginEnabled};
1
+ import{isEnabledOnServer,readInterfereEnv}from"../env.mjs";import{withPublicKey}from"../url.mjs";import{API_PATHS}from"@interfere/constants/api";import{readBakedPlugins}from"@interfere/types/sdk/remote-config";let cachedConfig=null;async function fetchAndCacheRemoteConfig(){if(readBakedPlugins()||!isEnabledOnServer())return;let env=readInterfereEnv();if(env.publicKey!==null)try{let url=withPublicKey(`${env.apiUrl}${API_PATHS.CONFIG}`,env.publicKey),response=await fetch(url,{method:`GET`,headers:{"content-type":`application/json`},signal:AbortSignal.timeout(1e4)});if(!response.ok)return;let config=await response.json();config?.plugins&&(cachedConfig=config.plugins)}catch{return}}function isPluginEnabled(plugin){let config=readBakedPlugins()??cachedConfig;return!config||config[plugin]!==!1}export{fetchAndCacheRemoteConfig,isPluginEnabled};
@@ -1,6 +1,5 @@
1
1
  import { NextjsContext } from "@interfere/types/sdk/plugins/context/next";
2
2
  import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
3
-
4
3
  //#region src/internal/server/types.d.ts
5
4
  type OnRequestErrorContext = Omit<NextjsContext, "errorDigest" | "requestMethod" | "requestPath" | "runtime">;
6
5
  interface CaptureErrorContext {
@@ -1,5 +1,4 @@
1
1
  import { NextMiddleware } from "next/server.js";
2
-
3
2
  //#region src/internal/server/wrap-proxy.d.ts
4
3
  /**
5
4
  * Wraps a Next.js proxy (formerly `middleware`) so thrown errors are captured
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/next`,version=`11.0.1`;export{name,version};
1
+ var name=`@interfere/next`,version=`11.0.4`;export{name,version};
@@ -1,7 +1,6 @@
1
1
  import { PluginOverrides, useInterfere, useSession } from "@interfere/react/provider";
2
2
  import { PropsWithChildren, ReactNode } from "react";
3
3
  import { ConsentCategory, ConsentState, ConsentState as ConsentState$1, GateableCategory } from "@interfere/types/sdk/plugins/manifest";
4
-
5
4
  //#region src/provider.d.ts
6
5
  interface InterfereProviderProps extends PropsWithChildren {
7
6
  consent?: ConsentState$1 | undefined;
@@ -16,11 +15,6 @@ interface InterfereProviderProps extends PropsWithChildren {
16
15
  * (`init()` is client-only); the core provider mounts with null-safe
17
16
  * accessors and switches to the real kernel on client hydration.
18
17
  */
19
- declare function InterfereProvider({
20
- children,
21
- consent,
22
- plugins,
23
- errorBoundary
24
- }: InterfereProviderProps): ReactNode;
18
+ declare function InterfereProvider({ children, consent, plugins, errorBoundary }: InterfereProviderProps): ReactNode;
25
19
  //#endregion
26
20
  export { type ConsentCategory, type ConsentState, type GateableCategory, InterfereProvider, useInterfere, useSession };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/next",
3
- "version": "11.0.1",
3
+ "version": "11.0.4",
4
4
  "license": "MIT",
5
5
  "description": "Build software that never breaks.",
6
6
  "keywords": [
@@ -93,25 +93,23 @@
93
93
  "typecheck": "tsgo --noEmit"
94
94
  },
95
95
  "dependencies": {
96
- "@interfere/constants": "^9.0.2",
97
- "@interfere/helpers": "^9.0.2",
98
- "@interfere/react": "^11.0.1",
99
- "@interfere/sdk": "^11.0.1",
100
- "@interfere/types": "^10.0.0",
96
+ "@interfere/constants": "^9.0.3",
97
+ "@interfere/helpers": "^9.0.3",
98
+ "@interfere/react": "^11.0.3",
99
+ "@interfere/sdk": "^11.0.3",
100
+ "@interfere/types": "^10.0.1",
101
101
  "@opentelemetry/api": "^1.9.1",
102
102
  "@opentelemetry/api-logs": "^0.219.0",
103
103
  "@opentelemetry/baggage-span-processor": "^0.5.0",
104
104
  "@opentelemetry/context-async-hooks": "^2.7.1",
105
105
  "@opentelemetry/core": "^2.7.0",
106
106
  "@opentelemetry/exporter-logs-otlp-http": "^0.219.0",
107
- "@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
108
107
  "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
109
108
  "@opentelemetry/instrumentation": "^0.219.0",
110
- "@opentelemetry/instrumentation-undici": "^0.29.0",
109
+ "@opentelemetry/otlp-exporter-base": "^0.219.0",
111
110
  "@opentelemetry/otlp-transformer": "^0.219.0",
112
111
  "@opentelemetry/resources": "^2.7.0",
113
112
  "@opentelemetry/sdk-logs": "^0.219.0",
114
- "@opentelemetry/sdk-metrics": "^2.7.0",
115
113
  "@opentelemetry/sdk-trace-base": "^2.7.0",
116
114
  "@opentelemetry/sdk-trace-node": "^2.7.1",
117
115
  "chalk": "^5.6.2"
@@ -133,7 +131,7 @@
133
131
  }
134
132
  },
135
133
  "devDependencies": {
136
- "@interfere/test-utils": "^9.0.2",
134
+ "@interfere/test-utils": "^9.0.4",
137
135
  "@interfere/typescript-config": "^9.0.1",
138
136
  "@types/node": "^26.1.0",
139
137
  "@types/react": "19.2.17",