@interfere/next 11.0.0-canary.6 → 11.0.0-canary.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -30
- package/dist/config.d.mts +55 -8
- package/dist/config.mjs +1 -1
- package/dist/internal/build/configure-build.d.mts +1 -1
- package/dist/internal/build/pipeline.mjs +1 -1
- package/dist/internal/build/source-maps/discover-webpack.d.mts +1 -35
- package/dist/internal/build/source-maps/discover-webpack.mjs +1 -1
- package/dist/internal/build/source-maps/discover.d.mts +2 -15
- package/dist/internal/build/source-maps/discover.mjs +1 -1
- package/dist/package.mjs +1 -1
- package/package.json +10 -15
- package/dist/internal/route/handle-get.d.mts +0 -17
- package/dist/internal/route/handle-get.mjs +0 -1
- package/dist/internal/route/handle-post.d.mts +0 -15
- package/dist/internal/route/handle-post.mjs +0 -1
- package/dist/internal/route/proxy.d.mts +0 -20
- package/dist/internal/route/proxy.mjs +0 -1
- package/dist/route-handler.d.mts +0 -12
- package/dist/route-handler.mjs +0 -1
package/README.md
CHANGED
|
@@ -61,14 +61,7 @@ export default withInterfere(config);
|
|
|
61
61
|
export { onRequestError, register } from "@interfere/next/instrumentation";
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
**3.
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
// app/api/interfere/[[...path]]/route.ts
|
|
68
|
-
export * from "@interfere/next/route-handler";
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**4. Add the provider**
|
|
64
|
+
**3. Add the provider**
|
|
72
65
|
|
|
73
66
|
```tsx
|
|
74
67
|
// app/layout.tsx
|
|
@@ -95,38 +88,24 @@ export default function RootLayout({
|
|
|
95
88
|
| --- | --- | --- |
|
|
96
89
|
| `INTERFERE_PUBLIC_KEY` | Yes | Surface publishable key in the `interfere_pub_us_*` or `interfere_pub_eu_*` format. Public by design; used for ingestion. |
|
|
97
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. |
|
|
98
|
-
| `
|
|
91
|
+
| `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
|
|
99
92
|
|
|
100
93
|
### Migrating From 0.x
|
|
101
94
|
|
|
102
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.
|
|
103
96
|
|
|
104
|
-
##
|
|
105
|
-
|
|
106
|
-
By default the SDK proxies all telemetry through `/api/interfere`. You can move this to any path by setting `NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX` and mounting the route handler at the matching location.
|
|
107
|
-
|
|
108
|
-
**Why change it?** A custom prefix lets you avoid ad-blocker deny-lists that target well-known SDK paths, or align with your own API routing conventions.
|
|
97
|
+
## Ingest Endpoint
|
|
109
98
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
```env
|
|
113
|
-
NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX=/__telemetry
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**2. Mount the route handler at the matching path**
|
|
99
|
+
By default the SDK posts telemetry to Interfere's endpoint. Ingest requests never carry cookies. Set `apiHost` to a subdomain of your own site that resolves to Interfere via a DNS record — this survives ad-blockers with no server-side component:
|
|
117
100
|
|
|
118
101
|
```ts
|
|
119
|
-
//
|
|
120
|
-
export
|
|
102
|
+
// next.config.ts
|
|
103
|
+
export default withInterfere({
|
|
104
|
+
interfere: { apiHost: "https://in.yourapp.com" },
|
|
105
|
+
});
|
|
121
106
|
```
|
|
122
107
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| Input | Resolved |
|
|
126
|
-
| --- | --- |
|
|
127
|
-
| `/__telemetry` | `/__telemetry` |
|
|
128
|
-
| `__telemetry/` | `/__telemetry` |
|
|
129
|
-
| *(empty)* or `/` | `/api/interfere` (default) |
|
|
108
|
+
Add a CNAME record pointing your subdomain at the target shown in your dashboard; Interfere provisions the TLS certificate automatically. To target a regional endpoint instead, set `apiHost` (or the `INTERFERE_API_URL` env) to it.
|
|
130
109
|
|
|
131
110
|
## Identity Management
|
|
132
111
|
|
package/dist/config.d.mts
CHANGED
|
@@ -3,14 +3,52 @@ import { NextConfig } from "next";
|
|
|
3
3
|
|
|
4
4
|
//#region src/config.d.ts
|
|
5
5
|
interface NextConfigWithInterfere extends NextConfig {
|
|
6
|
-
/**
|
|
7
|
-
* Reserved for future SDK config. Setting `buildId` here is rejected
|
|
8
|
-
* loudly — it caused server/client slug drift in 9.x. Override the
|
|
9
|
-
* commit SHA via the `INTERFERE_SOURCE_ID` env var (set on both build
|
|
10
|
-
* and runtime envs) instead.
|
|
11
|
-
*/
|
|
12
6
|
interfere?: {
|
|
7
|
+
/**
|
|
8
|
+
* Reserved. Setting `buildId` here is rejected loudly — it caused
|
|
9
|
+
* server/client slug drift in 9.x. Override the commit SHA via the
|
|
10
|
+
* `INTERFERE_SOURCE_ID` env var (set on both build and runtime envs)
|
|
11
|
+
* instead.
|
|
12
|
+
*/
|
|
13
13
|
readonly buildId?: never;
|
|
14
|
+
/**
|
|
15
|
+
* The endpoint the browser SDK posts telemetry to. Defaults to
|
|
16
|
+
* Interfere's endpoint. Set it to a subdomain of your own site that
|
|
17
|
+
* resolves to Interfere via a DNS record (e.g.
|
|
18
|
+
* `"https://in.yourapp.com"`) to survive ad-blockers, or to a
|
|
19
|
+
* regional endpoint. Must be a full URL.
|
|
20
|
+
*/
|
|
21
|
+
readonly apiHost?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Interfere-specific build settings, passed as the first argument to
|
|
26
|
+
* `withInterfere(settings, nextConfig)`. Kept separate from the Next.js
|
|
27
|
+
* config so SDK options never collide with Next's own keys.
|
|
28
|
+
*/
|
|
29
|
+
interface NextPluginConfig {
|
|
30
|
+
readonly sourceMaps?: {
|
|
31
|
+
/**
|
|
32
|
+
* Delete source maps from the build output after they've been uploaded
|
|
33
|
+
* to Interfere.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to `true`: maps are uploaded so Interfere can symbolicate
|
|
36
|
+
* stack traces, then removed from the build so your original source is
|
|
37
|
+
* never shipped to production.
|
|
38
|
+
*
|
|
39
|
+
* Set to `false` to keep the maps on disk after upload. Do this when another
|
|
40
|
+
* tool shares the same Next.js build output and runs its own source-map
|
|
41
|
+
* upload from `.next` — Interfere's default purge would delete the maps
|
|
42
|
+
* that tool needs.
|
|
43
|
+
*
|
|
44
|
+
* @default true
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Keep maps so another source-map uploader can consume them too:
|
|
48
|
+
* export default withInterfere({ sourceMaps: { purge: false } }, nextConfig);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
readonly purge?: boolean;
|
|
14
52
|
};
|
|
15
53
|
}
|
|
16
54
|
interface ResolvedBuildConfig {
|
|
@@ -33,6 +71,14 @@ interface ResolvedBuildConfig {
|
|
|
33
71
|
*/
|
|
34
72
|
readonly environment: string | null;
|
|
35
73
|
readonly publicKey: string | null;
|
|
74
|
+
/**
|
|
75
|
+
* When `true` (default), the build pipeline deletes source maps from the
|
|
76
|
+
* build output after uploading them, so they never ship to production.
|
|
77
|
+
* Resolved from `NextPluginConfig.sourceMaps.purge`; set it `false` to
|
|
78
|
+
* leave maps on disk for another uploader that shares the same `.next`
|
|
79
|
+
* output.
|
|
80
|
+
*/
|
|
81
|
+
readonly purgeSourceMaps: boolean;
|
|
36
82
|
/**
|
|
37
83
|
* Deterministically derived from the leading 16 hex of the commit SHA, so
|
|
38
84
|
* the build step and the collector arrive at the same
|
|
@@ -42,10 +88,11 @@ interface ResolvedBuildConfig {
|
|
|
42
88
|
*/
|
|
43
89
|
readonly releaseSlug: ReleaseSlug | null;
|
|
44
90
|
}
|
|
45
|
-
declare function withInterfere(
|
|
91
|
+
declare function withInterfere(upstream?: NextConfigWithInterfere): NextConfig;
|
|
92
|
+
declare function withInterfere(settings: NextPluginConfig, config: NextConfigWithInterfere): NextConfig;
|
|
46
93
|
interface ProductionCompileContext {
|
|
47
94
|
readonly distDir: string;
|
|
48
95
|
readonly projectDir: string;
|
|
49
96
|
}
|
|
50
97
|
//#endregion
|
|
51
|
-
export { NextConfigWithInterfere, ProductionCompileContext, ResolvedBuildConfig, withInterfere };
|
|
98
|
+
export { NextConfigWithInterfere, NextPluginConfig, ProductionCompileContext, ResolvedBuildConfig, withInterfere };
|
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{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(
|
|
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};
|
|
@@ -2,9 +2,9 @@ import { NextConfig } from "next";
|
|
|
2
2
|
|
|
3
3
|
//#region src/internal/build/configure-build.d.ts
|
|
4
4
|
interface InterfereInjectedValues {
|
|
5
|
+
readonly __INTERFERE_API_URL__?: string;
|
|
5
6
|
readonly __INTERFERE_ENVIRONMENT__?: string;
|
|
6
7
|
readonly __INTERFERE_FORCE_ENABLE__?: boolean;
|
|
7
|
-
readonly __INTERFERE_PROXY_MODE__?: boolean;
|
|
8
8
|
readonly __INTERFERE_PUBLIC_KEY__?: string;
|
|
9
9
|
readonly __INTERFERE_RELEASE_SLUG__: string | null;
|
|
10
10
|
}
|
|
@@ -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(()=>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{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,41 +1,7 @@
|
|
|
1
1
|
import { SourceMapFile } from "./discover.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/internal/build/source-maps/discover-webpack.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Inserts a `//# debugId=…` comment into the chunk. Idempotent: if a
|
|
6
|
-
* spec-compliant comment is already present (e.g. webpack 5.104+ with
|
|
7
|
-
* `SourceMapDevToolPlugin({ debugIds: true })`, turbopack with
|
|
8
|
-
* `experimental.turbopack.debugIds`, or any other tool) it's
|
|
9
|
-
* overwritten with the supplied debugId — callers that read the
|
|
10
|
-
* existing id via `readDebugIdFromJs` first get an idempotent no-op.
|
|
11
|
-
*
|
|
12
|
-
* Otherwise the comment is appended just before the existing
|
|
13
|
-
* `//# sourceMappingURL=…` line so the browser still resolves the map.
|
|
14
|
-
*/
|
|
15
|
-
declare function injectDebugIdIntoJs(content: string, debugId: string): string;
|
|
16
|
-
/**
|
|
17
|
-
* Adds `debugId` to the source-map JSON. Idempotent: writes both
|
|
18
|
-
* `debugId` (TC39 spec field) and `debug_id` (legacy variant some
|
|
19
|
-
* older Sentry tooling emits) so manifests stay readable across the
|
|
20
|
-
* ecosystem. Re-running with the same id produces identical output.
|
|
21
|
-
*/
|
|
22
4
|
declare function injectDebugIdIntoMap(content: string, debugId: string): string;
|
|
23
|
-
/**
|
|
24
|
-
* Webpack-mode discovery: walks the dist tree, parses each `.js`
|
|
25
|
-
* chunk's `//# sourceMappingURL=…` comment to find its paired `.map`,
|
|
26
|
-
* and ensures both carry the same `debugId`.
|
|
27
|
-
*
|
|
28
|
-
* Idempotent w.r.t. debug-id state on disk:
|
|
29
|
-
* - If the chunk already carries a `//# debugId=…` comment (webpack
|
|
30
|
-
* 5.104+ with `SourceMapDevToolPlugin({ debugIds: true })`, or a
|
|
31
|
-
* third-party tool that already injected one) we reuse it.
|
|
32
|
-
* - Otherwise we generate a fresh UUID and inject it into both files.
|
|
33
|
-
*
|
|
34
|
-
* The "reuse if present" branch matches the strategy in Sentry's
|
|
35
|
-
* webpack plugin (`addDebugIdToBundleSource` in
|
|
36
|
-
* `bundler-plugin-core/src/debug-id-upload.ts`) and means our
|
|
37
|
-
* discovery works transparently when bundlers natively emit debugIds.
|
|
38
|
-
*/
|
|
39
5
|
declare function discoverWebpack(opts: {
|
|
40
6
|
projectDir: string;
|
|
41
7
|
distDir: string;
|
|
@@ -44,4 +10,4 @@ declare function discoverWebpack(opts: {
|
|
|
44
10
|
sourceFileCount: number;
|
|
45
11
|
}>;
|
|
46
12
|
//#endregion
|
|
47
|
-
export { discoverWebpack,
|
|
13
|
+
export { discoverWebpack, injectDebugIdIntoMap };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{readFile,writeFile}from"node:fs/promises";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(\S+)\s
|
|
1
|
+
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{readFile,writeFile}from"node:fs/promises";import{injectDebugIdIntoJs,readDebugIdFromJs}from"@interfere/types/data/source-maps";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;function injectDebugIdIntoMap(content,debugId){let json=JSON.parse(content);return json.debugId=debugId,JSON.stringify(json)}async function discoverWebpack(opts){let absDistDir=resolveDistDir(opts.projectDir,opts.distDir),relDistDir=relative(opts.projectDir,absDistDir),jsPaths=await walkDistTrees(absDistDir,`.js`),pairs=new Map;for(let jsAbs of jsPaths){let content=await readFile(jsAbs,`utf8`),match=SOURCEMAPPING_RE.exec(content);if(!match?.[1])continue;let mapAbs=`${jsAbs.slice(0,jsAbs.lastIndexOf(`/`)+1)}${decodeURIComponent(match[1])}`,jsPublic=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir),mapPublic=toPublicPath(relative(opts.projectDir,mapAbs),relDistDir);pairs.set(mapPublic,{mapAbs,jsAbs,jsPublic,jsContent:content,existingDebugId:readDebugIdFromJs(content)})}return{files:(await Promise.all(Array.from(pairs.entries(),async([mapPublic,pair])=>{let mapContent=await readFile(pair.mapAbs,`utf8`).catch(()=>null);if(mapContent===null)return null;let debugId=pair.existingDebugId??randomUUID();if(!pair.existingDebugId){let injectedJs=injectDebugIdIntoJs(pair.jsContent,debugId);await writeFile(pair.jsAbs,injectedJs,`utf8`)}let finalMap=JSON.parse(mapContent).debugId===debugId?mapContent:injectDebugIdIntoMap(mapContent,debugId);return finalMap!==mapContent&&await writeFile(pair.mapAbs,finalMap,`utf8`),{absolute:pair.mapAbs,path:mapPublic,content:finalMap,hash:createHash(`sha256`).update(finalMap).digest(`hex`),debugId,chunkUrl:pair.jsPublic}}))).filter(f=>f!==null),sourceFileCount:pairs.size}}export{discoverWebpack,injectDebugIdIntoMap};
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { NextBuildBundler } from "../detect-bundler.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { injectDebugIdIntoMap } from "./discover-webpack.mjs";
|
|
3
|
+
import { injectDebugIdIntoJs } from "@interfere/types/data/source-maps";
|
|
3
4
|
|
|
4
5
|
//#region src/internal/build/source-maps/discover.d.ts
|
|
5
6
|
interface SourceMapFile {
|
|
6
7
|
readonly absolute: string;
|
|
7
|
-
/**
|
|
8
|
-
* Public URL the runtime will load this chunk from. ALWAYS populated
|
|
9
|
-
* for both bundlers — turbopack discovery walks JS chunks (in addition
|
|
10
|
-
* to .map files) so it can record this. Lifted onto the manifest as
|
|
11
|
-
* `chunkUrl`; server-side suffix-matching pairs `frame.fileName` to
|
|
12
|
-
* this field by content-hashed filename.
|
|
13
|
-
*/
|
|
14
8
|
readonly chunkUrl: string;
|
|
15
9
|
readonly content: string;
|
|
16
10
|
readonly debugId: string;
|
|
@@ -21,13 +15,6 @@ interface DiscoveryResult {
|
|
|
21
15
|
readonly files: SourceMapFile[];
|
|
22
16
|
readonly sourceFileCount: number;
|
|
23
17
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Single entry point for source-map discovery. The pipeline calls this
|
|
26
|
-
* with the active bundler from `detectActiveBundler()`; new bundlers (e.g.
|
|
27
|
-
* rspack) drop in here as additional cases without touching the pipeline.
|
|
28
|
-
* Other manifest bundlers (for Vite, CLI, etc.) live outside the Next
|
|
29
|
-
* build pipeline and should use their own discovery implementation.
|
|
30
|
-
*/
|
|
31
18
|
declare function discoverSourceMaps(opts: {
|
|
32
19
|
bundler: NextBuildBundler;
|
|
33
20
|
projectDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeDistDir}from"./paths.mjs";import{discoverTurbopack}from"./discover-turbopack.mjs";import{discoverWebpack,
|
|
1
|
+
import{normalizeDistDir}from"./paths.mjs";import{discoverTurbopack}from"./discover-turbopack.mjs";import{discoverWebpack,injectDebugIdIntoMap}from"./discover-webpack.mjs";import{injectDebugIdIntoJs}from"@interfere/types/data/source-maps";async function discoverSourceMaps(opts){let distDir=normalizeDistDir(opts.distDir);switch(opts.bundler){case`webpack`:return discoverWebpack({projectDir:opts.projectDir,distDir});case`turbopack`:return discoverTurbopack({projectDir:opts.projectDir,distDir})}}export{discoverSourceMaps,injectDebugIdIntoJs,injectDebugIdIntoMap};
|
package/dist/package.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var name=`@interfere/next`,version=`11.0.0-canary.
|
|
1
|
+
var name=`@interfere/next`,version=`11.0.0-canary.7`;export{name,version};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/next",
|
|
3
|
-
"version": "11.0.0-canary.
|
|
3
|
+
"version": "11.0.0-canary.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Build software that never breaks.",
|
|
6
6
|
"keywords": [
|
|
@@ -77,11 +77,6 @@
|
|
|
77
77
|
"types": "./dist/provider.d.mts",
|
|
78
78
|
"default": "./dist/provider.mjs"
|
|
79
79
|
},
|
|
80
|
-
"./route-handler": {
|
|
81
|
-
"monorepo": "./src/route-handler.ts",
|
|
82
|
-
"types": "./dist/route-handler.d.mts",
|
|
83
|
-
"default": "./dist/route-handler.mjs"
|
|
84
|
-
},
|
|
85
80
|
"./server": {
|
|
86
81
|
"monorepo": "./src/server.ts",
|
|
87
82
|
"types": "./dist/server.d.mts",
|
|
@@ -98,11 +93,11 @@
|
|
|
98
93
|
"typecheck": "tsgo --noEmit"
|
|
99
94
|
},
|
|
100
95
|
"dependencies": {
|
|
101
|
-
"@interfere/constants": "^9.0.2-canary.
|
|
102
|
-
"@interfere/helpers": "^9.0.1-canary.
|
|
103
|
-
"@interfere/react": "^11.0.0-canary.
|
|
104
|
-
"@interfere/sdk": "^11.0.0-canary.
|
|
105
|
-
"@interfere/types": "^10.0.0-canary.
|
|
96
|
+
"@interfere/constants": "^9.0.2-canary.3",
|
|
97
|
+
"@interfere/helpers": "^9.0.1-canary.2",
|
|
98
|
+
"@interfere/react": "^11.0.0-canary.7",
|
|
99
|
+
"@interfere/sdk": "^11.0.0-canary.7",
|
|
100
|
+
"@interfere/types": "^10.0.0-canary.6",
|
|
106
101
|
"@opentelemetry/api": "^1.9.1",
|
|
107
102
|
"@opentelemetry/api-logs": "^0.219.0",
|
|
108
103
|
"@opentelemetry/baggage-span-processor": "^0.5.0",
|
|
@@ -138,12 +133,12 @@
|
|
|
138
133
|
}
|
|
139
134
|
},
|
|
140
135
|
"devDependencies": {
|
|
141
|
-
"@interfere/test-utils": "^9.0.1-canary.
|
|
142
|
-
"@interfere/typescript-config": "^9.0.1-canary.
|
|
143
|
-
"@types/node": "^26.
|
|
136
|
+
"@interfere/test-utils": "^9.0.1-canary.1",
|
|
137
|
+
"@interfere/typescript-config": "^9.0.1-canary.1",
|
|
138
|
+
"@types/node": "^26.1.0",
|
|
144
139
|
"@types/react": "19.2.17",
|
|
145
140
|
"@types/react-dom": "19.2.3",
|
|
146
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
141
|
+
"@typescript/native-preview": "7.0.0-dev.20260701.1",
|
|
147
142
|
"@vitest/browser": "4.1.9",
|
|
148
143
|
"@vitest/browser-playwright": "4.1.9",
|
|
149
144
|
"@vitest/coverage-v8": "4.1.9",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
//#region src/internal/route/handle-get.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Generic GET proxy. SDK 10.x routes `GET /v2/config` (and any future
|
|
4
|
-
* GET-based endpoints) through here; SDK 9.x clients still hit
|
|
5
|
-
* `GET /v1/config` and get the same delegated response on the collector
|
|
6
|
-
* side.
|
|
7
|
-
*
|
|
8
|
-
* `/sw` is special-cased to serve the SDK's bundled service-worker
|
|
9
|
-
* script directly out of the customer's Next.js process. `SW_SCRIPT`
|
|
10
|
-
* is a string export emitted at @interfere/react build time
|
|
11
|
-
* (`scripts/build-sw.ts`); on Node runtime the customer resolves it
|
|
12
|
-
* via `node_modules`, on Edge runtime the customer's Next.js bundler
|
|
13
|
-
* inlines it into the Edge worker. Either way, no `fs.readFileSync`.
|
|
14
|
-
*/
|
|
15
|
-
declare function handleGet(request: Request): Promise<Response>;
|
|
16
|
-
//#endregion
|
|
17
|
-
export { handleGet };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{log}from"../logger.mjs";import{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv}from"./proxy.mjs";import{SW_SCRIPT}from"@interfere/react/sw";const SW_HEADERS={"content-type":`application/javascript; charset=utf-8`,"service-worker-allowed":`/`,"cache-control":`public, max-age=3600`};async function handleGet(request){let subPath=extractSubPath(request);if(subPath===`/sw`)return new Response(SW_SCRIPT,{status:200,headers:SW_HEADERS});let env=resolveAuthenticatedEnv();if(!hasPublicKeyCredential(request,env.publicKey))return notConfiguredResponse();try{return await forwardToCollector(request,env,subPath)}catch(error){let detail=formatProxyError(error);return log.error(`Proxy ${request.method} ${subPath} failed`,detail.lines),Response.json({code:`INTERFERE_PROXY_ERROR`,message:detail.message},{status:502})}}export{handleGet};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//#region src/internal/route/handle-post.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Generic POST proxy. SDK 10.x routes everything through here:
|
|
4
|
-
*
|
|
5
|
-
* - `/v2/sink` (OTLP traces / metrics / logs)
|
|
6
|
-
* - `/v2/replay/upload/:sessionId`
|
|
7
|
-
* - `/v1/session*` (session sync + identify)
|
|
8
|
-
*
|
|
9
|
-
* No envelope-aware special-casing — that endpoint (`/v1/ingest`) was
|
|
10
|
-
* 9.x-only and is not produced by 10.x. Customers on 9.x post directly
|
|
11
|
-
* to the collector through the same proxy without touching this file.
|
|
12
|
-
*/
|
|
13
|
-
declare function handlePost(request: Request): Promise<Response>;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { handlePost };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{log}from"../logger.mjs";import{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv}from"./proxy.mjs";async function handlePost(request){let env=resolveAuthenticatedEnv();if(!hasPublicKeyCredential(request,env.publicKey))return notConfiguredResponse();let subPath=extractSubPath(request);try{return await forwardToCollector(request,env,subPath)}catch(error){let detail=formatProxyError(error);return log.error(`Proxy ${request.method} ${subPath} failed`,detail.lines),Response.json({code:`INTERFERE_PROXY_ERROR`,message:detail.message},{status:502})}}export{handlePost};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { InterfereEnv } from "../env.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/internal/route/proxy.d.ts
|
|
4
|
-
interface AuthenticatedEnv {
|
|
5
|
-
apiUrl: string;
|
|
6
|
-
publicKey: string | null;
|
|
7
|
-
release: InterfereEnv["release"];
|
|
8
|
-
}
|
|
9
|
-
declare function resolveAuthenticatedEnv(): AuthenticatedEnv;
|
|
10
|
-
/** True when the proxy can attach a publishable key from env or the request. */
|
|
11
|
-
declare function hasPublicKeyCredential(request: Request, envPublicKey: string | null): boolean;
|
|
12
|
-
declare function notConfiguredResponse(): Response;
|
|
13
|
-
declare function extractSubPath(request: Request): string;
|
|
14
|
-
declare function formatProxyError(error: unknown): {
|
|
15
|
-
message: string;
|
|
16
|
-
lines: string[];
|
|
17
|
-
};
|
|
18
|
-
declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
|
|
19
|
-
//#endregion
|
|
20
|
-
export { AuthenticatedEnv, extractSubPath, formatProxyError, forwardToCollector, hasPublicKeyCredential, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{readInterfereEnv}from"../env.mjs";import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";import{extractCountryHeader}from"@interfere/types/sdk/geo";const STRIPPED_AUTH_HEADERS=[`authorization`,`x-api-key`,`x-interfere-api-key`],STRIPPED_TRANSPORT_HEADERS=[`host`,`content-length`,`connection`,`keep-alive`,`transfer-encoding`,`te`,`trailer`,`upgrade`,`proxy-authenticate`,`proxy-authorization`];function forwardedHeaders(request){let headers=new Headers(request.headers);for(let name of STRIPPED_AUTH_HEADERS)headers.delete(name);for(let name of STRIPPED_TRANSPORT_HEADERS)headers.delete(name);return headers}function resolveAuthenticatedEnv(){let env=readInterfereEnv();return{apiUrl:env.apiUrl,publicKey:env.publicKey,release:env.release}}function hasPublicKeyCredential(request,envPublicKey){return envPublicKey?!0:new URL(request.url).searchParams.has(`pk`)}function notConfiguredResponse(){return log.warn(`Not configured`,[`INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.`]),Response.json({code:`INTERFERE_NOT_CONFIGURED`,message:`INTERFERE_PUBLIC_KEY is required.`},{status:503})}function extractSubPath(request){let prefix=resolveRoutePrefix(),url=new URL(request.url),idx=url.pathname.indexOf(prefix);if(idx===-1)return`/`;let remainder=url.pathname.slice(idx+prefix.length);return remainder===``?`/`:remainder.startsWith(`/`)?remainder:`/${remainder}`}function formatProxyError(error){if(!(error instanceof Error))return{message:String(error),lines:[String(error)]};let lines=[`${error.name}: ${error.message}`];if(`cause`in error&&error.cause){let cause=error.cause instanceof Error?error.cause.message:String(error.cause);lines.push(`cause: ${cause}`)}return`code`in error&&typeof error.code==`string`&&lines.push(`code: ${error.code}`),{message:`${error.name}: ${error.message}`,lines}}function buildUpstreamUrl(apiUrl,subPath,request,publicKey){let incoming=new URL(request.url),upstream=new URL(`${apiUrl}${subPath}`);for(let[key,value]of incoming.searchParams)upstream.searchParams.append(key,value);return publicKey&&!upstream.searchParams.has(`pk`)&&upstream.searchParams.set(`pk`,publicKey),upstream.toString()}async function forwardToCollector(request,env,subPath){let url=buildUpstreamUrl(env.apiUrl,subPath,request,env.publicKey),body=request.method!==`GET`&&request.method!==`HEAD`?new Uint8Array(await request.arrayBuffer()):void 0,headers=forwardedHeaders(request);headers.has(`content-type`)||headers.set(`content-type`,`application/json`);let country=extractCountryHeader(request);country!==null&&headers.set(`x-country-code`,country);let upstream=await fetch(url,{...omitUndefined({body}),method:request.method,headers,signal:AbortSignal.timeout(1e4)});if(!upstream.ok){let text=await upstream.text().catch(()=>``);return upstream.status>=500?(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status})):upstream.status>=400?(log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_WARNING`,message:text},{status:upstream.status})):(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status}))}return new Response(upstream.body,{status:upstream.status,headers:{"content-type":upstream.headers.get(`content-type`)??`application/json`}})}export{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv};
|
package/dist/route-handler.d.mts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
//#region src/route-handler.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Customers consume this module via `export * from "@interfere/next/route-handler"`
|
|
4
|
-
* so any HTTP method we add later (PATCH, DELETE, …) is picked up
|
|
5
|
-
* without a customer file change. Do NOT add non-method exports here —
|
|
6
|
-
* `route-handler.lint.test.ts` will fail the build if you do.
|
|
7
|
-
*/
|
|
8
|
-
declare function GET(request: Request): Promise<Response>;
|
|
9
|
-
declare function POST(request: Request): Promise<Response>;
|
|
10
|
-
declare function OPTIONS(): Response;
|
|
11
|
-
//#endregion
|
|
12
|
-
export { GET, OPTIONS, POST };
|
package/dist/route-handler.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isEnabledOnServer}from"./internal/env.mjs";import{handleGet}from"./internal/route/handle-get.mjs";import{handlePost}from"./internal/route/handle-post.mjs";const DISABLED=()=>new Response(null,{status:204});async function GET(request){return isEnabledOnServer()?await handleGet(request):DISABLED()}function POST(request){return isEnabledOnServer()?handlePost(request):Promise.resolve(DISABLED())}const CORS_HEADERS={"Access-Control-Allow-Origin":`*`,"Access-Control-Allow-Methods":`GET, POST, OPTIONS`,"Access-Control-Allow-Headers":`Content-Type`,"Access-Control-Max-Age":`86400`};function OPTIONS(){return new Response(null,{status:200,headers:CORS_HEADERS})}export{GET,OPTIONS,POST};
|