@interfere/next 11.0.3 → 11.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/config.d.mts +10 -2
- package/dist/config.mjs +1 -1
- package/dist/internal/build/configure-build.d.mts +3 -5
- package/dist/internal/build/configure-build.mjs +1 -1
- package/dist/internal/build/detect-bundler.d.mts +1 -1
- package/dist/internal/build/detect-bundler.mjs +1 -1
- package/dist/internal/build/pipeline.mjs +1 -1
- package/dist/internal/build/source-maps/guard.d.mts +9 -0
- package/dist/internal/build/source-maps/guard.mjs +1 -0
- package/dist/internal/build/value-injection-loader.d.mts +3 -4
- package/dist/internal/build/value-injection-loader.mjs +2 -2
- package/dist/internal/release-slug.mjs +1 -1
- package/dist/internal/server/instrumentation-options.d.mts +1 -9
- package/dist/package.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,13 +86,13 @@ export default function RootLayout({
|
|
|
86
86
|
|
|
87
87
|
| Variable | Required | Description |
|
|
88
88
|
| --- | --- | --- |
|
|
89
|
-
| `INTERFERE_PUBLIC_KEY` | Yes | Surface
|
|
89
|
+
| `INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Public by design; used for ingestion. |
|
|
90
90
|
| `INTERFERE_API_KEY` | Yes for release metadata | Interfere secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format. Server-side build credential; never expose in browser code. |
|
|
91
91
|
| `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
|
|
92
92
|
|
|
93
93
|
### Migrating From 0.x
|
|
94
94
|
|
|
95
|
-
Upgrade all `@interfere/*` SDK packages together. Keep the env var names, but replace old `int_pub_*` / `interfere_pk_*` values with the dashboard's regional
|
|
95
|
+
Upgrade all `@interfere/*` SDK packages together. Keep the env var names, but replace old `int_pub_*` / `interfere_pk_*` values with the dashboard's regional public key, and replace old `ak_*` / `interfere_ak_*` build keys with the regional Interfere secret key.
|
|
96
96
|
|
|
97
97
|
## Ingest Endpoint
|
|
98
98
|
|
package/dist/config.d.mts
CHANGED
|
@@ -87,8 +87,16 @@ interface ResolvedBuildConfig {
|
|
|
87
87
|
*/
|
|
88
88
|
readonly releaseSlug: ReleaseSlug | null;
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
type InterfereConfigFn = (phase: string, context: {
|
|
91
|
+
defaultConfig: NextConfig;
|
|
92
|
+
}) => NextConfigWithInterfere | Promise<NextConfigWithInterfere> | PromiseLike<NextConfigWithInterfere>;
|
|
93
|
+
type InterfereConfigInput = NextConfigWithInterfere | InterfereConfigFn;
|
|
94
|
+
type ResolvedInterfereConfig<TConfig> = [TConfig] extends [(...args: never[]) => infer R] ? R extends Promise<unknown> ? Promise<NextConfig> : R extends PromiseLike<unknown> ? PromiseLike<NextConfig> : NextConfig : NextConfig;
|
|
95
|
+
type WithInterfereResult<TConfig> = (phase: string, context?: {
|
|
96
|
+
defaultConfig: NextConfig;
|
|
97
|
+
}) => ResolvedInterfereConfig<TConfig>;
|
|
98
|
+
declare function withInterfere<TConfig extends InterfereConfigInput>(upstream?: TConfig): WithInterfereResult<TConfig>;
|
|
99
|
+
declare function withInterfere<TConfig extends InterfereConfigInput>(settings: NextPluginConfig, config: TConfig): WithInterfereResult<TConfig>;
|
|
92
100
|
interface ProductionCompileContext {
|
|
93
101
|
readonly distDir: string;
|
|
94
102
|
readonly projectDir: string;
|
package/dist/config.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`};function reportReleaseDiagnostic(code,input){let diagnostic=diagnoseRelease(code,DIAGNOSTIC_CONTEXT,input),lines=[...diagnostic.details];if(diagnostic.severity===`fatal`){log.fatal(diagnostic.title,lines);return}log.warn(diagnostic.title,lines)}function withInterfere(settingsOrConfig={},maybeConfig){let[settings,nextConfig]=maybeConfig===void 0?[{},settingsOrConfig]:[settingsOrConfig,maybeConfig],{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(settings),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,projectDir,remoteConfigFetch:resolveRemoteConfigFetch(config),values:{
|
|
1
|
+
import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{API_URL_GLOBAL,ENVIRONMENT_GLOBAL,FORCE_ENABLE_GLOBAL,PUBLIC_KEY_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`};function reportReleaseDiagnostic(code,input){let diagnostic=diagnoseRelease(code,DIAGNOSTIC_CONTEXT,input),lines=[...diagnostic.details];if(diagnostic.severity===`fatal`){log.fatal(diagnostic.title,lines);return}log.warn(diagnostic.title,lines)}function isThenable(value){return typeof value==`object`&&!!value&&`then`in value&&typeof value.then==`function`}function withInterfere(settingsOrConfig={},maybeConfig){let[settings,nextConfig]=maybeConfig===void 0?[{},settingsOrConfig]:[settingsOrConfig,maybeConfig];return(phase,context)=>{let resolved=typeof nextConfig==`function`?nextConfig(phase,context??{defaultConfig:{}}):nextConfig;return isThenable(resolved)?Promise.resolve(resolved).then(config=>buildInterfereConfig(phase,settings,config)):buildInterfereConfig(phase,settings,resolved)}}function buildInterfereConfig(phase,settings,nextConfig){let{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(settings,phase),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,nextMajor,projectDir,remoteConfigFetch:resolveRemoteConfigFetch(config,phase),values:{[RELEASE_SLUG_GLOBAL]:releaseSlug,...config.publicKey&&{[PUBLIC_KEY_GLOBAL]:config.publicKey,...apiHost&&{[API_URL_GLOBAL]:apiHost}},...config.environment&&{[ENVIRONMENT_GLOBAL]:config.environment},...forceEnable&&{[FORCE_ENABLE_GLOBAL]:!0}}});if(config.publicKey!==null&&!build.clientInstrumentationApplies)return log.fatal(`Missing instrumentation client`,[`INTERFERE_PUBLIC_KEY is set but no instrumentation-client file was found.`,`Create an instrumentation-client.ts file in your project root or src/ directory.`]);config.publicKey!==null&&warnIfServerInstrumentationMissing(projectDir);let experimental=interfereExperimentalConfig({existing:rest.experimental,instrumentationHook:nextMajor===null||nextMajor<=14,traceMetadata:config.publicKey!==null||forceEnable}),existingHook=compiler?.runAfterProductionCompile,browserSourceMaps=config.apiKey!==null||productionBrowserSourceMaps;return config.apiKey!==null&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),{...rest,...userEnv?{env:userEnv}:{},compiler:{...omitUndefined(compiler??{}),async runAfterProductionCompile(context){existingHook&&await existingHook(context);try{await runBuildReleasePipeline(context,config)}catch(error){if(error instanceof FatalError)throw error;let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere encountered an unexpected error.`,`Build will continue, but this release's data will be ingested, but ignored as finding when generating insights.`,`Error: ${message}`])}}},...omitUndefined({productionBrowserSourceMaps:browserSourceMaps,turbopack:build.turbopack,webpack:build.webpack}),...experimental?{experimental}:{}}}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing,...instrumentationHook?{instrumentationHook:!0}:{},...traceMetadata?{clientTraceMetadata:[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]}:{}};return Object.keys(merged).length>0?merged:void 0}function resolveBrowserApiHost(apiHost,apiUrl){let host=apiHost??apiUrl;if(host!==API_URL)return normalizeHost(host)}function normalizeHost(host){try{return new URL(host).origin}catch{return log.fatal(`Invalid apiHost`,[`interfere.apiHost must be a full URL, e.g. "https://in.yourapp.com".`,`Received: ${host}`])}}function resolveBuildConfig(settings,phase){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(),environment=phase===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,purgeSourceMaps:settings.sourceMaps?.purge??!0,releaseSlug:slug}}function resolveRemoteConfigFetch(config,phase){if(!(phase!==PHASE_PRODUCTION_BUILD||!config.publicKey))return{apiUrl:config.apiUrl,publicKey:config.publicKey}}async function runBuildReleasePipeline(context,config){let{apiKey,buildId,releaseSlug}=config;if(!apiKey)return log.fatal(`Secret key not set`,[`withInterfere() requires a secret key to publish release metadata during production builds.`,`Set the INTERFERE_API_KEY environment variable, or remove withInterfere() from your Next.js config.`,`See: https://interfere.com/docs/sdk/next-js`]);if(!isInterfereApiKey(apiKey))return reportReleaseDiagnostic(`API_KEY_WRONG_PREFIX`,{apiKey});if(!(buildId&&releaseSlug))return log.fatal(`Commit SHA missing`,[`Could not resolve a commit SHA from environment variables or git.`,"Set `INTERFERE_SOURCE_ID`, ensure your CI exposes its git SHA env var (e.g. `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`), or run inside a git checkout."]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey,buildId,releaseSlug});if(!result.ready){log.warn(`Skipping`,[`No source maps found`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config?.org.slug}/surfaces/${result.config?.surface.slug}`,`Release: ${result.release?.destination.slug??`Unknown`}`,`Build: ${result.buildId}`,`Artifacts: ${result.fileCount} source maps`])}catch(error){let message=error instanceof Error?error.message:String(error);reportReleaseDiagnostic(classifyReleasePipelineError(error),{errorMessage:message});return}}export{withInterfere};
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { ServerTracingMode } from "@interfere/types/sdk/server-tracing";
|
|
2
1
|
import { NextConfig } from "next";
|
|
3
2
|
//#region src/internal/build/configure-build.d.ts
|
|
4
3
|
interface InterfereInjectedValues {
|
|
5
4
|
readonly __INTERFERE_API_URL__?: string;
|
|
6
5
|
readonly __INTERFERE_ENVIRONMENT__?: string;
|
|
7
6
|
readonly __INTERFERE_FORCE_ENABLE__?: boolean;
|
|
8
|
-
readonly __INTERFERE_INGEST_URL__?: string;
|
|
9
7
|
readonly __INTERFERE_PUBLIC_KEY__?: string;
|
|
10
8
|
readonly __INTERFERE_RELEASE_SLUG__: string | null;
|
|
11
|
-
readonly __INTERFERE_SERVER_TRACING__?: ServerTracingMode;
|
|
12
9
|
}
|
|
13
10
|
/**
|
|
14
11
|
* Inputs for the loader's build-time `/v2/config` fetch that resolves the
|
|
@@ -23,6 +20,7 @@ interface RemoteConfigFetch {
|
|
|
23
20
|
interface ConfigureBuildInput {
|
|
24
21
|
readonly existingTurbopack: NextConfig["turbopack"] | undefined;
|
|
25
22
|
readonly existingWebpack: NextConfig["webpack"] | undefined;
|
|
23
|
+
readonly nextMajor: number | null;
|
|
26
24
|
readonly projectDir: string;
|
|
27
25
|
readonly remoteConfigFetch?: RemoteConfigFetch | undefined;
|
|
28
26
|
readonly values: InterfereInjectedValues;
|
|
@@ -32,6 +30,6 @@ interface ConfigureBuildOutput {
|
|
|
32
30
|
readonly turbopack: NextConfig["turbopack"] | undefined;
|
|
33
31
|
readonly webpack: NextConfig["webpack"] | undefined;
|
|
34
32
|
}
|
|
35
|
-
declare function configureBuild({ existingWebpack, existingTurbopack, projectDir, remoteConfigFetch, values }: ConfigureBuildInput): ConfigureBuildOutput;
|
|
33
|
+
declare function configureBuild({ existingWebpack, existingTurbopack, nextMajor, projectDir, remoteConfigFetch, values }: ConfigureBuildInput): ConfigureBuildOutput;
|
|
36
34
|
//#endregion
|
|
37
|
-
export {
|
|
35
|
+
export { RemoteConfigFetch, configureBuild };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const
|
|
1
|
+
import{omitUndefined}from"@interfere/helpers/omit-undefined";import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_EXTENSIONS=[`ts`,`tsx`,`js`,`jsx`];function instrumentationCandidates(base){return[``,`src/`].flatMap(dir=>INSTRUMENTATION_EXTENSIONS.map(ext=>`${dir}${base}.${ext}`))}const LOADER_FILE_CANDIDATES=[`ts`,`js`,`mjs`,`cjs`].map(ext=>`value-injection-loader.${ext}`);function configureBuild({existingWebpack,existingTurbopack,nextMajor,projectDir,remoteConfigFetch,values}){let clientPath=resolveFirstExisting(projectDir,instrumentationCandidates(`instrumentation-client`)),targets=[clientPath,resolveFirstExisting(projectDir,instrumentationCandidates(`instrumentation`))].filter(path=>path!==null),hasMetadata=values.__INTERFERE_RELEASE_SLUG__!==null||values.__INTERFERE_PUBLIC_KEY__!==void 0,injectionApplies=targets.length>0&&hasMetadata;return{clientInstrumentationApplies:!!clientPath&&hasMetadata,webpack:buildWebpackHook(existingWebpack,targets,values,remoteConfigFetch,injectionApplies),turbopack:buildTurbopackConfig(existingTurbopack,targets,values,remoteConfigFetch,injectionApplies,nextMajor)}}function buildWebpackHook(existingWebpack,targets,values,remoteConfigFetch,injectionApplies){if(!injectionApplies)return existingWebpack;let normalizedTargets=new Set(targets.map(path=>normalize(path))),injectionLoaderPath=resolveLoaderPath();return(webpackConfig,options)=>{let outputConfig=existingWebpack?existingWebpack(webpackConfig,options):webpackConfig,mutableConfig=outputConfig;return mutableConfig.module??={},mutableConfig.module.rules??=[],mutableConfig.module.rules.push({test:resource=>!!resource&&normalizedTargets.has(normalize(resource)),use:[{loader:injectionLoaderPath,options:omitUndefined({serializedValues:JSON.stringify(values),serializedFetch:remoteConfigFetch?JSON.stringify(remoteConfigFetch):void 0})}]}),outputConfig}}function buildTurbopackConfig(existingTurbopack,targets,values,remoteConfigFetch,injectionApplies,nextMajor){let baseConfig=existingTurbopack??{},debugIds=nextMajor===null||nextMajor>=16?baseConfig.debugIds??!0:baseConfig.debugIds;if(!injectionApplies)return omitUndefined({...baseConfig,debugIds});let rules={...baseConfig.rules},loaderPath=resolveLoaderPath();for(let target of targets){let ruleKey=`**/${basename(target)}`;rules[ruleKey]=appendTurbopackLoader(rules[ruleKey],{loader:loaderPath,options:omitUndefined({serializedValues:JSON.stringify(values),serializedFetch:remoteConfigFetch?JSON.stringify(remoteConfigFetch):void 0})})}return omitUndefined({...baseConfig,debugIds,rules})}function appendTurbopackLoader(existing,loader){let rule=existing??{},loaders=Array.isArray(rule.loaders)?rule.loaders:[];return{...rule,loaders:[...loaders,loader]}}function resolveFirstExisting(projectDir,candidates){for(let candidate of candidates){let filePath=resolve(projectDir,candidate);if(existsSync(filePath))return filePath}return null}function resolveLoaderPath(){let directory=resolve(dirname(fileURLToPath(import.meta.url)));for(let candidate of LOADER_FILE_CANDIDATES){let filePath=join(directory,candidate);if(existsSync(filePath))return filePath}return join(directory,`value-injection-loader.js`)}export{configureBuild};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ManifestBundler } from "@interfere/types/data/source-maps";
|
|
2
2
|
//#region src/internal/build/detect-bundler.d.ts
|
|
3
3
|
type NextBuildBundler = Extract<ManifestBundler, "webpack" | "turbopack">;
|
|
4
|
-
declare function detectActiveBundler(env?: Readonly<Record<string, string | undefined>>, argv?: readonly string[]): NextBuildBundler;
|
|
4
|
+
declare function detectActiveBundler(env?: Readonly<Record<string, string | undefined>>, argv?: readonly string[], nextMajor?: number | null): NextBuildBundler;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { NextBuildBundler, detectActiveBundler };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const TURBOPACK_FLAGS=[`--turbo`,`--turbopack`];function detectActiveBundler(env=process.env,argv=process.argv){return env.TURBOPACK===`1`||TURBOPACK_FLAGS.some(flag=>argv.includes(flag))?`turbopack`:`webpack`}export{detectActiveBundler};
|
|
1
|
+
const TURBOPACK_FLAGS=[`--turbo`,`--turbopack`];function detectActiveBundler(env=process.env,argv=process.argv,nextMajor=null){return argv.includes(`--webpack`)?`webpack`:env.TURBOPACK===`1`||TURBOPACK_FLAGS.some(flag=>argv.includes(flag))||nextMajor!==null&&nextMajor>=16?`turbopack`:`webpack`}export{detectActiveBundler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{uploadSourceMaps}from"./source-maps/upload.mjs";import{cleanupSourceMaps}from"./source-maps/index.mjs";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{HTTPClient,Interfere}from"@interfere/sdk";async function timed(fn){let start=performance.now();return[await fn(),Math.round(performance.now()-start)]}async function runBuildPipeline(context,metadata){let{apiUrl,apiKey}=metadata,httpClient=new HTTPClient;httpClient.addHook(`beforeRequest`,request=>{let nextRequest=new Request(request);return nextRequest.headers.set(`Authorization`,`Bearer ${apiKey}`),nextRequest});let sdk=new Interfere({serverURL:apiUrl,httpClient}),security={apiKey},start=performance.now(),bundler=detectActiveBundler(),[{discovered,config},discoverMs]=await timed(async()=>{let[discovered,config]=await Promise.all([discoverSourceMaps({bundler,projectDir:context.projectDir,distDir:context.distDir}),sdk.releases.getConfig(security)]);return{discovered,config}});if(discovered.files.length===0)return{ready:!1,reason:`no_source_maps`,fileCount:0};let releaseRequest=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config),[release,createReleaseMs]=await timed(()=>sdk.releases.create(security,releaseRequest)),releaseSlug=releaseSlugSchema.parse(release.destination.slug),buildId=release.build.hash??metadata.buildId,[{totalBytes},uploadMs]=await timed(()=>uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug})),[,preflightMs]=await timed(()=>sdk.releases.preflight(security,{releaseSlug})),[,cleanupMs]=await timed(async()=>{metadata.purgeSourceMaps&&await cleanupSourceMaps(discovered.files)}),timing={discover:discoverMs,createRelease:createReleaseMs,upload:uploadMs,preflight:preflightMs,cleanup:cleanupMs,total:Math.round(performance.now()-start),fileCount:discovered.files.length,totalBytes};return{ready:!0,fileCount:discovered.files.length,release:{...release,destination:{...release.destination,slug:releaseSlug}},config,buildId,timing}}export{runBuildPipeline};
|
|
1
|
+
import{readNextMajorVersion}from"../setup-warnings.mjs";import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{assertClientChunksAreMapped}from"./source-maps/guard.mjs";import{uploadSourceMaps}from"./source-maps/upload.mjs";import{cleanupSourceMaps}from"./source-maps/index.mjs";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{HTTPClient,Interfere}from"@interfere/sdk";async function timed(fn){let start=performance.now();return[await fn(),Math.round(performance.now()-start)]}async function runBuildPipeline(context,metadata){let{apiUrl,apiKey}=metadata,httpClient=new HTTPClient;httpClient.addHook(`beforeRequest`,request=>{let nextRequest=new Request(request);return nextRequest.headers.set(`Authorization`,`Bearer ${apiKey}`),nextRequest});let sdk=new Interfere({serverURL:apiUrl,httpClient}),security={apiKey},start=performance.now(),bundler=detectActiveBundler(process.env,process.argv,readNextMajorVersion(context.projectDir)),[{discovered,config},discoverMs]=await timed(async()=>{let[discovered,config]=await Promise.all([discoverSourceMaps({bundler,projectDir:context.projectDir,distDir:context.distDir}),sdk.releases.getConfig(security)]);return{discovered,config}});if(discovered.files.length===0)return{ready:!1,reason:`no_source_maps`,fileCount:0};await assertClientChunksAreMapped({projectDir:context.projectDir,distDir:context.distDir,discovered:discovered.files});let releaseRequest=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config),[release,createReleaseMs]=await timed(()=>sdk.releases.create(security,releaseRequest)),releaseSlug=releaseSlugSchema.parse(release.destination.slug),buildId=release.build.hash??metadata.buildId,[{totalBytes},uploadMs]=await timed(()=>uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug})),[,preflightMs]=await timed(()=>sdk.releases.preflight(security,{releaseSlug})),[,cleanupMs]=await timed(async()=>{metadata.purgeSourceMaps&&await cleanupSourceMaps(discovered.files)}),timing={discover:discoverMs,createRelease:createReleaseMs,upload:uploadMs,preflight:preflightMs,cleanup:cleanupMs,total:Math.round(performance.now()-start),fileCount:discovered.files.length,totalBytes};return{ready:!0,fileCount:discovered.files.length,release:{...release,destination:{...release.destination,slug:releaseSlug}},config,buildId,timing}}export{runBuildPipeline};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SourceMapFile } from "./discover.mjs";
|
|
2
|
+
//#region src/internal/build/source-maps/guard.d.ts
|
|
3
|
+
declare function assertClientChunksAreMapped(opts: {
|
|
4
|
+
projectDir: string;
|
|
5
|
+
distDir: string;
|
|
6
|
+
discovered: readonly SourceMapFile[];
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { assertClientChunksAreMapped };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{log}from"../../logger.mjs";import{resolveDistDir,walkDistTrees}from"./paths.mjs";import{readFile}from"node:fs/promises";import{readDebugIdFromJs}from"@interfere/types/data/source-maps";async function assertClientChunksAreMapped(opts){if(opts.discovered.some(file=>file.chunkUrl.includes(`static/chunks`)))return;let clientChunks=(await walkDistTrees(resolveDistDir(opts.projectDir,opts.distDir),`.js`)).filter(path=>path.replaceAll(`\\`,`/`).includes(`/static/chunks/`)),stampedCount=(await Promise.all(clientChunks.map(async jsAbs=>readDebugIdFromJs(await readFile(jsAbs,`utf8`).catch(()=>``))!==null))).filter(Boolean).length;stampedCount!==0&&log.fatal(`Client source maps were not uploaded`,[`${stampedCount} client chunk(s) carry debug IDs but no source maps were uploaded — browser errors from this release won't symbolicate.`,`A coexisting build tool likely deleted them before withInterfere() ran; make withInterfere() the outer wrapper so its discovery runs first, or disable that tool's post-upload map deletion.`])}export{assertClientChunksAreMapped};
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { InterfereInjectedValues, RemoteConfigFetch } from "./configure-build.mjs";
|
|
2
1
|
//#region src/internal/build/value-injection-loader.d.ts
|
|
2
|
+
type BakedGlobals = Record<string, unknown>;
|
|
3
3
|
interface ValueInjectionLoaderOptions {
|
|
4
|
-
readonly fetch?: RemoteConfigFetch;
|
|
5
4
|
readonly serializedFetch?: string;
|
|
6
5
|
readonly serializedValues?: string;
|
|
7
|
-
readonly values?: Partial<InterfereInjectedValues>;
|
|
8
6
|
}
|
|
9
7
|
interface LoaderThis<T> {
|
|
10
8
|
async(): (error: Error | null, content?: string) => void;
|
|
11
9
|
getOptions(): T;
|
|
12
10
|
}
|
|
13
11
|
declare function valueInjectionLoader(this: LoaderThis<ValueInjectionLoaderOptions>, userCode: string): void;
|
|
12
|
+
declare function remoteGlobals(raw: unknown): BakedGlobals;
|
|
14
13
|
//#endregion
|
|
15
|
-
export { ValueInjectionLoaderOptions, valueInjectionLoader as default };
|
|
14
|
+
export { ValueInjectionLoaderOptions, valueInjectionLoader as default, remoteGlobals };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{SERVER_TRACING_GLOBAL}from"@interfere/types/sdk/server-tracing";import{PLUGINS_GLOBAL}from"@interfere/types/sdk/remote-config";import{LOG_SOURCING_GLOBAL}from"@interfere/types/sdk/log-sourcing";let remoteConfigPromise=null;function valueInjectionLoader(userCode){let options=this.getOptions(),callback=this.async(),values=resolveValues(options);resolveRemoteConfig(resolveFetch(options)).then(
|
|
2
|
-
`)}\n${userCode}`}function resolveRemoteConfig(fetchOptions){return fetchOptions?(remoteConfigPromise??=fetchRemoteConfig(fetchOptions),remoteConfigPromise):Promise.resolve(
|
|
1
|
+
import{omitUndefined}from"@interfere/helpers/omit-undefined";import{INGEST_URL_GLOBAL}from"@interfere/types/sdk/injected-globals";import{SERVER_TRACING_GLOBAL}from"@interfere/types/sdk/server-tracing";import{PLUGINS_GLOBAL,remoteConfigSchema}from"@interfere/types/sdk/remote-config";import{LOG_SOURCING_GLOBAL}from"@interfere/types/sdk/log-sourcing";let remoteConfigPromise=null;function valueInjectionLoader(userCode){let options=this.getOptions(),callback=this.async(),values=resolveValues(options);resolveRemoteConfig(resolveFetch(options)).then(globals=>{callback(null,render({...values,...globals},userCode))}).catch(()=>callback(null,render(values,userCode)))}function remoteGlobals(raw){if(!isRecord(raw))return{};let{serverTracing,logSourcing,ingestUrl,plugins}=raw,{shape}=remoteConfigSchema;return omitUndefined({[SERVER_TRACING_GLOBAL]:shape.serverTracing.catch(void 0).parse(serverTracing),[LOG_SOURCING_GLOBAL]:shape.logSourcing.catch(void 0).parse(logSourcing),[INGEST_URL_GLOBAL]:shape.ingestUrl.catch(void 0).parse(ingestUrl),[PLUGINS_GLOBAL]:shape.plugins.partial().optional().catch(void 0).parse(plugins)})}function render(values,userCode){let lines=Object.entries(values).map(([key,value])=>`globalThis[${JSON.stringify(key)}] = ${JSON.stringify(value)};`);return lines.length===0?userCode:`${lines.join(`
|
|
2
|
+
`)}\n${userCode}`}function resolveRemoteConfig(fetchOptions){return fetchOptions?(remoteConfigPromise??=fetchRemoteConfig(fetchOptions),remoteConfigPromise):Promise.resolve({})}async function fetchRemoteConfig(fetchOptions){try{let url=`${fetchOptions.apiUrl}/v2/config?pk=${encodeURIComponent(fetchOptions.publicKey)}`,response=await fetch(url,{method:`GET`,headers:{"content-type":`application/json`},signal:AbortSignal.timeout(3e3)});return response.ok?remoteGlobals(await response.json()):{}}catch{return{}}}function parseJson(text){if(typeof text==`string`)try{return JSON.parse(text)}catch{return}}function resolveFetch(options){let parsed=parseJson(options.serializedFetch);return isRemoteConfigFetch(parsed)?parsed:null}function isRemoteConfigFetch(value){return isRecord(value)&&typeof value.apiUrl==`string`&&typeof value.publicKey==`string`}function resolveValues(options){let parsed=parseJson(options.serializedValues);return isRecord(parsed)?parsed:{}}function isRecord(value){return typeof value==`object`&&!!value&&!Array.isArray(value)}export{valueInjectionLoader as default,remoteGlobals};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";function readBakedReleaseSlug(){let value=globalThis
|
|
1
|
+
import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";function readBakedReleaseSlug(){let value=globalThis[RELEASE_SLUG_GLOBAL];if(typeof value!=`string`)return null;let parsed=releaseSlugSchema.safeParse(value);return parsed.success?parsed.data:null}function resolveCommitSha(){return readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
|
|
@@ -22,15 +22,7 @@ import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
|
22
22
|
* them at all.
|
|
23
23
|
*/
|
|
24
24
|
interface ServerInstrumentationOptions {
|
|
25
|
-
/**
|
|
26
|
-
* @internal
|
|
27
|
-
*
|
|
28
|
-
* Extra log-record processors fanned into the LoggerProvider's
|
|
29
|
-
* processor list. Used by `@interfere/observability` for internal-only
|
|
30
|
-
* dual-write to BetterStack from `interfere/homepage` +
|
|
31
|
-
* `interfere/dashboard`. Customers don't get a fan-out hook on the
|
|
32
|
-
* SDK surface; this is a private bridge for our own dogfood apps.
|
|
33
|
-
*/
|
|
25
|
+
/** @internal */
|
|
34
26
|
_internalAdditionalLogRecordProcessors?: LogRecordProcessor[];
|
|
35
27
|
/**
|
|
36
28
|
* @internal
|
package/dist/package.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var name=`@interfere/next`,version=`11.0.
|
|
1
|
+
var name=`@interfere/next`,version=`11.0.4`;export{name,version};
|