@interfere/next 12.0.5 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -6
- package/dist/config.d.mts +7 -21
- package/dist/config.mjs +1 -1
- package/dist/instrument-client.mjs +1 -1
- package/dist/instrumentation.edge.mjs +1 -1
- package/dist/instrumentation.mjs +1 -1
- package/dist/internal/build/configure-build.d.mts +1 -0
- package/dist/internal/build/pipeline.d.mts +9 -0
- package/dist/internal/build/pipeline.mjs +1 -1
- package/dist/internal/build/release/index.d.mts +60 -46
- package/dist/internal/build/release/index.mjs +1 -1
- package/dist/internal/build/source-maps/discover-turbopack.d.mts +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs +1 -1
- package/dist/internal/build/source-maps/discover-webpack.d.mts +1 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs +1 -1
- package/dist/internal/build/source-maps/discover.d.mts +2 -0
- package/dist/internal/build/source-maps/guard.d.mts +1 -1
- package/dist/internal/build/source-maps/guard.mjs +1 -1
- package/dist/internal/build/value-injection-loader.mjs +1 -1
- package/dist/internal/build-marker-guard.d.mts +1 -0
- package/dist/internal/build-marker-guard.mjs +1 -1
- package/dist/internal/console-output.d.mts +3 -0
- package/dist/internal/console-output.mjs +1 -0
- package/dist/internal/env.mjs +1 -1
- package/dist/internal/logger.mjs +1 -1
- package/dist/internal/server/counter-id-generator.d.mts +12 -0
- package/dist/internal/server/counter-id-generator.mjs +1 -0
- package/dist/internal/server/edge-exporter.mjs +1 -1
- package/dist/internal/server/id-generator.d.mts +2 -28
- package/dist/internal/server/id-generator.mjs +1 -1
- package/dist/internal/server/instrumentation-options.d.mts +12 -0
- package/dist/internal/server/trace-payload.d.mts +5 -0
- package/dist/internal/server/trace-payload.mjs +1 -0
- package/dist/package.mjs +1 -1
- package/dist/provider.d.mts +4 -1
- package/dist/provider.mjs +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -82,14 +82,54 @@ export default function RootLayout({
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
`InterfereProvider` captures render errors by default and rethrows them so your host error boundary keeps control of the fallback. Set `captureRenderErrors={false}` to disable this capture. The deprecated `errorBoundary` prop remains supported; `captureRenderErrors` takes precedence when both are set.
|
|
86
|
+
|
|
85
87
|
### Environment Variables
|
|
86
88
|
|
|
87
|
-
| Variable | Required | Description
|
|
88
|
-
| ---------------------------------------- | ------------------------ |
|
|
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
|
-
| `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
|
-
| `INTERFERE_SOURCEMAP_UPLOAD_CONCURRENCY` | No | Maximum concurrent source-map uploads. Defaults to 20; must be a positive integer.
|
|
92
|
-
| `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint.
|
|
89
|
+
| Variable | Required | Description |
|
|
90
|
+
| ---------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Public by design; used for ingestion. A build without it logs a warning and sends no events. |
|
|
92
|
+
| `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. A build without it skips the release. |
|
|
93
|
+
| `INTERFERE_SOURCEMAP_UPLOAD_CONCURRENCY` | No | Maximum concurrent source-map uploads. Defaults to 20; must be a positive integer. |
|
|
94
|
+
| `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
|
|
95
|
+
|
|
96
|
+
### Connect releases to Vercel
|
|
97
|
+
|
|
98
|
+
In Interfere, connect Vercel and link this Surface to its Vercel project. Also link the Surface to its source repository. These links let the build register releases and upload source maps; installing the SDK does not select a project for you.
|
|
99
|
+
|
|
100
|
+
An incomplete Surface setup produces a build warning and skips release registration and source-map upload. It does not fail the application build or disable SDK monitoring. Source-map cleanup still follows your `purge` setting. Complete the links and redeploy to enable release attribution and source-mapped stack traces.
|
|
101
|
+
|
|
102
|
+
For Vercel deployments, choose the Vercel project rather than CLI as the deployment target. CLI registers a generic deployment and does not provide the Vercel project mapping used for deployment events and drains.
|
|
103
|
+
|
|
104
|
+
### Source-map uploads and cleanup
|
|
105
|
+
|
|
106
|
+
Source-map uploads and cleanup are enabled by default. To upload only in production, select the deployment environment explicitly:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { withInterfere } from "@interfere/next/config";
|
|
110
|
+
|
|
111
|
+
export default withInterfere(
|
|
112
|
+
{
|
|
113
|
+
sourceMaps: {
|
|
114
|
+
upload: process.env.INTERFERE_ENVIRONMENT === "production",
|
|
115
|
+
purge: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
nextConfig,
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Set `INTERFERE_ENVIRONMENT` to the deployment environment, such as `staging` or `production`. `NODE_ENV` is usually `production` for both staging and production builds, so it cannot distinguish them.
|
|
123
|
+
|
|
124
|
+
With `upload: false`, the SDK still registers the release and retains browser/server monitoring. It skips source-map signing, upload, and finalization requests. Stack traces cannot be resolved to original source in environments without uploaded maps. Release registration still requires the build API key.
|
|
125
|
+
|
|
126
|
+
`purge` controls cleanup independently. When true, the SDK deletes discovered maps and removes their source-map references after the pipeline, including upload failures or intentionally disabled uploads. Cleanup failures are reported separately without replacing the original pipeline error. Set `purge: false` to retain maps for another uploader. Setting both options to false also skips source-map discovery in the build hook.
|
|
127
|
+
|
|
128
|
+
A production build registers a release when `INTERFERE_API_KEY` is set, so set it on builds that you deploy. Without it, the build logs a warning and skips the release. `upload: false` skips only the source maps; it does not skip the release.
|
|
129
|
+
|
|
130
|
+
To turn Interfere off for a build, pass `withInterfere({ disable: true }, nextConfig)`. The build skips the release, the source-map upload and every Interfere check, and the browser and server SDKs send no events.
|
|
131
|
+
|
|
132
|
+
Only configuration that is wrong fails a production build: an `INTERFERE_API_KEY` with the wrong prefix, a missing commit SHA when the key is set, an invalid `apiHost`, or a public key without an `instrumentation-client` file. A missing `INTERFERE_PUBLIC_KEY` logs a warning. Errors from the Interfere API, including a rejected secret key, are logged and do not fail the build.
|
|
93
133
|
|
|
94
134
|
### Migrating From 0.x
|
|
95
135
|
|
|
@@ -292,6 +332,69 @@ await init({ consent: false });
|
|
|
292
332
|
|
|
293
333
|
The provider's `consent` prop will then keep it in sync as the user updates their preferences.
|
|
294
334
|
|
|
335
|
+
## Privacy
|
|
336
|
+
|
|
337
|
+
### Private elements
|
|
338
|
+
|
|
339
|
+
Mark elements whose content the SDK never records in browser `init()`. The selectors apply to session replay and to click, submit and rage-click records from the first capture, even when replay is disabled:
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// instrumentation-client.ts
|
|
343
|
+
import { init } from "@interfere/next/instrument-client";
|
|
344
|
+
|
|
345
|
+
await init({
|
|
346
|
+
privacy: { blockSelector: "[data-sensitive]" },
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The `interfere-block` and `interfere-mask` classes always apply. Interaction records keep the event, tag and field label of a private target, never its text or value. `replay.blockSelector` and `replay.maskTextSelector` on the provider apply to replay only. `<InterfereProvider plugins={{ pageEvents: { interactions: false } }}>` drops click and submit spans but keeps navigation, document-load and resource timing. The [`@interfere/react` README](https://www.npmjs.com/package/@interfere/react#privacy) lists every recorded field.
|
|
351
|
+
|
|
352
|
+
### URL redaction
|
|
353
|
+
|
|
354
|
+
The browser SDK and the server kit always redact URL credentials, and redact the values of built-in sensitive query parameters (such as `token`, `password`, `sig` and `X-Amz-Signature`) by default. Parameter names stay, and redacted values become `REDACTED`. Use `privacy.urls` in browser `init()` and in the server kit or `register()` (Node and Edge) to change this:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
// instrumentation-client.ts
|
|
358
|
+
import { init } from "@interfere/next/instrument-client";
|
|
359
|
+
|
|
360
|
+
await init({ privacy: { urls: { allow: ["page", "sort"] } } });
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
// instrumentation.ts
|
|
365
|
+
import { buildInterfereOtelKit } from "@interfere/next/instrumentation";
|
|
366
|
+
|
|
367
|
+
const kit = buildInterfereOtelKit({ privacy: { urls: { deny: ["email"] } } });
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`{ allow }` redacts every value except the listed names. `{ deny }` adds names to the built-in list. Both keep fragments unless you pass `fragment: true`. `true` redacts every value and removes fragments. `false` keeps query values. Requests, routing and trace propagation keep the original URL. The [`@interfere/react` README](https://www.npmjs.com/package/@interfere/react#url-redaction) lists the built-in names and which records are covered.
|
|
371
|
+
|
|
372
|
+
On the server, the kit's processor redacts each span while it ends. Every processor and exporter on the same provider then receives the redacted span, including Vercel's `"auto"` drain processors and any second exporter. Because sensitive-name redaction is on by default, those exporters now receive `REDACTED` for the built-in names too; pass `privacy: { urls: false }` to keep query values. Providers built on `@opentelemetry/sdk-trace-base` older than 2.3 lack the `onEnding` hook; there the SDK redacts attributes and exception events in `onEnd`, which only reaches processors listed after the kit's and leaves span names and status messages unchanged.
|
|
373
|
+
|
|
374
|
+
## Browser span processors
|
|
375
|
+
|
|
376
|
+
Pass standard OpenTelemetry processors to browser `init()` in `instrumentation-client.ts`:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { init } from "@interfere/next/instrument-client";
|
|
380
|
+
import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
381
|
+
|
|
382
|
+
const dropTenant: SpanProcessor = {
|
|
383
|
+
onStart() {},
|
|
384
|
+
onEnd(span) {
|
|
385
|
+
delete span.attributes["app.tenant_name"];
|
|
386
|
+
},
|
|
387
|
+
async forceFlush() {},
|
|
388
|
+
async shutdown() {},
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
await init({ tracing: { spanProcessors: [dropTenant] } });
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
These processors run in array order after built-in session and device enrichment and before internal wrapper processors and the Interfere batch exporter. Instrumentation and export remain enabled. Complete mutations to `span.attributes` synchronously in `onEnd`; asynchronous callbacks are not awaited, and `setAttribute` cannot change ended spans. Use `privacy.urls` for query values and fragments.
|
|
395
|
+
|
|
396
|
+
The SDK owns processor flushing and shutdown. Give each SDK initialization fresh processor instances, and do not share them with another provider. `tracing: false` skips browser tracing setup. These options do not configure server tracing or `withInterfere()`.
|
|
397
|
+
|
|
295
398
|
## What's Included
|
|
296
399
|
|
|
297
400
|
- **Error tracking** — automatic capture of server and client errors with rich stack traces
|
package/dist/config.d.mts
CHANGED
|
@@ -27,28 +27,13 @@ export interface NextConfigWithInterfere extends NextConfig {
|
|
|
27
27
|
*/
|
|
28
28
|
export interface NextPluginConfig {
|
|
29
29
|
readonly sourceMaps?: {
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* Defaults to `true`: maps are uploaded so Interfere can symbolicate
|
|
35
|
-
* stack traces, then removed from the build so your original source is
|
|
36
|
-
* never shipped to production.
|
|
37
|
-
*
|
|
38
|
-
* Set to `false` to keep the maps on disk after upload. Do this when another
|
|
39
|
-
* tool shares the same Next.js build output and runs its own source-map
|
|
40
|
-
* upload from `.next` — Interfere's default purge would delete the maps
|
|
41
|
-
* that tool needs.
|
|
42
|
-
*
|
|
43
|
-
* @default true
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* // Keep maps so another source-map uploader can consume them too:
|
|
47
|
-
* export default withInterfere({ sourceMaps: { purge: false } }, nextConfig);
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
30
|
+
/** Upload source maps to Interfere. Defaults to true. Release registration and telemetry stay enabled when false. */
|
|
31
|
+
readonly upload?: boolean;
|
|
32
|
+
/** Delete discovered source maps and their references after the build hook, including upload failures or disabled uploads. Defaults to true. */
|
|
50
33
|
readonly purge?: boolean;
|
|
51
34
|
};
|
|
35
|
+
/** Turns Interfere off for this build: no release, no source-map upload, and the browser and server SDKs send no events. */
|
|
36
|
+
readonly disable?: boolean;
|
|
52
37
|
}
|
|
53
38
|
export interface ResolvedBuildConfig {
|
|
54
39
|
readonly apiKey: string | null;
|
|
@@ -72,12 +57,13 @@ export interface ResolvedBuildConfig {
|
|
|
72
57
|
readonly publicKey: string | null;
|
|
73
58
|
/**
|
|
74
59
|
* When `true` (default), the build pipeline deletes source maps from the
|
|
75
|
-
* build output
|
|
60
|
+
* build output whether uploading succeeds, fails, or is disabled.
|
|
76
61
|
* Resolved from `NextPluginConfig.sourceMaps.purge`; set it `false` to
|
|
77
62
|
* leave maps on disk for another uploader that shares the same `.next`
|
|
78
63
|
* output.
|
|
79
64
|
*/
|
|
80
65
|
readonly purgeSourceMaps: boolean;
|
|
66
|
+
readonly uploadSourceMaps: boolean;
|
|
81
67
|
/**
|
|
82
68
|
* Deterministically derived from the leading 16 hex of the commit SHA, so
|
|
83
69
|
* the build step and the collector arrive at the same
|
package/dist/config.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{configureBuild}from"./internal/build/configure-build.mjs";import{runGitCommand}from"./internal/build/release/git.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{
|
|
1
|
+
import{configureBuild}from"./internal/build/configure-build.mjs";import{runGitCommand}from"./internal/build/release/git.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{checkReleaseConfig,diagnoseRelease}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{API_URL_GLOBAL,BUILD_MARKER_GLOBAL,DISABLED_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`,`tracestate`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`,docsUrl:`https://interfere.com/docs/sdk/next-js`};function isThenable(value){return typeof value==`object`&&!!value&&`then`in value&&typeof value.then==`function`}function withInterfere(...args){let[settings,nextConfig]=args.length===2?[args[0],args[1]]:[{},args[0]??{}];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 interfereInjectedValues(config,apiHost,forceEnable){let releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug;return omitUndefined({[RELEASE_SLUG_GLOBAL]:releaseSlug,[BUILD_MARKER_GLOBAL]:!0,[PUBLIC_KEY_GLOBAL]:config.publicKey||void 0,[API_URL_GLOBAL]:config.publicKey&&apiHost?apiHost:void 0,[ENVIRONMENT_GLOBAL]:config.environment||void 0,[FORCE_ENABLE_GLOBAL]:forceEnable?!0:void 0})}function interfereCompilerConfig(compiler,config){let existingHook=compiler?.runAfterProductionCompile;return omitUndefined({...compiler,async runAfterProductionCompile(context){existingHook&&await existingHook(context),config.publicKey===null&&log.warn(`Public key not set`,[`INTERFERE_PUBLIC_KEY is not set, so this build sends no events.`,`Set it on builds that you deploy.`]);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}`])}}})}function resolveBrowserSourceMaps(config,productionBrowserSourceMaps){let releasing=config.apiKey!==null&&config.uploadSourceMaps;return releasing&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),releasing?!0:productionBrowserSourceMaps}function disabledInterfereConfig(nextConfig){let{interfere:_interfere,webpack,turbopack,...rest}=nextConfig,projectDir=process.cwd(),build=configureBuild({existingTurbopack:turbopack,existingWebpack:webpack,nextMajor:readNextMajorVersion(projectDir),projectDir,remoteConfigFetch:void 0,values:{[BUILD_MARKER_GLOBAL]:!0,[DISABLED_GLOBAL]:!0,[RELEASE_SLUG_GLOBAL]:null}});return omitUndefined({...rest,turbopack:build.turbopack,webpack:build.webpack})}function buildInterfereConfig(phase,settings,nextConfig){if(settings.disable)return disabledInterfereConfig(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,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingTurbopack:turbopack,existingWebpack:webpack,nextMajor,projectDir,remoteConfigFetch:resolveRemoteConfigFetch(config,phase),values:interfereInjectedValues(config,apiHost,forceEnable)});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});return omitUndefined({...rest,env:userEnv,compiler:interfereCompilerConfig(compiler,config),productionBrowserSourceMaps:resolveBrowserSourceMaps(config,productionBrowserSourceMaps),turbopack:build.turbopack,webpack:build.webpack,experimental})}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing};return instrumentationHook&&(merged.instrumentationHook=!0),traceMetadata&&(merged.clientTraceMetadata=[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]),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(runGitCommand),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,uploadSourceMaps:settings.sourceMaps?.upload??!0,releaseSlug:slug}}function resolveRemoteConfigFetch(config,phase){if(phase===PHASE_PRODUCTION_BUILD&&config.publicKey)return{apiUrl:config.apiUrl,cacheScope:crypto.randomUUID(),publicKey:config.publicKey}}async function runBuildReleasePipeline(context,config){let release=checkReleaseConfig(DIAGNOSTIC_CONTEXT,{apiKey:config.apiKey,commitSha:config.buildId,releaseSlug:config.releaseSlug});if(release.status===`skip`){log.warn(release.title,[...release.details]);return}if(release.status===`fail`)return log.fatal(release.title,[...release.details]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey:release.apiKey,buildId:release.commitSha,releaseSlug:release.releaseSlug});if(!result.accepted){result.reason===`no_source_maps`&&log.warn(`Release remains unready`,[`No source maps found. Enable source-map generation and upload them during the build.`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config.org.slug}/surfaces/${result.config.surface.slug}`,`Release: ${result.release.destination.slug}`,`Build: ${result.buildId}`,config.uploadSourceMaps?`Artifacts: ${result.fileCount} source maps`:`Source-map uploads disabled`])}catch(error){let diagnostic=diagnoseRelease(DIAGNOSTIC_CONTEXT,error);log[diagnostic.severity](diagnostic.title,[...diagnostic.details])}}export{withInterfere};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildIntegrationRan,hasEnvCredentials,warnBuildIntegrationDegraded,warnBuildIntegrationMissing}from"./internal/build-marker-guard.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{createWrapperSingleton}from"@interfere/react/internal/wrapper-singleton";import{capture,span}from"@interfere/react/api";const wrapper=createWrapperSingleton({initEntryName:`instrumentation-client`,producerVersion:PRODUCER_VERSION}),{close,consent,getKernel,getKernelOrNull,identity,subscribeToKernel}=wrapper;async function init(opts){return buildIntegrationRan()?
|
|
1
|
+
import{buildIntegrationRan,hasEnvCredentials,interfereDisabled,warnBuildIntegrationDegraded,warnBuildIntegrationMissing}from"./internal/build-marker-guard.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{createWrapperSingleton}from"@interfere/react/internal/wrapper-singleton";import{capture,span}from"@interfere/react/api";const wrapper=createWrapperSingleton({initEntryName:`instrumentation-client`,producerVersion:PRODUCER_VERSION}),{close,consent,getKernel,getKernelOrNull,identity,subscribeToKernel}=wrapper;async function init(opts){return buildIntegrationRan()?initKernel(opts):initAfterHoistedImports(opts)}async function initKernel(opts){return interfereDisabled()?null:wrapper.init(opts)}async function initAfterHoistedImports(opts){if(await Promise.resolve(),!buildIntegrationRan()){let{promise,resolve}=Promise.withResolvers();setTimeout(resolve,0),await promise}return buildIntegrationRan()?initKernel(opts):hasEnvCredentials()?(warnBuildIntegrationDegraded(),wrapper.init(opts)):(warnBuildIntegrationMissing(),null)}export{capture,close,consent,getKernel,getKernelOrNull,identity,init,span,subscribeToKernel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.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{onRequestError}from"./internal/server/capture.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{getStringFromEnv}from"@opentelemetry/core";import{trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;async 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:resolveReleaseSlug().slug;releaseSlug||console.warn(`[interfere] edge runtime has no release.slug; configure a build release or INTERFERE_SOURCE_ID.`);let provider=new BasicTracerProvider({resource:resourceFromAttributes(omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0})),spanProcessors:[new SimpleSpanProcessor(new FetchTraceExporter({baseUrl:env.apiUrl,publicKeyHeader:{apiKey:env.publicKey},headers:{"x-interfere-producer-version":PRODUCER_VERSION}}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(async()=>provider.forceFlush()),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
|
|
1
|
+
import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.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{onRequestError}from"./internal/server/capture.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{getStringFromEnv}from"@opentelemetry/core";import{createUrlRedactionSpanProcessor,resolveUrlRedaction}from"@interfere/types/sdk/url-redaction";import{trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;async 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:resolveReleaseSlug().slug;releaseSlug||console.warn(`[interfere] edge runtime has no release.slug; configure a build release or INTERFERE_SOURCE_ID.`);let provider=new BasicTracerProvider({resource:resourceFromAttributes(omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0})),spanProcessors:[createUrlRedactionSpanProcessor(resolveUrlRedaction(opts.privacy?.urls)),new SimpleSpanProcessor(new FetchTraceExporter({baseUrl:env.apiUrl,publicKeyHeader:{apiKey:env.publicKey},headers:{"x-interfere-producer-version":PRODUCER_VERSION}}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(async()=>provider.forceFlush()),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
|
package/dist/instrumentation.mjs
CHANGED
|
@@ -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{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{CacheSafeTraceContextPropagator}from"./internal/server/cache-safe-trace-context.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{FreshnessStampingPropagator}from"./internal/server/freshness-propagator.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{drainUrl}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{CompositePropagator,W3CBaggagePropagator,getStringFromEnv}from"@opentelemetry/core";import{ProxyTracerProvider,propagation,trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{readLogSourcingMode}from"@interfere/types/sdk/log-sourcing";import{readServerTracingMode}from"@interfere/types/sdk/server-tracing";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;function canFlush(provider){return`forceFlush`in provider&&typeof provider.forceFlush==`function`}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 tracesUrl=drainUrl(env.apiUrl,`traces`,env.publicKey),logsUrl=drainUrl(env.apiUrl,`logs`,env.publicKey),exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION},resourceAttributes=omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0}),compression=opts.compression===`gzip`?CompressionAlgorithm.GZIP:CompressionAlgorithm.NONE,isDrainMode=(opts.serverTracing??readServerTracingMode())===`drain`,spanProcessors=[new BaggageSpanProcessor(()=>!0),...isDrainMode?[]:[new BatchSpanProcessor(new OTLPTraceExporter({compression,headers:exporterHeaders,timeoutMillis:EXPORT_TIMEOUT_MS,url:tracesUrl}))],...opts._internalAdditionalSpanProcessors??[]],isLogDrainMode=readLogSourcingMode()===`drain`,logRecordProcessors=[...isLogDrainMode?[]:[new BatchLogRecordProcessor({exporter:new OTLPLogExporter({compression,headers:exporterHeaders,timeoutMillis:EXPORT_TIMEOUT_MS,url:logsUrl})})],...opts._internalAdditionalLogRecordProcessors??[]];return{enableConsoleBridge:()=>isLogDrainMode?()=>{}:bridgeConsoleToOtel(),fetchRemoteConfig:async()=>fetchAndCacheRemoteConfig(),idGenerator:new PrerenderSafeIdGenerator,instrumentations:[],traceContextPropagator:new CacheSafeTraceContextPropagator,logRecordProcessors,propagators:[new FreshnessStampingPropagator,new W3CBaggagePropagator],resourceAttributes,spanProcessors}}async function register(opts={}){if(registered)return;let drainMode=(opts.serverTracing??readServerTracingMode())===`drain`;if(drainMode&&process.env.OTEL_SDK_DISABLED?.trim())return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);if(drainMode){let{registerOTel}=await import(`@vercel/otel`);registerOTel({serviceName:String(kit.resourceAttributes[`service.name`]),attributes:kit.resourceAttributes,idGenerator:kit.idGenerator,instrumentations:kit.instrumentations,propagators:[`auto`,kit.traceContextPropagator,...kit.propagators],spanProcessors:[`auto`,...kit.spanProcessors]});let provider=trace.getTracerProvider(),delegate=provider instanceof ProxyTracerProvider?provider.getDelegate():provider;if(!canFlush(delegate))throw Error(`Interfere could not register the Vercel tracer provider. Use buildInterfereOtelKit with an existing OpenTelemetry bootstrap.`);tracerProvider=delegate}else{let provider=new NodeTracerProvider({idGenerator:kit.idGenerator,resource,spanProcessors:kit.spanProcessors});propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[kit.traceContextPropagator,...kit.propagators]})),provider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),tracerProvider=provider}loggerProvider=new LoggerProvider({processors:kit.logRecordProcessors,resource});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),registerInstrumentations({instrumentations:kit.instrumentations,tracerProvider:trace.getTracerProvider()}),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
|
+
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{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{CacheSafeTraceContextPropagator}from"./internal/server/cache-safe-trace-context.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{FreshnessStampingPropagator}from"./internal/server/freshness-propagator.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{drainUrl}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{CompositePropagator,W3CBaggagePropagator,getStringFromEnv}from"@opentelemetry/core";import{createUrlRedactionSpanProcessor,resolveUrlRedaction}from"@interfere/types/sdk/url-redaction";import{ProxyTracerProvider,propagation,trace}from"@opentelemetry/api";import{defaultServiceName,resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{readLogSourcingMode}from"@interfere/types/sdk/log-sourcing";import{readServerTracingMode}from"@interfere/types/sdk/server-tracing";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;function canFlush(provider){return`forceFlush`in provider&&typeof provider.forceFlush==`function`}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 tracesUrl=drainUrl(env.apiUrl,`traces`,env.publicKey),logsUrl=drainUrl(env.apiUrl,`logs`,env.publicKey),exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION},resourceAttributes=omitUndefined({"deployment.environment.name":env.nodeEnvironment,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,"service.name":opts.serviceName||getStringFromEnv(`OTEL_SERVICE_NAME`)||defaultServiceName(),"service.namespace":opts.serviceNamespace,"telemetry.sdk.language":`nodejs`,"release.slug":releaseSlug||void 0}),compression=opts.compression===`gzip`?CompressionAlgorithm.GZIP:CompressionAlgorithm.NONE,isDrainMode=(opts.serverTracing??readServerTracingMode())===`drain`,spanProcessors=[createUrlRedactionSpanProcessor(resolveUrlRedaction(opts.privacy?.urls)),new BaggageSpanProcessor(()=>!0),...isDrainMode?[]:[new BatchSpanProcessor(new OTLPTraceExporter({compression,headers:exporterHeaders,timeoutMillis:EXPORT_TIMEOUT_MS,url:tracesUrl}))],...opts._internalAdditionalSpanProcessors??[]],isLogDrainMode=readLogSourcingMode()===`drain`,logRecordProcessors=[...isLogDrainMode?[]:[new BatchLogRecordProcessor({exporter:new OTLPLogExporter({compression,headers:exporterHeaders,timeoutMillis:EXPORT_TIMEOUT_MS,url:logsUrl})})],...opts._internalAdditionalLogRecordProcessors??[]];return{enableConsoleBridge:()=>isLogDrainMode?()=>{}:bridgeConsoleToOtel(),fetchRemoteConfig:async()=>fetchAndCacheRemoteConfig(),idGenerator:new PrerenderSafeIdGenerator,instrumentations:[],traceContextPropagator:new CacheSafeTraceContextPropagator,logRecordProcessors,propagators:[new FreshnessStampingPropagator,new W3CBaggagePropagator],resourceAttributes,spanProcessors}}async function register(opts={}){if(registered)return;let drainMode=(opts.serverTracing??readServerTracingMode())===`drain`;if(drainMode&&process.env.OTEL_SDK_DISABLED?.trim())return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);if(drainMode){let{registerOTel}=await import(`@vercel/otel`);registerOTel({serviceName:String(kit.resourceAttributes[`service.name`]),attributes:kit.resourceAttributes,idGenerator:kit.idGenerator,instrumentations:kit.instrumentations,propagators:[`auto`,kit.traceContextPropagator,...kit.propagators],spanProcessors:[`auto`,...kit.spanProcessors]});let provider=trace.getTracerProvider(),delegate=provider instanceof ProxyTracerProvider?provider.getDelegate():provider;if(!canFlush(delegate))throw Error(`Interfere could not register the Vercel tracer provider. Use buildInterfereOtelKit with an existing OpenTelemetry bootstrap.`);tracerProvider=delegate}else{let provider=new NodeTracerProvider({idGenerator:kit.idGenerator,resource,spanProcessors:kit.spanProcessors});propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[kit.traceContextPropagator,...kit.propagators]})),provider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),tracerProvider=provider}loggerProvider=new LoggerProvider({processors:kit.logRecordProcessors,resource});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),registerInstrumentations({instrumentations:kit.instrumentations,tracerProvider:trace.getTracerProvider()}),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};
|
|
@@ -3,6 +3,7 @@ import { NextConfig } from "next";
|
|
|
3
3
|
interface InterfereInjectedValues {
|
|
4
4
|
readonly __INTERFERE_API_URL__?: string;
|
|
5
5
|
readonly __INTERFERE_BUILD__?: boolean;
|
|
6
|
+
readonly __INTERFERE_DISABLED__?: boolean;
|
|
6
7
|
readonly __INTERFERE_ENVIRONMENT__?: string;
|
|
7
8
|
readonly __INTERFERE_FORCE_ENABLE__?: boolean;
|
|
8
9
|
readonly __INTERFERE_PUBLIC_KEY__?: string;
|
|
@@ -7,6 +7,15 @@ export declare function runBuildPipeline(context: ProductionCompileContext, meta
|
|
|
7
7
|
buildId: string;
|
|
8
8
|
releaseSlug: ReleaseSlug;
|
|
9
9
|
}): Promise<{
|
|
10
|
+
accepted: false;
|
|
11
|
+
fileCount: number;
|
|
12
|
+
reason: "missing_destination_provider" | "missing_source_provider";
|
|
13
|
+
buildId?: never;
|
|
14
|
+
cleanupFailures?: never;
|
|
15
|
+
config?: never;
|
|
16
|
+
release?: never;
|
|
17
|
+
timing?: never;
|
|
18
|
+
} | {
|
|
10
19
|
accepted: false;
|
|
11
20
|
fileCount: number;
|
|
12
21
|
reason: "no_source_maps";
|
|
@@ -1 +1 @@
|
|
|
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{
|
|
1
|
+
import{log}from"../logger.mjs";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{reportUnmappedClientChunks}from"./source-maps/guard.mjs";import{EMPTY_TELEMETRY}from"./telemetry.mjs";import{classifyReleasePipelineError}from"@interfere/types/sdk/build-diagnostics";import{InterfereClient}from"@interfere/javascript";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{uploadSourceMapChunks}from"@interfere/build/upload-source-map-chunks";import{completeSourceMapsResponse}from"@interfere/types/data/source-maps";import{releaseBuildResultSchema}from"@interfere/types/releases/build-telemetry";import{createReleaseResponse}from"@interfere/types/releases/definition";import{extractRichness}from"@interfere/types/sdk/source-map-richness";async function runBuildPipeline(context,metadata){let client=new InterfereClient({fetch:sourceMapUploadFetch,baseUrl:metadata.apiUrl,apiKey:{token:metadata.apiKey},maxRetries:4,timeoutInSeconds:10}),start=performance.now(),timing={...EMPTY_TELEMETRY},{releaseSlug}=metadata,files=[],cleanupFailures=[],reportResult=`pipeline_failed`;try{let bundler=detectActiveBundler(process.env,process.argv,readNextMajorVersion(context.projectDir)),discovered=metadata.uploadSourceMaps||metadata.purgeSourceMaps?await discoverSourceMaps({bundler,...context}):{files:[],missingSourceMapCount:0,sourceFileCount:0};({files}=discovered);let{sourceFileCount}=discovered;metadata.uploadSourceMaps&&discovered.missingSourceMapCount>0&&log.warn(`Some source maps are missing`,[`${discovered.missingSourceMapCount} referenced source map(s) not found. Errors in those chunks will not resolve to source.`]);let config=await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)});timing.discover=Math.round(performance.now()-start),timing.fileCount=files.length;let request=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config);if(!request.ok)return reportResult=null,{accepted:!1,fileCount:0,reason:request.reason};if(metadata.uploadSourceMaps&&files.length===0)return reportResult=`no_source_maps`,{accepted:!1,fileCount:0,reason:`no_source_maps`};metadata.uploadSourceMaps&&await reportUnmappedClientChunks({discovered:files,...context});let registering=performance.now(),release=createReleaseResponse.parse(await client.releases.createRelease(request.request,{abortSignal:AbortSignal.timeout(6e4)})),registered=performance.now();if(releaseSlug=release.destination.slug,timing.createRelease=Math.round(registered-registering),metadata.uploadSourceMaps){let signFiles=files.map(file=>({path:file.path,sizeBytes:new TextEncoder().encode(file.content).byteLength,...extractRichness(file.content)})),environment=metadata.environment??void 0,signed=await client.releases.signSourceMapUploads({releaseSlug,environment,files:signFiles},{abortSignal:AbortSignal.timeout(6e4)});timing.totalBytes=await uploadSourceMapChunks({compression:`gzip`,client,environment,releaseSlug,files,signed});let accepted=completeSourceMapsResponse.safeParse(await client.releases.finalizeSourceMaps({releaseSlug,environment,bundler,sourceFileCount,files:files.map(({path,hash,debugId,chunkUrl})=>({path,hash,debugId,chunkUrl}))},{abortSignal:AbortSignal.timeout(Math.max(0,signed.expiresAt*1e3-Date.now()))}));if(!accepted.success||!accepted.data.ok||accepted.data.fileCount!==files.length)throw Error(`Source-map server did not accept the complete batch`)}return timing.upload=metadata.uploadSourceMaps?Math.round(performance.now()-registered):0,reportResult=`ok`,{accepted:!0,buildId:release.build.hash,cleanupFailures,config,fileCount:metadata.uploadSourceMaps?files.length:0,release,timing}}catch(error){throw reportResult=releaseBuildResultSchema.safeParse(classifyReleasePipelineError(error).toLowerCase()).data??`pipeline_failed`,error}finally{let cleanupStart=performance.now();if(metadata.purgeSourceMaps)try{cleanupFailures.push(...await cleanupSourceMaps(files))}catch(error){cleanupFailures.push({absolute:context.distDir,code:void 0,message:error instanceof Error?error.message:String(error)})}timing.cleanup=Math.round(performance.now()-cleanupStart),cleanupFailures.length>0&&log.warn(`Source-map cleanup left files on disk`,cleanupFailures.map(failure=>`${failure.absolute}: ${failure.message}`)),timing.total=Math.round(performance.now()-start),reportResult!==null&&await client.releases.reportTelemetry({...timing,result:reportResult,releaseSlug},{abortSignal:AbortSignal.timeout(1e4)}).catch(()=>void 0)}}export{runBuildPipeline};
|
|
@@ -2,52 +2,66 @@ import { ReleaseSlug } from "@interfere/types/releases/slug";
|
|
|
2
2
|
import { resolveReleaseRequest as resolveReleaseRequest$1 } from "@interfere/build/release-request";
|
|
3
3
|
//#region src/internal/build/release/index.d.ts
|
|
4
4
|
export declare function resolveReleaseRequest(buildId: string, environment: string | null, releaseSlug: ReleaseSlug, config: Parameters<typeof resolveReleaseRequest$1>[0]["config"]): {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
5
|
+
ok: false;
|
|
6
|
+
reason: "missing_source_provider";
|
|
7
|
+
request?: never;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
reason: "missing_destination_provider";
|
|
11
|
+
request?: never;
|
|
12
|
+
} | {
|
|
13
|
+
reason?: never;
|
|
14
|
+
ok: true;
|
|
15
|
+
request: {
|
|
16
|
+
buildId: string;
|
|
17
|
+
destination: {
|
|
18
|
+
deploymentId: string | null;
|
|
19
|
+
deploymentUrl: string | null;
|
|
20
|
+
destinationReleaseId: string | null;
|
|
21
|
+
environment: string | null;
|
|
22
|
+
environmentName: string | null;
|
|
23
|
+
environmentTarget: string | null;
|
|
24
|
+
provider: "vercel";
|
|
25
|
+
} | {
|
|
26
|
+
accountId: string | null;
|
|
27
|
+
deploymentUrl: string | null;
|
|
28
|
+
destinationReleaseId: string | null;
|
|
29
|
+
environment: string | null;
|
|
30
|
+
environmentName: string | null;
|
|
31
|
+
environmentTarget: string | null;
|
|
32
|
+
provider: "cloudflare";
|
|
33
|
+
deploymentId?: string | null | undefined;
|
|
34
|
+
trafficPercentage?: number | null | undefined;
|
|
35
|
+
scriptName: string | null;
|
|
36
|
+
versionId: string | null;
|
|
37
|
+
} | {
|
|
38
|
+
deploymentUrl: string | null;
|
|
39
|
+
destinationReleaseId: string | null;
|
|
40
|
+
environment: string | null;
|
|
41
|
+
provider: "cli";
|
|
42
|
+
} | null;
|
|
43
|
+
producerVersion?: string | undefined;
|
|
44
|
+
slug?: string | undefined;
|
|
45
|
+
source: {
|
|
46
|
+
branch: string;
|
|
47
|
+
commitMessage: string;
|
|
48
|
+
commitSha: string | null;
|
|
49
|
+
provider: "github";
|
|
50
|
+
runId?: string | null | undefined;
|
|
51
|
+
runUrl?: string | null | undefined;
|
|
52
|
+
runnerName?: string | null | undefined;
|
|
53
|
+
} | {
|
|
54
|
+
commitMessage: string | null;
|
|
55
|
+
commitRef: string | null;
|
|
56
|
+
commitSha: string | null;
|
|
57
|
+
commitUrl: string | null;
|
|
58
|
+
deploymentUrl?: string | null | undefined;
|
|
59
|
+
environment?: string | null | undefined;
|
|
60
|
+
provider: "cli";
|
|
61
|
+
runId: string | null;
|
|
62
|
+
runUrl: string | null;
|
|
63
|
+
runnerName: string | null;
|
|
64
|
+
};
|
|
51
65
|
};
|
|
52
66
|
};
|
|
53
67
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log}from"../../logger.mjs";import{PRODUCER_VERSION}from"../../version.mjs";import{resolveReleaseRequest as resolveReleaseRequest$1}from"@interfere/build/release-request";function resolveReleaseRequest(buildId,environment,releaseSlug,config){let result=resolveReleaseRequest$1({buildId,config,environment,producerVersion:PRODUCER_VERSION,releaseSlug});if(result.ok)return result
|
|
1
|
+
import{log}from"../../logger.mjs";import{PRODUCER_VERSION}from"../../version.mjs";import{resolveReleaseRequest as resolveReleaseRequest$1}from"@interfere/build/release-request";function resolveReleaseRequest(buildId,environment,releaseSlug,config){let result=resolveReleaseRequest$1({buildId,config,environment,producerVersion:PRODUCER_VERSION,releaseSlug});if(result.ok)return result;let sourceMissing=result.reason===`missing_source_provider`;return log.warn(sourceMissing?`Release skipped: missing version control provider`:`Release skipped: missing deployment target`,[sourceMissing?`This surface does not have a version control provider configured.`:`This surface does not have a deployment target integration configured.`,`Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`]),result}export{resolveReleaseRequest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash}from"node:crypto";import{mapWithConcurrency}from"@interfere/helpers/map-with-concurrency";import{open,readFile,stat}from"node:fs/promises";const DEBUG_ID_RE=/\/\/[#@]\s*debugId=(?<debugId>[0-9a-f-]+)\s*$/mu,DEBUG_ID_TAIL_BYTES=65536;async function
|
|
1
|
+
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash}from"node:crypto";import{mapWithConcurrency}from"@interfere/helpers/map-with-concurrency";import{open,readFile,stat}from"node:fs/promises";const DEBUG_ID_RE=/\/\/[#@]\s*debugId=(?<debugId>[0-9a-f-]+)\s*$/mu,SOURCE_MAPPING_URL_RE=/\/\/[#@]\s*sourceMappingURL=\S+\s*$/mu,DEBUG_ID_TAIL_BYTES=65536;function readChunkMarkers(text){return{debugId:DEBUG_ID_RE.exec(text)?.groups?.debugId??null,referencesMap:SOURCE_MAPPING_URL_RE.test(text)}}async function readChunk(jsAbs){let tail=await readTail(jsAbs).catch(()=>null),fromTail=tail===null?null:readChunkMarkers(tail);if(fromTail?.debugId)return fromTail;let content=await readFile(jsAbs,`utf8`).catch(()=>null);return content===null?{debugId:null,referencesMap:!1}:readChunkMarkers(content)}async function readTail(jsAbs){let{size}=await stat(jsAbs);if(size<=DEBUG_ID_TAIL_BYTES)return null;let handle=await open(jsAbs,`r`);try{let buf=Buffer.allocUnsafe(DEBUG_ID_TAIL_BYTES),{bytesRead}=await handle.read(buf,0,DEBUG_ID_TAIL_BYTES,size-DEBUG_ID_TAIL_BYTES);return buf.subarray(0,bytesRead).toString(`utf8`)}finally{await handle.close()}}async function discoverTurbopack(opts){let absDistDir=resolveDistDir(opts.projectDir,opts.distDir),relDistDir=relative(opts.projectDir,absDistDir),[mapPaths,jsPaths]=await Promise.all([walkDistTrees(absDistDir,`.js.map`),walkDistTrees(absDistDir,`.js`)]),chunksByDebugId=new Map;await mapWithConcurrency(jsPaths,16,async jsAbs=>{let{debugId,referencesMap}=await readChunk(jsAbs);if(!debugId)return;let chunkUrl=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir),chunk=chunksByDebugId.get(debugId);chunk?(chunk.chunkAbsolutes.push(jsAbs),chunk.referencesMap||=referencesMap):chunksByDebugId.set(debugId,{chunkAbsolutes:[jsAbs],chunkUrl,referencesMap})});let loaded=(await mapWithConcurrency(mapPaths,16,async mapAbs=>{let content=await readFile(mapAbs,`utf8`).catch(()=>null);if(content===null)return null;let debugId;try{let parsed=JSON.parse(content);typeof parsed==`object`&&parsed&&`debugId`in parsed&&typeof parsed.debugId==`string`&&parsed.debugId.length>0&&({debugId}=parsed)}catch{return null}if(!debugId)return null;let chunk=chunksByDebugId.get(debugId);return chunk?{absolute:mapAbs,chunkAbsolutes:chunk.chunkAbsolutes,chunkUrl:chunk.chunkUrl,content,debugId,hash:createHash(`sha256`).update(content).digest(`hex`),path:toPublicPath(relative(opts.projectDir,mapAbs),relDistDir)}:null})).filter(f=>f!==null),loadedDebugIds=new Set(loaded.map(file=>file.debugId));return{files:loaded,missingSourceMapCount:[...chunksByDebugId].filter(([debugId,chunk])=>chunk.referencesMap&&!loadedDebugIds.has(debugId)).length,sourceFileCount:loaded.length}}export{discoverTurbopack};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{injectDebugIdIntoJs,readDebugIdFromJs}from"@interfere/types/data/source-maps";import{mapWithConcurrency}from"@interfere/helpers/map-with-concurrency";import{readFile,writeFile}from"node:fs/promises";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(?<sourceMap>\S+)\s*$/u;function injectDebugIdIntoMap(content,debugId){let json=JSON.parse(content);if(typeof json!=`object`||!json||Array.isArray(json))throw TypeError(`Source map must be a JSON object`);return Reflect.set(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`),discovered=await mapWithConcurrency(jsPaths,16,async jsAbs=>{let content=await readFile(jsAbs,`utf8`),match=SOURCEMAPPING_RE.exec(content);if(!match?.groups?.sourceMap)return null;let mapAbs=`${jsAbs.slice(0,jsAbs.lastIndexOf(`/`)+1)}${decodeURIComponent(match.groups.sourceMap)}`,jsPublic=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir);return[toPublicPath(relative(opts.projectDir,mapAbs),relDistDir),{existingDebugId:readDebugIdFromJs(content),jsAbs,jsContent:content,jsPublic,mapAbs}]}),pairs=new Map;for(let[mapPublic,pair]of discovered.filter(entry=>entry!==null)){let chunkAbsolutes=pairs.get(mapPublic)?.chunkAbsolutes??[];pairs.set(mapPublic,{...pair,chunkAbsolutes:[...chunkAbsolutes,pair.jsAbs]})}
|
|
1
|
+
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{injectDebugIdIntoJs,readDebugIdFromJs}from"@interfere/types/data/source-maps";import{mapWithConcurrency}from"@interfere/helpers/map-with-concurrency";import{readFile,writeFile}from"node:fs/promises";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(?<sourceMap>\S+)\s*$/u;function injectDebugIdIntoMap(content,debugId){let json=JSON.parse(content);if(typeof json!=`object`||!json||Array.isArray(json))throw TypeError(`Source map must be a JSON object`);return Reflect.set(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`),discovered=await mapWithConcurrency(jsPaths,16,async jsAbs=>{let content=await readFile(jsAbs,`utf8`),match=SOURCEMAPPING_RE.exec(content);if(!match?.groups?.sourceMap||match.groups.sourceMap.startsWith(`data:`))return null;let mapAbs=`${jsAbs.slice(0,jsAbs.lastIndexOf(`/`)+1)}${decodeURIComponent(match.groups.sourceMap)}`,jsPublic=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir);return[toPublicPath(relative(opts.projectDir,mapAbs),relDistDir),{existingDebugId:readDebugIdFromJs(content),jsAbs,jsContent:content,jsPublic,mapAbs}]}),pairs=new Map;for(let[mapPublic,pair]of discovered.filter(entry=>entry!==null)){let chunkAbsolutes=pairs.get(mapPublic)?.chunkAbsolutes??[];pairs.set(mapPublic,{...pair,chunkAbsolutes:[...chunkAbsolutes,pair.jsAbs]})}let loaded=(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(),mapJson=JSON.parse(mapContent),finalMap=typeof mapJson==`object`&&mapJson&&`debugId`in mapJson&&mapJson.debugId===debugId?mapContent:injectDebugIdIntoMap(mapContent,debugId);if(!pair.existingDebugId){let injectedJs=injectDebugIdIntoJs(pair.jsContent,debugId);await writeFile(pair.jsAbs,injectedJs,`utf8`)}return finalMap!==mapContent&&await writeFile(pair.mapAbs,finalMap,`utf8`),{absolute:pair.mapAbs,chunkAbsolutes:pair.chunkAbsolutes,chunkUrl:pair.jsPublic,content:finalMap,debugId,hash:createHash(`sha256`).update(finalMap).digest(`hex`),path:mapPublic}}))).filter(f=>f!==null);return{files:loaded,missingSourceMapCount:pairs.size-loaded.length,sourceFileCount:pairs.size}}export{discoverWebpack,injectDebugIdIntoMap};
|
|
@@ -13,6 +13,7 @@ export interface SourceMapFile {
|
|
|
13
13
|
}
|
|
14
14
|
export interface DiscoveryResult {
|
|
15
15
|
readonly files: SourceMapFile[];
|
|
16
|
+
readonly missingSourceMapCount: number;
|
|
16
17
|
readonly sourceFileCount: number;
|
|
17
18
|
}
|
|
18
19
|
export declare function discoverSourceMaps(opts: {
|
|
@@ -21,6 +22,7 @@ export declare function discoverSourceMaps(opts: {
|
|
|
21
22
|
distDir: string;
|
|
22
23
|
}): Promise<{
|
|
23
24
|
files: SourceMapFile[];
|
|
25
|
+
missingSourceMapCount: number;
|
|
24
26
|
sourceFileCount: number;
|
|
25
27
|
}>;
|
|
26
28
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SourceMapFile } from "./discover.mjs";
|
|
2
2
|
//#region src/internal/build/source-maps/guard.d.ts
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function reportUnmappedClientChunks(opts: {
|
|
4
4
|
projectDir: string;
|
|
5
5
|
distDir: string;
|
|
6
6
|
discovered: readonly SourceMapFile[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log}from"../../logger.mjs";import{resolveDistDir,walkDistTrees}from"./paths.mjs";import{readDebugIdFromJs}from"@interfere/types/data/source-maps";import{readFile}from"node:fs/promises";async function
|
|
1
|
+
import{log}from"../../logger.mjs";import{resolveDistDir,walkDistTrees}from"./paths.mjs";import{readDebugIdFromJs}from"@interfere/types/data/source-maps";import{readFile}from"node:fs/promises";async function reportUnmappedClientChunks(opts){if(opts.discovered.some(file=>file.chunkUrl.includes(`static/chunks`)))return;let absDistDir=resolveDistDir(opts.projectDir,opts.distDir),clientChunks=(await walkDistTrees(absDistDir,`.js`)).filter(path=>path.replaceAll(`\\`,`/`).includes(`/static/chunks/`)),stampedCount=(await Promise.all(clientChunks.map(async jsAbs=>{let content=await readFile(jsAbs,`utf8`).catch(()=>``);return readDebugIdFromJs(content)!==null}))).filter(Boolean).length;stampedCount!==0&&log.error(`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.`,`The build continues.`])}export{reportUnmappedClientChunks};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{omitUndefined}from"@interfere/helpers/omit-undefined";import{INGEST_URL_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PLUGINS_GLOBAL}from"@interfere/types/sdk/remote-config";import{LOG_SOURCING_GLOBAL}from"@interfere/types/sdk/log-sourcing";import{SERVER_TRACING_GLOBAL}from"@interfere/types/sdk/server-tracing";import{callbackify}from"node:util";import{createBuildConfigResolver}from"@interfere/build/config";const resolveBuildConfig=createBuildConfigResolver();function valueInjectionLoader(userCode){let options=this.getOptions(),callback=this.async(),values=resolveValues(options);callbackify(async()=>{let globals=await resolveRemoteConfig(resolveFetch(options)).catch(()=>({}));return render({...values,...globals},userCode)})(callback)}function remoteGlobals(config){return omitUndefined({[SERVER_TRACING_GLOBAL]:config.serverTracing,[LOG_SOURCING_GLOBAL]:config.logSourcing,[INGEST_URL_GLOBAL]:config.ingestUrl,[PLUGINS_GLOBAL]:config.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(`
|
|
1
|
+
import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{INGEST_URL_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PLUGINS_GLOBAL}from"@interfere/types/sdk/remote-config";import{LOG_SOURCING_GLOBAL}from"@interfere/types/sdk/log-sourcing";import{SERVER_TRACING_GLOBAL}from"@interfere/types/sdk/server-tracing";import{callbackify}from"node:util";import{createBuildConfigResolver}from"@interfere/build/config";let remoteConfigFallbackLogged=!1;const resolveBuildConfig=createBuildConfigResolver(result=>{result.status!==`fallback`||remoteConfigFallbackLogged||(remoteConfigFallbackLogged=!0,log.warn(`Using default SDK settings`,[`Could not load SDK settings from Interfere. The build continues with the default settings.`]))});function valueInjectionLoader(userCode){let options=this.getOptions(),callback=this.async(),values=resolveValues(options);callbackify(async()=>{let globals=await resolveRemoteConfig(resolveFetch(options)).catch(()=>({}));return render({...values,...globals},userCode)})(callback)}function remoteGlobals(config){return omitUndefined({[SERVER_TRACING_GLOBAL]:config.serverTracing,[LOG_SOURCING_GLOBAL]:config.logSourcing,[INGEST_URL_GLOBAL]:config.ingestUrl,[PLUGINS_GLOBAL]:config.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
2
|
`)}\n${userCode}`}async function resolveRemoteConfig(fetchOptions){return fetchOptions?resolveBuildConfig(fetchOptions.apiUrl,fetchOptions.publicKey,fetchOptions.cacheScope).then(remoteGlobals):{}}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`&&(value.cacheScope===void 0||typeof value.cacheScope==`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};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//#region src/internal/build-marker-guard.d.ts
|
|
2
2
|
export declare function buildIntegrationRan(): boolean;
|
|
3
|
+
export declare function interfereDisabled(): boolean;
|
|
3
4
|
export declare function hasEnvCredentials(): boolean;
|
|
4
5
|
export declare function warnBuildIntegrationMissing(): void;
|
|
5
6
|
export declare function warnBuildIntegrationDegraded(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BUILD_MARKER_GLOBAL}from"@interfere/types/sdk/injected-globals";function buildIntegrationRan(){let globals=globalThis;return BUILD_MARKER_GLOBAL in globals&&globals[BUILD_MARKER_GLOBAL]!==void 0}function hasEnvCredentials(){return typeof process>`u`?!1:!!process.env.INTERFERE_PUBLIC_KEY||!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function warnBuildIntegrationMissing(){console.warn(`[interfere] withInterfere() did not run for this build, so Interfere is inactive and will send no events. Another Next config wrapper can silently drop it when wrapping withInterfere(). Make withInterfere() the outermost wrapper in next.config, or upgrade the wrapping tool.`)}function warnBuildIntegrationDegraded(){console.warn(`[interfere] withInterfere() did not run for this build. Interfere will still send events using credentials from the environment, but release association and sourcemaps are unavailable. Make withInterfere() the outermost wrapper in next.config to restore them.`)}export{buildIntegrationRan,hasEnvCredentials,warnBuildIntegrationDegraded,warnBuildIntegrationMissing};
|
|
1
|
+
import{BUILD_MARKER_GLOBAL,DISABLED_GLOBAL}from"@interfere/types/sdk/injected-globals";function buildIntegrationRan(){let globals=globalThis;return BUILD_MARKER_GLOBAL in globals&&globals[BUILD_MARKER_GLOBAL]!==void 0}function interfereDisabled(){let globals=globalThis;return DISABLED_GLOBAL in globals&&globals[DISABLED_GLOBAL]===!0}function hasEnvCredentials(){return typeof process>`u`?!1:!!process.env.INTERFERE_PUBLIC_KEY||!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function warnBuildIntegrationMissing(){console.warn(`[interfere] withInterfere() did not run for this build, so Interfere is inactive and will send no events. Another Next config wrapper can silently drop it when wrapping withInterfere(). Make withInterfere() the outermost wrapper in next.config, or upgrade the wrapping tool.`)}function warnBuildIntegrationDegraded(){console.warn(`[interfere] withInterfere() did not run for this build. Interfere will still send events using credentials from the environment, but release association and sourcemaps are unavailable. Make withInterfere() the outermost wrapper in next.config to restore them.`)}export{buildIntegrationRan,hasEnvCredentials,interfereDisabled,warnBuildIntegrationDegraded,warnBuildIntegrationMissing};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function isConsoleMethod(method){return typeof method==`function`}function writeConsoleLines(method,lines){if(isConsoleMethod(method))for(let line of lines)method.call(globalThis.console,line)}export{writeConsoleLines};
|
package/dist/internal/env.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{API_URL}from"@interfere/constants/api";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";import{getStringGlobal}from"@interfere/react/util/global";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,explicit:getStringGlobal(ENVIRONMENT_GLOBAL)});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:{destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID),sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID)}}}export{isEnabledOnServer,readInterfereEnv};
|
|
1
|
+
import{interfereDisabled}from"./build-marker-guard.mjs";import{API_URL}from"@interfere/constants/api";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";import{getStringGlobal}from"@interfere/react/util/global";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return interfereDisabled()?!1:process.env.NODE_ENV===`production`||!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=resolveEnvironment({env:process.env,explicit:getStringGlobal(ENVIRONMENT_GLOBAL)});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:interfereDisabled()?null:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID),sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID)}}}export{isEnabledOnServer,readInterfereEnv};
|
package/dist/internal/logger.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";const styles={error:{content:chalk.redBright,prefix:`${chalk.red.bold(`⨯`)} `,prependLevel:!0,text:chalk.red.bold},fatal:{content:chalk.redBright,prefix:`${chalk.red.bold(`⨯`)} `,prependLevel:!0,text:chalk.red.bold},info:{content:chalk.white,prefix:chalk.whiteBright.bold(`❖`),prependLevel:!1,text:chalk.cyan.bold},warn:{content:chalk.yellowBright,prefix:`${chalk.yellow.bold(`⚠`)} `,prependLevel:!1,text:chalk.yellow.bold}},consoleMethods={error:`error`,fatal:`error`,info:`log`,warn:`warn`};function isTestEnv(){return!!(process.env.VITEST||process.env.VITEST_WORKER_ID)}var FatalError=class extends Error{name=`FatalError`};function emit(level,title,lines){if(isTestEnv())return;let style=styles[level],method=consoleMethods[level],fn=globalThis.console[method]
|
|
1
|
+
import{writeConsoleLines}from"./console-output.mjs";import chalk from"chalk";const styles={error:{content:chalk.redBright,prefix:`${chalk.red.bold(`⨯`)} `,prependLevel:!0,text:chalk.red.bold},fatal:{content:chalk.redBright,prefix:`${chalk.red.bold(`⨯`)} `,prependLevel:!0,text:chalk.red.bold},info:{content:chalk.white,prefix:chalk.whiteBright.bold(`❖`),prependLevel:!1,text:chalk.cyan.bold},warn:{content:chalk.yellowBright,prefix:`${chalk.yellow.bold(`⚠`)} `,prependLevel:!1,text:chalk.yellow.bold}},consoleMethods={error:`error`,fatal:`error`,info:`log`,warn:`warn`};function isTestEnv(){return!!(process.env.VITEST||process.env.VITEST_WORKER_ID)}var FatalError=class extends Error{name=`FatalError`};function emit(level,title,lines){if(isTestEnv())return;let style=styles[level],method=consoleMethods[level],fn=globalThis.console[method],prependLevel=style.prependLevel?`[${style.text(level.toUpperCase())}] `:``,output=[`${prependLevel} ${chalk.white(`Interfere →`)} ${style.text(title)}`,...lines.map((line,index)=>{let connector=index===lines.length-1?`└`:`├`;return`${prependLevel} ${connector} ${style.content(line)}`})];writeConsoleLines(fn,output)}const log={error:(title,lines)=>{emit(`error`,title,lines)},fatal(title,lines){if(emit(`fatal`,title,lines),isTestEnv())throw new FatalError(title);process.exit(1)},info:(title,lines)=>{emit(`info`,title,lines)},warn:(title,lines)=>{emit(`warn`,title,lines)}};export{FatalError,log};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IdGenerator } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
//#region src/internal/server/counter-id-generator.d.ts
|
|
3
|
+
export declare class CounterIdGenerator implements IdGenerator {
|
|
4
|
+
private readonly tracePrefix;
|
|
5
|
+
private readonly spanPrefix;
|
|
6
|
+
private traceCounter;
|
|
7
|
+
private spanCounter;
|
|
8
|
+
constructor();
|
|
9
|
+
generateTraceId(): string;
|
|
10
|
+
generateSpanId(): string;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomBytes}from"node:crypto";const TRACE_COUNTER_MODULUS=2**32,SPAN_COUNTER_MODULUS=2**16;var CounterIdGenerator=class{tracePrefix;spanPrefix;traceCounter=0;spanCounter=0;constructor(){let seed=randomBytes(18);this.tracePrefix=seed.subarray(0,12).toString(`hex`),this.spanPrefix=seed.subarray(12).toString(`hex`)}generateTraceId(){return this.traceCounter=(this.traceCounter+1)%TRACE_COUNTER_MODULUS,this.tracePrefix+this.traceCounter.toString(16).padStart(8,`0`)}generateSpanId(){return this.spanCounter=(this.spanCounter+1)%SPAN_COUNTER_MODULUS,this.spanPrefix+this.spanCounter.toString(16).padStart(4,`0`)}};export{CounterIdGenerator};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{exportTracePayload}from"./trace-payload.mjs";import{ExportResultCode}from"@opentelemetry/core";import{InterfereClient}from"@interfere/javascript";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer";var FetchTraceExporter=class{client;constructor(options){this.client=new InterfereClient({...options,maxRetries:0,fetch:Object.assign(async(url,init)=>globalThis.fetch(url,{...init,keepalive:!0}),globalThis.fetch)})}export(spans,resultCallback){if(spans.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}let payload=JsonTraceSerializer.serializeRequest(spans);exportTracePayload(payload,this.client,resultCallback)}async shutdown(){return Promise.resolve()}async forceFlush(){return Promise.resolve()}};export{FetchTraceExporter};
|
|
@@ -1,36 +1,10 @@
|
|
|
1
1
|
import { IdGenerator } from "@opentelemetry/sdk-trace-base";
|
|
2
2
|
//#region src/internal/server/id-generator.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* IdGenerator hybrid that uses OTel's default `RandomIdGenerator` whenever
|
|
5
|
-
* available and falls back to a counter-based generator inside Next 16's
|
|
6
|
-
* prerender contexts.
|
|
7
|
-
*
|
|
8
|
-
* Why: Next 16's prerender extension throws synchronously on any
|
|
9
|
-
* `Math.random()` / `crypto.getRandomValues()` call inside a Server
|
|
10
|
-
* Component that hasn't first awaited Request data
|
|
11
|
-
* (`next-prerender-random` / `next-prerender-crypto`). OTel's default
|
|
12
|
-
* generator hits `Math.random()` on every span creation, so spans Next
|
|
13
|
-
* opens around prerendered route renders blow up the build.
|
|
14
|
-
*
|
|
15
|
-
* The hybrid path keeps full 128-bit trace IDs / 64-bit span IDs for the
|
|
16
|
-
* common case (SSR, runtime requests, fluid-compute invocations) and only
|
|
17
|
-
* degrades inside prerender — where the alternative is build failure.
|
|
18
|
-
*
|
|
19
|
-
* Inside prerender, IDs are minted from a per-process random prefix
|
|
20
|
-
* (seeded once at construction time, outside any prerender ALS frame) plus
|
|
21
|
-
* a monotonic counter. Counter widths are masked to keep IDs at their
|
|
22
|
-
* spec-mandated lengths even after wrap.
|
|
23
|
-
*/
|
|
3
|
+
/** Generates random telemetry IDs, with counter IDs when runtime randomness is unavailable. */
|
|
24
4
|
export declare class PrerenderSafeIdGenerator implements IdGenerator {
|
|
25
5
|
private readonly random;
|
|
26
|
-
private readonly
|
|
27
|
-
private readonly spanPrefix;
|
|
28
|
-
private traceCounter;
|
|
29
|
-
private spanCounter;
|
|
30
|
-
constructor();
|
|
6
|
+
private readonly fallback;
|
|
31
7
|
generateTraceId(): string;
|
|
32
8
|
generateSpanId(): string;
|
|
33
|
-
private fallbackTraceId;
|
|
34
|
-
private fallbackSpanId;
|
|
35
9
|
}
|
|
36
10
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RandomIdGenerator}from"@opentelemetry/sdk-trace-base";
|
|
1
|
+
import{CounterIdGenerator}from"./counter-id-generator.mjs";import{RandomIdGenerator}from"@opentelemetry/sdk-trace-base";var PrerenderSafeIdGenerator=class{random=new RandomIdGenerator;fallback=new CounterIdGenerator;generateTraceId(){try{return this.random.generateTraceId()}catch{return this.fallback.generateTraceId()}}generateSpanId(){try{return this.random.generateSpanId()}catch{return this.fallback.generateSpanId()}}};export{PrerenderSafeIdGenerator};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UrlRedactionOption } from "@interfere/types/sdk/url-redaction";
|
|
1
2
|
import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
2
3
|
import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
3
4
|
//#region src/internal/server/instrumentation-options.d.ts
|
|
@@ -57,6 +58,17 @@ export interface ServerInstrumentationOptions {
|
|
|
57
58
|
* opt out to avoid double-emit.
|
|
58
59
|
*/
|
|
59
60
|
consoleBridge?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* `urls` controls query and fragment redaction in span and event URL
|
|
63
|
+
* attributes, URL-bearing span names and absolute URLs in exception
|
|
64
|
+
* messages. By default the kit redacts built-in sensitive query values such
|
|
65
|
+
* as `token`; URL credentials are always redacted. The kit's processor
|
|
66
|
+
* redacts while each span ends, so every processor and exporter on the same
|
|
67
|
+
* provider receives the redacted span.
|
|
68
|
+
*/
|
|
69
|
+
privacy?: {
|
|
70
|
+
urls?: UrlRedactionOption;
|
|
71
|
+
};
|
|
60
72
|
/**
|
|
61
73
|
* Application service name. Defaults to `OTEL_SERVICE_NAME`, then OTel's
|
|
62
74
|
* `unknown_service:<executable>` or `unknown_service` when unavailable.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ExportResult } from "@opentelemetry/core";
|
|
2
|
+
import { InterfereClient } from "@interfere/javascript";
|
|
3
|
+
//#region src/internal/server/trace-payload.d.ts
|
|
4
|
+
export declare function exportTracePayload(payload: Uint8Array | null | undefined, client: InterfereClient, resultCallback: (result: ExportResult) => void): void;
|
|
5
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ExportResultCode}from"@opentelemetry/core";import{toError}from"@interfere/types/sdk/errors";import{z}from"zod";function exportTracePayload(payload,client,resultCallback){if(!payload||payload.byteLength===0){resultCallback({code:ExportResultCode.SUCCESS});return}(async()=>{try{let body=z.record(z.string(),z.unknown()).parse(JSON.parse(new TextDecoder().decode(payload)));await client.drain.otlpExport({signal:`traces`,body}),resultCallback({code:ExportResultCode.SUCCESS})}catch(error){resultCallback({code:ExportResultCode.FAILED,error:toError(error)})}})()}export{exportTracePayload};
|
package/dist/package.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var name=`@interfere/next`,version=`12.0
|
|
1
|
+
var name=`@interfere/next`,version=`12.1.0`;export{name,version};
|
package/dist/provider.d.mts
CHANGED
|
@@ -4,6 +4,9 @@ import { ConsentCategory, ConsentInput, ConsentInput as ConsentInput$1, ConsentS
|
|
|
4
4
|
//#region src/provider.d.ts
|
|
5
5
|
interface InterfereProviderProps extends PropsWithChildren {
|
|
6
6
|
consent?: ConsentInput$1 | undefined;
|
|
7
|
+
/** Reports render errors and rethrows to the host boundary. Defaults to true. */
|
|
8
|
+
captureRenderErrors?: boolean;
|
|
9
|
+
/** @deprecated Use captureRenderErrors, which takes precedence when both are set. */
|
|
7
10
|
errorBoundary?: boolean;
|
|
8
11
|
plugins?: PluginOverrides | undefined;
|
|
9
12
|
}
|
|
@@ -15,6 +18,6 @@ interface InterfereProviderProps extends PropsWithChildren {
|
|
|
15
18
|
* (`init()` is client-only); the core provider mounts with null-safe
|
|
16
19
|
* accessors and switches to the real kernel on client hydration.
|
|
17
20
|
*/
|
|
18
|
-
export declare function InterfereProvider({ children, consent, plugins, errorBoundary }: InterfereProviderProps): import("react").JSX.Element;
|
|
21
|
+
export declare function InterfereProvider({ children, consent, plugins, captureRenderErrors, errorBoundary }: InterfereProviderProps): import("react").JSX.Element;
|
|
19
22
|
//#endregion
|
|
20
23
|
export { type ConsentCategory, type ConsentInput, type ConsentState, type GateableCategory, useInterfere, useSession };
|
package/dist/provider.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{getKernelOrNull,subscribeToKernel}from"./instrument-client.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{InterfereProvider as InterfereProvider$1,useInterfere,useSession}from"@interfere/react/provider";import{useSyncExternalStore}from"react";import{jsx}from"react/jsx-runtime";const nullSnapshot=()=>null;function InterfereProvider({children,consent,plugins,errorBoundary}){let kernel=useSyncExternalStore(subscribeToKernel,getKernelOrNull,nullSnapshot);return jsx(InterfereProvider$1,{kernel,...omitUndefined({consent,errorBoundary,plugins}),children})}export{InterfereProvider,useInterfere,useSession};
|
|
1
|
+
"use client";import{getKernelOrNull,subscribeToKernel}from"./instrument-client.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{InterfereProvider as InterfereProvider$1,useInterfere,useSession}from"@interfere/react/provider";import{useSyncExternalStore}from"react";import{jsx}from"react/jsx-runtime";const nullSnapshot=()=>null;function InterfereProvider({children,consent,plugins,captureRenderErrors,errorBoundary}){let kernel=useSyncExternalStore(subscribeToKernel,getKernelOrNull,nullSnapshot);return jsx(InterfereProvider$1,{kernel,...omitUndefined({captureRenderErrors,consent,errorBoundary,plugins}),children})}export{InterfereProvider,useInterfere,useSession};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/next",
|
|
3
|
-
"version": "12.0
|
|
3
|
+
"version": "12.1.0",
|
|
4
4
|
"description": "Build software that never breaks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"error-tracking",
|
|
@@ -96,12 +96,12 @@
|
|
|
96
96
|
"typecheck": "bun check"
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"@interfere/build": "^0.3.
|
|
99
|
+
"@interfere/build": "^0.3.1",
|
|
100
100
|
"@interfere/constants": "^9.2.0",
|
|
101
101
|
"@interfere/helpers": "^9.2.0",
|
|
102
102
|
"@interfere/javascript": "^0.2.1",
|
|
103
|
-
"@interfere/react": "^12.0
|
|
104
|
-
"@interfere/types": "^10.
|
|
103
|
+
"@interfere/react": "^12.1.0",
|
|
104
|
+
"@interfere/types": "^10.3.0",
|
|
105
105
|
"@opentelemetry/api": "^1.9.1",
|
|
106
106
|
"@opentelemetry/api-logs": "^0.222.0",
|
|
107
107
|
"@opentelemetry/baggage-span-processor": "^0.5.0",
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
"@types/react": "19.2.18",
|
|
131
131
|
"@types/react-dom": "19.2.7",
|
|
132
132
|
"@vitejs/plugin-react": "^6.1.1",
|
|
133
|
-
"@vitest/browser": "5.0.
|
|
134
|
-
"@vitest/browser-playwright": "5.0.
|
|
135
|
-
"@vitest/coverage-v8": "5.0.
|
|
133
|
+
"@vitest/browser": "5.0.5",
|
|
134
|
+
"@vitest/browser-playwright": "5.0.5",
|
|
135
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
136
136
|
"msw": "^2.14.6",
|
|
137
137
|
"next": "^16.3.8",
|
|
138
138
|
"playwright": "^1.63.0",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"react-dom": "19.3.0",
|
|
141
141
|
"tsdown": "^0.23.0",
|
|
142
142
|
"typescript": "^7.0.2",
|
|
143
|
-
"vitest": "5.0.
|
|
143
|
+
"vitest": "5.0.5",
|
|
144
144
|
"vitest-browser-react": "2.3.0",
|
|
145
145
|
"webpack": "^5.111.1"
|
|
146
146
|
},
|