@interfere/next 11.1.0 → 11.2.1

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/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{API_URL_GLOBAL,BUILD_MARKER_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 interfereInjectedValues(config,apiHost,forceEnable){let releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug;return{[RELEASE_SLUG_GLOBAL]:releaseSlug,[BUILD_MARKER_GLOBAL]:!0,...config.publicKey&&{[PUBLIC_KEY_GLOBAL]:config.publicKey,...apiHost&&{[API_URL_GLOBAL]:apiHost}},...config.environment&&{[ENVIRONMENT_GLOBAL]:config.environment},...forceEnable&&{[FORCE_ENABLE_GLOBAL]:!0}}}function interfereCompilerConfig(compiler,config){let existingHook=compiler?.runAfterProductionCompile;return 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}`])}}})}function resolveBrowserSourceMaps(config,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`."]),config.apiKey!==null||productionBrowserSourceMaps}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,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,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,...userEnv?{env:userEnv}:{},compiler:interfereCompilerConfig(compiler,config),productionBrowserSourceMaps:resolveBrowserSourceMaps(config,productionBrowserSourceMaps),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})}}export{withInterfere};
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{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{API_URL_GLOBAL,BUILD_MARKER_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 interfereInjectedValues(config,apiHost,forceEnable){let releaseSlug=forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug;return{[RELEASE_SLUG_GLOBAL]:releaseSlug,[BUILD_MARKER_GLOBAL]:!0,...config.publicKey&&{[PUBLIC_KEY_GLOBAL]:config.publicKey,...apiHost&&{[API_URL_GLOBAL]:apiHost}},...config.environment&&{[ENVIRONMENT_GLOBAL]:config.environment},...forceEnable&&{[FORCE_ENABLE_GLOBAL]:!0}}}function interfereCompilerConfig(compiler,config){let existingHook=compiler?.runAfterProductionCompile;return 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}`])}}})}function resolveBrowserSourceMaps(config,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`."]),config.apiKey!==null||productionBrowserSourceMaps}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,nextMajor=readNextMajorVersion(projectDir),build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,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,...userEnv?{env:userEnv}:{},compiler:interfereCompilerConfig(compiler,config),productionBrowserSourceMaps:resolveBrowserSourceMaps(config,productionBrowserSourceMaps),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(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,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})}}export{withInterfere};
@@ -8,8 +8,8 @@ import { InterfereOtelKit } from "./instrumentation.mjs";
8
8
  * Next.js compiles `instrumentation.ts` for the Edge runtime.
9
9
  *
10
10
  * The Node entry's pipeline (`NodeTracerProvider`, `AsyncLocalStorage`, undici,
11
- * console bridge, `node:child_process`/`node:crypto` release-slug derivation)
12
- * can't run on edge, so this builds an edge-safe pipeline instead: a
11
+ * console bridge) can't run on edge, so this builds an edge-safe pipeline
12
+ * instead: a
13
13
  * `BasicTracerProvider` + `SimpleSpanProcessor` exporting via {@link FetchTraceExporter}.
14
14
  * Under `serverTracing: "drain"` the exporter is omitted and traces come from
15
15
  * the platform trace drain instead — the provider is still registered so
@@ -0,0 +1,4 @@
1
+ //#region src/internal/build/source-maps/concurrency.d.ts
2
+ declare function mapWithConcurrency<T, R>(items: readonly T[], concurrency: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
3
+ //#endregion
4
+ export { mapWithConcurrency };
@@ -0,0 +1 @@
1
+ async function mapWithConcurrency(items,concurrency,fn){let results=Array(items.length),pending=items.entries();async function worker(){for(let[index,item]of pending)results[index]=await fn(item,index)}return await Promise.all(Array.from({length:Math.max(1,Math.min(concurrency,items.length))},()=>worker())),results}export{mapWithConcurrency};
@@ -1 +1 @@
1
- import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash}from"node:crypto";import{readFile}from"node:fs/promises";const DEBUG_ID_RE=/\/\/[#@]\s*debugId=([0-9a-f-]+)\s*$/m;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`)]),debugIdToChunkUrl=new Map;await Promise.all(jsPaths.map(async jsAbs=>{let content=await readFile(jsAbs,`utf8`).catch(()=>null);if(content===null)return;let debugId=DEBUG_ID_RE.exec(content)?.[1];if(!debugId)return;let chunkUrl=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir);debugIdToChunkUrl.has(debugId)||debugIdToChunkUrl.set(debugId,chunkUrl)}));let loaded=(await Promise.all(mapPaths.map(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.debugId==`string`&&parsed.debugId.length>0&&(debugId=parsed.debugId)}catch{return null}if(!debugId)return null;let chunkUrl=debugIdToChunkUrl.get(debugId);return chunkUrl?{absolute:mapAbs,path:toPublicPath(relative(opts.projectDir,mapAbs),relDistDir),content,hash:createHash(`sha256`).update(content).digest(`hex`),debugId,chunkUrl}:null}))).filter(f=>f!==null);return{files:loaded,sourceFileCount:loaded.length}}export{discoverTurbopack};
1
+ import{mapWithConcurrency}from"./concurrency.mjs";import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash}from"node:crypto";import{open,readFile,stat}from"node:fs/promises";const DEBUG_ID_RE=/\/\/[#@]\s*debugId=([0-9a-f-]+)\s*$/m,DEBUG_ID_TAIL_BYTES=65536;async function readChunkDebugId(jsAbs){let fromTail=await readTailDebugId(jsAbs).catch(()=>null);if(fromTail!==null)return fromTail;let content=await readFile(jsAbs,`utf8`).catch(()=>null);return content===null?null:DEBUG_ID_RE.exec(content)?.[1]??null}async function readTailDebugId(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 DEBUG_ID_RE.exec(buf.subarray(0,bytesRead).toString(`utf8`))?.[1]??null}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`)]),debugIdToChunkUrl=new Map;await mapWithConcurrency(jsPaths,16,async jsAbs=>{let debugId=await readChunkDebugId(jsAbs);if(!debugId)return;let chunkUrl=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir);debugIdToChunkUrl.has(debugId)||debugIdToChunkUrl.set(debugId,chunkUrl)});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.debugId==`string`&&parsed.debugId.length>0&&(debugId=parsed.debugId)}catch{return null}if(!debugId)return null;let chunkUrl=debugIdToChunkUrl.get(debugId);return chunkUrl?{absolute:mapAbs,path:toPublicPath(relative(opts.projectDir,mapAbs),relDistDir),content,hash:createHash(`sha256`).update(content).digest(`hex`),debugId,chunkUrl}:null})).filter(f=>f!==null);return{files:loaded,sourceFileCount:loaded.length}}export{discoverTurbopack};
@@ -1 +1 @@
1
- async function uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug}){let filesByPath=new Map(discovered.files.map(f=>[f.path,f])),signRequest={files:discovered.files.map(file=>({path:file.path,sizeBytes:byteLengthOf(file.content),...extractRichness(file.content)}))},signResponse=await postJson(httpClient,new URL(`/v1/releases/${encodeURIComponent(releaseSlug)}/source-maps/sign`,apiUrl),signRequest),totalBytes=0;await mapWithConcurrency(signResponse.uploads,12,async upload=>{let file=filesByPath.get(upload.path);if(!file)throw Error(`Sign response referenced unknown path "${upload.path}"`);totalBytes+=byteLengthOf(file.content),await putToR2(upload.presignedUrl,file.content)});let completeRequest={files:discovered.files.map(file=>({path:file.path,hash:file.hash,debugId:file.debugId,chunkUrl:file.chunkUrl})),sourceFileCount:discovered.sourceFileCount,bundler};return{fileCount:(await postJson(httpClient,new URL(`/v1/releases/${encodeURIComponent(releaseSlug)}/source-maps/complete`,apiUrl),completeRequest)).fileCount,totalBytes}}function byteLengthOf(content){return new TextEncoder().encode(content).byteLength}const EMPTY_RICHNESS={hasSourcesContent:!1,hasNames:!1,hasFile:!1,mappingsPresent:!1};function leafRichness(map){let sourcesContent=map.sourcesContent,names=map.names,file=map.file,mappings=map.mappings;return{hasSourcesContent:Array.isArray(sourcesContent)&&sourcesContent.length>0,hasNames:Array.isArray(names)&&names.length>0,hasFile:typeof file==`string`&&file.length>0,mappingsPresent:typeof mappings==`string`&&mappings.length>0}}function extractRichness(content){let parsed;try{parsed=JSON.parse(content)}catch{return EMPTY_RICHNESS}if(!(parsed&&typeof parsed==`object`))return EMPTY_RICHNESS;let top=parsed,sections=top.sections;if(!Array.isArray(sections))return leafRichness(top);let merged={hasSourcesContent:!1,hasNames:!1,hasFile:typeof top.file==`string`&&top.file.length>0,mappingsPresent:!1};for(let section of sections){let inner=section?.map;if(!(inner&&typeof inner==`object`))continue;let r=leafRichness(inner);merged.hasSourcesContent||=r.hasSourcesContent,merged.hasNames||=r.hasNames,merged.mappingsPresent||=r.mappingsPresent}return merged}async function postJson(httpClient,url,body){let response=await httpClient.request(new Request(url,{method:`POST`,headers:{"content-type":`application/json`,accept:`application/json`},body:JSON.stringify(body)}));if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`Source-map API request failed: POST ${url.pathname} -> ${response.status} ${detail}`)}return await response.json()}async function putToR2(presignedUrl,content){let response=await fetch(presignedUrl,{method:`PUT`,headers:{"content-type":`application/json`},body:content});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`Release metadata file upload failed: ${response.status} ${detail}`)}}async function mapWithConcurrency(items,concurrency,fn){let cursor=0;async function worker(){for(;;){let i=cursor++;if(i>=items.length)return;let item=items[i];if(item===void 0)return;await fn(item,i)}}await Promise.all(Array.from({length:Math.min(concurrency,items.length)},()=>worker()))}export{extractRichness,uploadSourceMaps};
1
+ import{mapWithConcurrency}from"./concurrency.mjs";async function uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug}){let filesByPath=new Map(discovered.files.map(f=>[f.path,f])),signRequest={files:discovered.files.map(file=>({path:file.path,sizeBytes:byteLengthOf(file.content),...extractRichness(file.content)}))},signResponse=await postJson(httpClient,new URL(`/v1/releases/${encodeURIComponent(releaseSlug)}/source-maps/sign`,apiUrl),signRequest),totalBytes=0;await mapWithConcurrency(signResponse.uploads,64,async upload=>{let file=filesByPath.get(upload.path);if(!file)throw Error(`Sign response referenced unknown path "${upload.path}"`);totalBytes+=byteLengthOf(file.content),await putToR2(upload.presignedUrl,file.content)});let completeRequest={files:discovered.files.map(file=>({path:file.path,hash:file.hash,debugId:file.debugId,chunkUrl:file.chunkUrl})),sourceFileCount:discovered.sourceFileCount,bundler};return{fileCount:(await postJson(httpClient,new URL(`/v1/releases/${encodeURIComponent(releaseSlug)}/source-maps/complete`,apiUrl),completeRequest)).fileCount,totalBytes}}function byteLengthOf(content){return new TextEncoder().encode(content).byteLength}const EMPTY_RICHNESS={hasSourcesContent:!1,hasNames:!1,hasFile:!1,mappingsPresent:!1};function leafRichness(map){let sourcesContent=map.sourcesContent,names=map.names,file=map.file,mappings=map.mappings;return{hasSourcesContent:Array.isArray(sourcesContent)&&sourcesContent.length>0,hasNames:Array.isArray(names)&&names.length>0,hasFile:typeof file==`string`&&file.length>0,mappingsPresent:typeof mappings==`string`&&mappings.length>0}}function extractRichness(content){let parsed;try{parsed=JSON.parse(content)}catch{return EMPTY_RICHNESS}if(!(parsed&&typeof parsed==`object`))return EMPTY_RICHNESS;let top=parsed,sections=top.sections;if(!Array.isArray(sections))return leafRichness(top);let merged={hasSourcesContent:!1,hasNames:!1,hasFile:typeof top.file==`string`&&top.file.length>0,mappingsPresent:!1};for(let section of sections){let inner=section?.map;if(!(inner&&typeof inner==`object`))continue;let r=leafRichness(inner);merged.hasSourcesContent||=r.hasSourcesContent,merged.hasNames||=r.hasNames,merged.mappingsPresent||=r.mappingsPresent}return merged}async function postJson(httpClient,url,body){let response=await httpClient.request(new Request(url,{method:`POST`,headers:{"content-type":`application/json`,accept:`application/json`},body:JSON.stringify(body)}));if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`Source-map API request failed: POST ${url.pathname} -> ${response.status} ${detail}`)}return await response.json()}async function putToR2(presignedUrl,content){let response=await fetch(presignedUrl,{method:`PUT`,headers:{"content-type":`application/json`},body:content});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`Release metadata file upload failed: ${response.status} ${detail}`)}}export{extractRichness,uploadSourceMaps};
@@ -1,6 +1,7 @@
1
1
  import { ReleaseSlug } from "@interfere/types/releases/slug";
2
+ import { RunGitCommand } from "@interfere/types/sdk/github-source";
2
3
  //#region src/internal/release-slug.d.ts
3
- declare function resolveReleaseSlug(): {
4
+ declare function resolveReleaseSlug(runGit?: RunGitCommand): {
4
5
  commitSha: string | null;
5
6
  slug: ReleaseSlug | null;
6
7
  };
@@ -1 +1 @@
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{resolveReleaseCommitSha}from"@interfere/types/sdk/github-source";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 resolveReleaseCommitSha(process.env,runGitCommand)}function resolveReleaseSlug(){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
1
+ import{deriveReleaseSlug,releaseSlugSchema}from"@interfere/types/releases/slug";import{RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{resolveReleaseCommitSha}from"@interfere/types/sdk/github-source";const noGit=()=>null;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 resolveReleaseSlug(runGit=noGit){let baked=readBakedReleaseSlug();if(baked)return{commitSha:null,slug:baked};let commitSha=resolveReleaseCommitSha(process.env,runGit);return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
@@ -13,6 +13,11 @@
13
13
  * recursively emit forever). Failures are bounded-rate logged via the
14
14
  * original `console.error` so a broken backend doesn't drown the
15
15
  * customer's logs in bridge-failure messages.
16
+ *
17
+ * Serialization is deferred to a microtask and bodies are capped at
18
+ * {@link MAX_BODY_CHARS} characters, so the synchronous `console.*`
19
+ * call only captures the active context; an arg mutated before the
20
+ * microtask runs is serialized in its mutated state.
16
21
  */
17
22
  declare function bridgeConsoleToOtel(): () => void;
18
23
  //#endregion
@@ -1 +1 @@
1
- import{context,trace}from"@opentelemetry/api";import{logs}from"@opentelemetry/api-logs";const CONSOLE_METHODS=[`debug`,`log`,`info`,`warn`,`error`],SEVERITY={debug:5,log:9,info:9,warn:13,error:17};function stringify(value){if(typeof value==`string`)return value;if(value instanceof Error)return value.stack??`${value.name}: ${value.message}`;try{return JSON.stringify(value)}catch{return String(value)}}function extractException(args){for(let arg of args)if(arg instanceof Error)return arg}function buildExceptionAttributes(args){let exception=extractException(args);if(!exception)return;let attrs={"exception.type":exception.name,"exception.message":exception.message};return exception.stack&&(attrs[`exception.stacktrace`]=exception.stack),attrs}function bridgeConsoleToOtel(){let logger=logs.getLogger(`@interfere/next/console`),emitting=!1,bridgeFailures=0,original={debug:console.debug,log:console.log,info:console.info,warn:console.warn,error:console.error};function emitLog(orig,severity,method,body,activeContext,activeSpan,attributes){if(!emitting){emitting=!0;try{logger.emit({severityNumber:severity,severityText:method.toUpperCase(),body,...activeSpan?{context:activeContext}:{},...attributes?{attributes}:{}})}catch(error){bridgeFailures+=1,bridgeFailures%100==1&&orig.call(console,`[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`,error)}finally{emitting=!1}}}for(let method of CONSOLE_METHODS){let orig=original[method],severity=SEVERITY[method];console[method]=(...args)=>{orig.apply(console,args);let body=args.map(stringify).join(` `),activeContext=context.active(),activeSpan=trace.getSpan(activeContext),attributes=buildExceptionAttributes(args);queueMicrotask(()=>{emitLog(orig,severity,method,body,activeContext,activeSpan,attributes)})}}return()=>{for(let method of CONSOLE_METHODS)console[method]=original[method]}}export{bridgeConsoleToOtel};
1
+ import{context,trace}from"@opentelemetry/api";import{logs}from"@opentelemetry/api-logs";const CONSOLE_METHODS=[`debug`,`log`,`info`,`warn`,`error`],SEVERITY={debug:5,log:9,info:9,warn:13,error:17},MAX_BODY_CHARS=8192;function buildBody(args){let body=args.map(stringify).join(` `);return body.length>MAX_BODY_CHARS?`${body.slice(0,MAX_BODY_CHARS)}…`:body}function stringify(value){if(typeof value==`string`)return value;if(value instanceof Error)return value.stack??`${value.name}: ${value.message}`;try{return JSON.stringify(value)}catch{return String(value)}}function extractException(args){for(let arg of args)if(arg instanceof Error)return arg}function buildExceptionAttributes(args){let exception=extractException(args);if(!exception)return;let attrs={"exception.type":exception.name,"exception.message":exception.message};return exception.stack&&(attrs[`exception.stacktrace`]=exception.stack),attrs}function bridgeConsoleToOtel(){let logger=logs.getLogger(`@interfere/next/console`),emitting=!1,bridgeFailures=0,original={debug:console.debug,log:console.log,info:console.info,warn:console.warn,error:console.error};function emitLog(orig,severity,method,args,activeContext,activeSpan){if(!emitting){emitting=!0;try{let attributes=buildExceptionAttributes(args);logger.emit({severityNumber:severity,severityText:method.toUpperCase(),body:buildBody(args),...activeSpan?{context:activeContext}:{},...attributes?{attributes}:{}})}catch(error){bridgeFailures+=1,bridgeFailures%100==1&&orig.call(console,`[interfere] console→OTel bridge emission failed (${bridgeFailures} total):`,error)}finally{emitting=!1}}}for(let method of CONSOLE_METHODS){let orig=original[method],severity=SEVERITY[method];console[method]=(...args)=>{orig.apply(console,args);let activeContext=context.active(),activeSpan=trace.getSpan(activeContext);queueMicrotask(()=>{emitLog(orig,severity,method,args,activeContext,activeSpan)})}}return()=>{for(let method of CONSOLE_METHODS)console[method]=original[method]}}export{bridgeConsoleToOtel};
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/next`,version=`11.1.0`;export{name,version};
1
+ var name=`@interfere/next`,version=`11.2.1`;export{name,version};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/next",
3
- "version": "11.1.0",
3
+ "version": "11.2.1",
4
4
  "license": "MIT",
5
5
  "description": "Build software that never breaks.",
6
6
  "keywords": [
@@ -93,11 +93,11 @@
93
93
  "typecheck": "tsc --noEmit"
94
94
  },
95
95
  "dependencies": {
96
- "@interfere/constants": "^9.0.4",
97
- "@interfere/helpers": "^9.1.0",
98
- "@interfere/react": "^11.1.0",
99
- "@interfere/sdk": "^11.1.0",
100
- "@interfere/types": "^10.1.0",
96
+ "@interfere/constants": "^9.0.6",
97
+ "@interfere/helpers": "^9.1.2",
98
+ "@interfere/react": "^11.2.1",
99
+ "@interfere/sdk": "^11.2.1",
100
+ "@interfere/types": "^10.1.2",
101
101
  "@opentelemetry/api": "^1.9.1",
102
102
  "@opentelemetry/api-logs": "^0.221.0",
103
103
  "@opentelemetry/baggage-span-processor": "^0.5.0",
@@ -131,7 +131,7 @@
131
131
  }
132
132
  },
133
133
  "devDependencies": {
134
- "@interfere/test-utils": "^9.1.0",
134
+ "@interfere/test-utils": "^9.1.2",
135
135
  "@interfere/typescript-config": "^9.0.2",
136
136
  "@types/node": "^26.2.0",
137
137
  "@types/react": "19.2.18",