@interfere/vite 12.4.2 → 12.4.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 CHANGED
@@ -135,16 +135,11 @@ For **Vercel preview / production**, set `INTERFERE_API_KEY` in your `.env.local
135
135
 
136
136
  ## Environment Variables
137
137
 
138
- | Variable | Required | Description |
139
- | ---------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
- | `VITE_INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Stamped into the page by the plugin at build time, and reused server-side (`register()`, request middleware) — the plugin mirrors it into the dev server's process, so no separate server variable is needed. A production build without it logs a warning and sends no events. |
141
- | `INTERFERE_API_KEY` | Yes for deploy builds | Workspace secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format, with the **Publish releases** (`release:write`) permission. Used by the plugin at build time to register the release and upload source maps for the app that `VITE_INTERFERE_PUBLIC_KEY` names. A production build without it logs a warning and skips the release. A key with the wrong prefix fails the build. If Interfere rejects the key, the build logs an error and continues without a release. |
142
- | `INTERFERE_SOURCEMAP_UPLOAD_CONCURRENCY` | No | Maximum concurrent source-map uploads. Defaults to 20; must be a positive integer. |
143
- | `INTERFERE_API_URL` | No | Override the collector URL the build pipeline targets. Defaults to `https://api.interfere.com`. |
144
- | `VITE_INTERFERE_API_URL` | No | Override the collector URL the browser kernel targets in direct mode. Proxy mode uses `INTERFERE_API_URL` server-side. |
145
- | `VITE_INTERFERE_COMMIT_SHA` | No | Override the auto-detected commit SHA used to derive the release slug. Defaults to `git rev-parse HEAD`. |
146
- | `VITE_INTERFERE_FORCE_ENABLE` | No | `"1"` / `"true"` flips the SDK into force-enable mode so `vite dev` (which sets `NODE_ENV=development`) emits events. Production collectors silently 202-drop force-enabled batches; safe to ship by mistake but useless. Use only in dev. |
147
- | `VITE_INTERFERE_SERVICE_NAME` | No | Legacy build default for browser and server OTel `service.name`. The plugin `serviceName` option and `OTEL_SERVICE_NAME` take precedence; otherwise this overrides the unscoped package name (`@acme/storefront` → `storefront`). Explicit runtime service names always win. |
138
+ | Variable | Required | Description |
139
+ | --------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
+ | `VITE_INTERFERE_PUBLIC_KEY` | Yes | Surface public key in the `interfere_public_us_*` or `interfere_public_eu_*` format. Stamped into the page by the plugin at build time, and reused server-side (`register()`, request middleware) — the plugin mirrors it into the dev server's process, so no separate server variable is needed. A production build without it logs a warning and sends no events. |
141
+ | `INTERFERE_API_KEY` | Yes for deploy builds | Workspace secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format, with the **Publish releases** (`release:write`) permission. Used by the plugin at build time to register the release and upload source maps for the app that `VITE_INTERFERE_PUBLIC_KEY` names. A production build without it logs a warning and skips the release. A key with the wrong prefix fails the build. If Interfere rejects the key, the build logs an error and continues without a release. |
142
+ | `INTERFERE_API_URL` | No | Override the collector URL the build pipeline targets. Defaults to `https://api.interfere.com`. |
148
143
 
149
144
  ## Plugin Options
150
145
 
@@ -154,14 +149,14 @@ interfere({
154
149
  });
155
150
  ```
156
151
 
157
- | Option | Default | Description |
158
- | -------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
- | `apiHost` | — | The endpoint the browser SDK posts to. Defaults to Interfere's endpoint. Set it to a subdomain of your own site (e.g. `"https://in.yourapp.com"`) that resolves to Interfere via a DNS record to survive ad-blockers, or to a regional endpoint. Add a CNAME pointing at the target shown in your dashboard; Interfere provisions TLS automatically. |
160
- | `commitSha` | `VITE_INTERFERE_COMMIT_SHA` → `git rev-parse HEAD` | Commit SHA used to derive the deterministic release slug. |
161
- | `sourceMaps` | `{ upload: true }` | `{ upload: false }` registers the release without uploading source maps. Interfere API failures, including a rejected secret key, timeouts, network failures and HTTP error responses, never fail the build: they are logged, local source maps are deleted and the build finishes. |
162
- | `disable` | `false` | Turns Interfere off for this build: no release, no source-map upload, no build checks, and the browser and server SDKs send no events. |
163
- | `serviceName` | `OTEL_SERVICE_NAME` → `VITE_INTERFERE_SERVICE_NAME` → package name | Build default for browser and server OTel `service.name`, resolved by the same shared policy as Next.js. Uses the unscoped package name when no override is set. Explicit runtime service names and server runtime `OTEL_SERVICE_NAME` take precedence over the build default. |
164
- | `buildReceipt` | disabled | Private JSON path inside the project and outside public/build output directories. Records consumed SDK config and fetch status, original release/build identity, source publication status when available, and hashes of native Vite output files. Keep this artifact private; missing or fallback evidence cannot establish safe deployment reuse. |
152
+ | Option | Default | Description |
153
+ | -------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
154
+ | `apiHost` | — | The endpoint the browser SDK posts to. Defaults to Interfere's endpoint. Set it to a subdomain of your own site (e.g. `"https://in.yourapp.com"`) that resolves to Interfere via a DNS record to survive ad-blockers, or to a regional endpoint. Add a CNAME pointing at the target shown in your dashboard; Interfere provisions TLS automatically. |
155
+ | `commitSha` | `git rev-parse HEAD` | Commit SHA used to derive the deterministic release slug. |
156
+ | `sourceMaps` | `{ upload: true }` | `{ upload: false }` registers the release without uploading source maps. Interfere API failures, including a rejected secret key, timeouts, network failures and HTTP error responses, never fail the build: they are logged, local source maps are deleted and the build finishes. |
157
+ | `disable` | `false` | Turns Interfere off for this build: no release, no source-map upload, no build checks, and the browser and server SDKs send no events. |
158
+ | `serviceName` | `OTEL_SERVICE_NAME` → package name | Build default for browser and server OTel `service.name`, resolved by the same shared policy as Next.js. Uses the unscoped package name when no override is set. Explicit runtime service names and server runtime `OTEL_SERVICE_NAME` take precedence over the build default. |
159
+ | `buildReceipt` | disabled | Private JSON path inside the project and outside public/build output directories. Records consumed SDK config and fetch status, original release/build identity, source publication status when available, and hashes of native Vite output files. Keep this artifact private; missing or fallback evidence cannot establish safe deployment reuse. |
165
160
 
166
161
  Build logs report release creation, signing, session lookup, upload, finalization, receipt capture and cleanup in milliseconds, plus transferred bytes. Finalization confirms durable acceptance. When Vite builds several environments, the plugin uploads once after all of them finish. It does not replace runtime stack/source canaries or continually rehash every previously published map.
167
162
 
@@ -1 +1 @@
1
- import{API_URL_GLOBAL,ENVIRONMENT_GLOBAL,FORCE_ENABLE_GLOBAL,INGEST_URL_GLOBAL,PUBLIC_KEY_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PLUGINS_GLOBAL}from"@interfere/types/sdk/remote-config";import{SERVICE_NAME_GLOBAL}from"@interfere/types/sdk/service-name";function applyBrowserGlobals(){let g=globalThis;import.meta.env.VITE_INTERFERE_PUBLIC_KEY&&(g[PUBLIC_KEY_GLOBAL]??=import.meta.env.VITE_INTERFERE_PUBLIC_KEY),import.meta.env.VITE_INTERFERE_API_HOST&&(g[API_URL_GLOBAL]??=import.meta.env.VITE_INTERFERE_API_HOST),import.meta.env.VITE_INTERFERE_ENVIRONMENT&&(g[ENVIRONMENT_GLOBAL]??=import.meta.env.VITE_INTERFERE_ENVIRONMENT),import.meta.env.VITE_INTERFERE_RELEASE_SLUG&&(g[RELEASE_SLUG_GLOBAL]??=import.meta.env.VITE_INTERFERE_RELEASE_SLUG),import.meta.env.VITE_INTERFERE_INGEST_URL&&(g[INGEST_URL_GLOBAL]??=import.meta.env.VITE_INTERFERE_INGEST_URL),import.meta.env.VITE_INTERFERE_SERVICE_NAME&&(g[SERVICE_NAME_GLOBAL]??=import.meta.env.VITE_INTERFERE_SERVICE_NAME);let bakedPlugins=parseBakedPlugins(import.meta.env.VITE_INTERFERE_PLUGINS);bakedPlugins&&(g[PLUGINS_GLOBAL]??=bakedPlugins);let forceEnable=import.meta.env.VITE_INTERFERE_FORCE_ENABLE;(forceEnable===`1`||forceEnable===`true`)&&(g[FORCE_ENABLE_GLOBAL]??=!0)}function parseBakedPlugins(raw){if(raw)try{let parsed=JSON.parse(raw);return typeof parsed==`object`&&parsed&&!Array.isArray(parsed)?parsed:void 0}catch{return}}export{applyBrowserGlobals};
1
+ import{BACKEND_HOSTS_GLOBAL,backendHostsSchema}from"@interfere/types/sdk/remote-config/backend-hosts";import{API_URL_GLOBAL,ENVIRONMENT_GLOBAL,INGEST_URL_GLOBAL,PUBLIC_KEY_GLOBAL,RELEASE_SLUG_GLOBAL}from"@interfere/types/sdk/injected-globals";import{PLUGINS_GLOBAL}from"@interfere/types/sdk/remote-config";import{ANONYMOUS_REPLAY_GLOBAL}from"@interfere/types/sdk/remote-config/anonymous-replay";import{SERVICE_NAME_GLOBAL}from"@interfere/types/sdk/service-name";function applyBrowserGlobals(){let g=globalThis;import.meta.env.VITE_INTERFERE_PUBLIC_KEY&&(g[PUBLIC_KEY_GLOBAL]??=import.meta.env.VITE_INTERFERE_PUBLIC_KEY),import.meta.env.VITE_INTERFERE_API_HOST&&(g[API_URL_GLOBAL]??=import.meta.env.VITE_INTERFERE_API_HOST),import.meta.env.VITE_INTERFERE_ENVIRONMENT&&(g[ENVIRONMENT_GLOBAL]??=import.meta.env.VITE_INTERFERE_ENVIRONMENT),import.meta.env.VITE_INTERFERE_RELEASE_SLUG&&(g[RELEASE_SLUG_GLOBAL]??=import.meta.env.VITE_INTERFERE_RELEASE_SLUG),import.meta.env.VITE_INTERFERE_INGEST_URL&&(g[INGEST_URL_GLOBAL]??=import.meta.env.VITE_INTERFERE_INGEST_URL),import.meta.env.VITE_INTERFERE_SERVICE_NAME&&(g[SERVICE_NAME_GLOBAL]??=import.meta.env.VITE_INTERFERE_SERVICE_NAME);let bakedHosts=parseBakedHosts(import.meta.env.VITE_INTERFERE_BACKEND_HOSTS);bakedHosts&&(g[BACKEND_HOSTS_GLOBAL]??=bakedHosts);let bakedPlugins=parseBakedPlugins(import.meta.env.VITE_INTERFERE_PLUGINS);bakedPlugins&&(g[PLUGINS_GLOBAL]??=bakedPlugins),import.meta.env.VITE_INTERFERE_ANONYMOUS_REPLAY&&(g[ANONYMOUS_REPLAY_GLOBAL]??=import.meta.env.VITE_INTERFERE_ANONYMOUS_REPLAY)}function parseBakedPlugins(raw){if(raw)try{let parsed=JSON.parse(raw);return typeof parsed==`object`&&parsed&&!Array.isArray(parsed)?parsed:void 0}catch{return}}function parseBakedHosts(raw){if(raw)try{let parsed=JSON.parse(raw);return backendHostsSchema.safeParse(parsed).data}catch{return}}export{applyBrowserGlobals};
@@ -1 +1 @@
1
- import{readPublicationStatus}from"./read-publication-status.mjs";import{transferSourceMaps}from"./transfer-source-maps.mjs";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{sourceMapBatchDigest}from"@interfere/types/data/source-map-batch";import{resolveReleaseRequest}from"@interfere/build/release-request";import{createReleaseResponse}from"@interfere/types/releases/definition";async function runReleasePipeline(input){let{client,discovery,releasesConfig}=input,{files}=discovery,releaseRequest=resolveReleaseRequest({...input,config:releasesConfig});if(!releaseRequest.ok)return{failure:{config:releasesConfig,kind:releaseRequest.reason},ok:!1};if(input.uploadSourceMaps&&files.length===0)throw Error(`Source maps are required to prepare a release`);let start=performance.now(),release=createReleaseResponse.parse(await client.releases.createRelease(releaseRequest.request,{abortSignal:AbortSignal.timeout(6e4)})),createdAt=performance.now(),releaseSlug=release.destination.slug;if(!input.uploadSourceMaps)return{data:{cleanupFailures:[],fileCount:0,releaseSlug,publication:void 0,timing:void 0},ok:!0};let transfer=await transferSourceMaps({...input,releaseSlug}),{completion,session,acceptedAt}=transfer,publication=input.capturePublication?{releaseSlug,buildId:release.build.hash,environment:input.environment,sessionId:session?.releaseSlug===releaseSlug&&session.environment===input.environment?session.sessionId:null,batchDigest:await sourceMapBatchDigest(completion),status:await readPublicationStatus({...input,releaseSlug})}:void 0,publicationReadAt=performance.now();return{data:{cleanupFailures:await cleanupSourceMaps(files),fileCount:files.length,releaseSlug,publication,timing:{createRelease:Math.round(createdAt-start),...transfer.timing,receipt:Math.round(publicationReadAt-acceptedAt),cleanup:Math.round(performance.now()-publicationReadAt),total:Math.round(performance.now()-start)}},ok:!0}}export{runReleasePipeline};
1
+ import{readPublicationStatus}from"./read-publication-status.mjs";import{transferSourceMaps}from"./transfer-source-maps.mjs";import{resolveReleaseRequest}from"@interfere/build/release-request";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{sourceMapBatchDigest}from"@interfere/types/data/source-map-batch";import{createReleaseResponse}from"@interfere/types/releases/definition";async function runReleasePipeline(input){let{client,discovery,releasesConfig}=input,{files}=discovery,releaseRequest=resolveReleaseRequest({...input,config:releasesConfig});if(!releaseRequest.ok)return{failure:{config:releasesConfig,kind:releaseRequest.reason},ok:!1};if(input.uploadSourceMaps&&files.length===0)throw Error(`Source maps are required to prepare a release`);let start=performance.now(),release=createReleaseResponse.parse(await client.releases.createRelease(releaseRequest.request,{abortSignal:AbortSignal.timeout(6e4)})),createdAt=performance.now(),releaseSlug=release.destination.slug;if(!input.uploadSourceMaps)return{data:{cleanupFailures:[],fileCount:0,releaseSlug,publication:void 0,timing:void 0},ok:!0};let transfer=await transferSourceMaps({...input,releaseSlug}),{completion,session,acceptedAt}=transfer,publication=input.capturePublication?{releaseSlug,buildId:release.build.hash,environment:input.environment,sessionId:session?.releaseSlug===releaseSlug&&session.environment===input.environment?session.sessionId:null,batchDigest:await sourceMapBatchDigest(completion),status:await readPublicationStatus({...input,releaseSlug})}:void 0,publicationReadAt=performance.now();return{data:{cleanupFailures:await cleanupSourceMaps(files),fileCount:files.length,releaseSlug,publication,timing:{createRelease:Math.round(createdAt-start),...transfer.timing,receipt:Math.round(publicationReadAt-acceptedAt),cleanup:Math.round(performance.now()-publicationReadAt),total:Math.round(performance.now()-start)}},ok:!0}}export{runReleasePipeline};
@@ -0,0 +1,10 @@
1
+ import { Interfere } from "@interfere/javascript";
2
+ import { ReleaseDiagnosticContext } from "@interfere/types/sdk/build-diagnostics";
3
+ //#region src/internal/build/preflight.d.ts
4
+ export declare const DIAGNOSTIC_CONTEXT: ReleaseDiagnosticContext;
5
+ export declare function preflightViteBinding(input: {
6
+ apiUrl: string;
7
+ apiKey: string;
8
+ publicKey: string;
9
+ }): Promise<Interfere.ReleasesConfigResponse | null>;
10
+ //#endregion
@@ -0,0 +1 @@
1
+ import{preflightReleaseBinding}from"@interfere/build/release-request";import{PUBLIC_KEY_HEADER}from"@interfere/constants/api";import{InterfereClient}from"@interfere/javascript";const DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`VITE_INTERFERE_PUBLIC_KEY`,docsUrl:`https://interfere.com/docs/sdk/vite`};async function preflightViteBinding(input){let client=new InterfereClient({baseUrl:input.apiUrl,apiKey:{token:input.apiKey},headers:{[PUBLIC_KEY_HEADER]:input.publicKey},maxRetries:0,timeoutInSeconds:10});return await preflightReleaseBinding(client)}export{DIAGNOSTIC_CONTEXT,preflightViteBinding};
@@ -0,0 +1,13 @@
1
+ import { createBuildConfigResolver } from "@interfere/build/config";
2
+ //#region src/internal/build/remote-config-define.d.ts
3
+ type BakedConfig = Awaited<ReturnType<ReturnType<typeof createBuildConfigResolver>>>;
4
+ export declare function remoteConfigDefine(baked: BakedConfig | null): {
5
+ "import.meta.env.VITE_INTERFERE_INGEST_URL": string | undefined;
6
+ "import.meta.env.VITE_INTERFERE_SERVER_TRACING": string;
7
+ "import.meta.env.VITE_INTERFERE_LOG_SOURCING": string;
8
+ "import.meta.env.VITE_INTERFERE_PLATFORM": string;
9
+ "import.meta.env.VITE_INTERFERE_BACKEND_HOSTS": string | undefined;
10
+ "import.meta.env.VITE_INTERFERE_ANONYMOUS_REPLAY": string;
11
+ "import.meta.env.VITE_INTERFERE_PLUGINS": string | undefined;
12
+ };
13
+ //#endregion
@@ -0,0 +1 @@
1
+ function remoteConfigDefine(baked){return{"import.meta.env.VITE_INTERFERE_INGEST_URL":baked?.ingestUrl?JSON.stringify(baked.ingestUrl):void 0,"import.meta.env.VITE_INTERFERE_SERVER_TRACING":JSON.stringify(baked?.serverTracing??null),"import.meta.env.VITE_INTERFERE_LOG_SOURCING":JSON.stringify(baked?.logSourcing??null),"import.meta.env.VITE_INTERFERE_PLATFORM":baked?.platform===void 0?`undefined`:JSON.stringify(baked.platform),"import.meta.env.VITE_INTERFERE_BACKEND_HOSTS":baked?.backendHosts?JSON.stringify(JSON.stringify(baked.backendHosts)):void 0,"import.meta.env.VITE_INTERFERE_ANONYMOUS_REPLAY":JSON.stringify(baked?.anonymousReplayPolicy??null),"import.meta.env.VITE_INTERFERE_PLUGINS":baked?.plugins?JSON.stringify(JSON.stringify(baked.plugins)):void 0}}export{remoteConfigDefine};
@@ -12,7 +12,7 @@ export interface InterfereViteOptions {
12
12
  apiHost?: string;
13
13
  /**
14
14
  * Override the commit SHA used to derive the release slug. Defaults
15
- * to `VITE_INTERFERE_COMMIT_SHA` env then `git rev-parse HEAD`.
15
+ * to `git rev-parse HEAD`.
16
16
  * Set this when the build runs outside a git checkout (e.g. a
17
17
  * Docker layer-cached image build).
18
18
  */
@@ -21,7 +21,7 @@ export interface InterfereViteOptions {
21
21
  buildVariant?: string;
22
22
  /**
23
23
  * Default service name for browser and server telemetry. Falls back to
24
- * `OTEL_SERVICE_NAME`, `VITE_INTERFERE_SERVICE_NAME`, then the unscoped
24
+ * `OTEL_SERVICE_NAME`, then the unscoped
25
25
  * app package name. Explicit runtime service names still win.
26
26
  */
27
27
  serviceName?: string;
@@ -12,7 +12,6 @@
12
12
  export declare function isEnabledOnServer(): boolean;
13
13
  export declare function readInterfereServerEnv(): {
14
14
  apiUrl: string;
15
- forceEnable: boolean;
16
15
  nodeEnvironment: string;
17
16
  publicKey: string | null;
18
17
  };
@@ -1 +1 @@
1
- import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return import.meta.env.VITE_INTERFERE_DISABLED?!1:process.env.NODE_ENV===`production`||!!process.env.VITE_INTERFERE_PUBLIC_KEY}function readInterfereServerEnv(){let nodeEnvironment=resolveEnvironment({env:process.env,explicit:import.meta.env.VITE_INTERFERE_ENVIRONMENT});return{apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,forceEnable:!!process.env.INTERFERE_FORCE_ENABLE,nodeEnvironment,publicKey:import.meta.env.VITE_INTERFERE_DISABLED?null:parseEnvValue(process.env.VITE_INTERFERE_PUBLIC_KEY)}}export{isEnabledOnServer,readInterfereServerEnv};
1
+ import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{resolveEnvironment}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return import.meta.env.VITE_INTERFERE_DISABLED?!1:process.env.NODE_ENV===`production`||!!process.env.VITE_INTERFERE_PUBLIC_KEY}function readInterfereServerEnv(){let nodeEnvironment=resolveEnvironment({env:process.env,explicit:import.meta.env.VITE_INTERFERE_ENVIRONMENT});return{apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nodeEnvironment,publicKey:import.meta.env.VITE_INTERFERE_DISABLED?null:parseEnvValue(process.env.VITE_INTERFERE_PUBLIC_KEY)}}export{isEnabledOnServer,readInterfereServerEnv};
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/vite`,version=`12.4.2`;export{name,version};
1
+ var name=`@interfere/vite`,version=`12.4.4`;export{name,version};
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{PRODUCER_VERSION}from"./version.mjs";import{runGitCommand}from"./internal/build/git.mjs";import{rejectRemovedOptions}from"./internal/plugin-options.mjs";import{createBuildReceipt}from"./internal/build/build-receipt.mjs";import{runReleasePipeline}from"./internal/build/pipeline.mjs";import{renderDebugId}from"./internal/build/render-debug-id.mjs";import{discoverByPairing}from"./internal/build/source-maps.mjs";import{resolveCommitSha}from"./internal/release-slug.mjs";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{resolveBuildServiceName}from"@interfere/build/service-name";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{InterfereClient}from"@interfere/javascript";import{createBuildConfigResolver}from"@interfere/build/config";import{API_URL,PUBLIC_KEY_HEADER}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{checkReleaseConfig,diagnoseRelease}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{createHash}from"node:crypto";import{existsSync,readdirSync,statSync}from"node:fs";import path from"node:path";import{loadEnv}from"vite";const SERVER_RUNTIME_PLUGIN_HINTS=[`tanstack-start`,`nitro`,`@tanstack/react-start`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`VITE_INTERFERE_PUBLIC_KEY`,docsUrl:`https://interfere.com/docs/sdk/vite`};function buildError(title,details){return Error([`Interfere: ${title}`,...details].join(`
2
- `))}function logDiagnostic(logger,diagnostic){for(let line of[diagnostic.title,...diagnostic.details])logger[diagnostic.severity](`[interfere] ${line}`)}function isTestEnv(){return!!(process.env.VITEST||process.env.VITEST_WORKER_ID)}function pickEnvValue(env,key){let value=env[key];return value&&value.length>0?value:void 0}function hasServerRuntimePlugin(pluginNames){return pluginNames.some(name=>SERVER_RUNTIME_PLUGIN_HINTS.some(hint=>name.includes(hint)))}function normalizeOrigin(host){try{return new URL(host).origin}catch{return null}}function resolveApiHost(options,env){let host=options.apiHost??env.VITE_INTERFERE_API_URL;if(!host)return;let origin=normalizeOrigin(host);if(!origin)throw Error(`interfere({ apiHost }) requires a full URL, e.g. apiHost: "https://in.yourapp.com"`);return origin}function resolveBrowserConfig(options,env,command,root){let commitSha=options.commitSha??env.VITE_INTERFERE_COMMIT_SHA??resolveCommitSha(runGitCommand);return{apiHost:resolveApiHost(options,env),commitSha,environment:command===`build`?resolveDeployEnvironment({...env,...process.env}):null,publicKey:env.VITE_INTERFERE_PUBLIC_KEY,serviceName:resolveBuildServiceName({option:options.serviceName,env,root,legacyEnvName:`VITE_INTERFERE_SERVICE_NAME`}),releaseSlug:commitSha?deriveReleaseSlug(options.buildVariant?createHash(`sha256`).update(JSON.stringify([commitSha,options.buildVariant])).digest(`hex`):commitSha):null}}function browserDefine(browser){return{"import.meta.env.VITE_INTERFERE_API_HOST":JSON.stringify(browser.apiHost??null),"import.meta.env.VITE_INTERFERE_ENVIRONMENT":JSON.stringify(browser.environment),"import.meta.env.VITE_INTERFERE_RELEASE_SLUG":JSON.stringify(browser.releaseSlug),"import.meta.env.VITE_INTERFERE_SERVICE_NAME":JSON.stringify(browser.serviceName??null)}}function hasFreshServerSourceMaps(root,sinceMs){if(!existsSync(root))return!1;let entries=readdirSync(root,{withFileTypes:!0});for(let entry of entries){let absolute=path.join(root,entry.name);if(entry.isDirectory()){if(hasFreshServerSourceMaps(absolute,sinceMs))return!0;continue}if(entry.name.endsWith(`.map`)&&statSync(absolute).mtimeMs>=sinceMs)return!0}return!1}function isReleaseReady(state){return!!(state.releaseEnabled&&state.apiKey&&state.releaseSlug&&state.commitSha&&!state.releaseStarted)}function shouldWaitForServerOutput(state){return state.uploadSourceMaps&&state.serverBuild&&!hasFreshServerSourceMaps(state.serverSourcemapsDir,state.buildStartedAt)}function logSkippedRelease(logger,failure){if(failure.kind===`missing_source_provider`){logger.warn(`[interfere] release skipped: this surface has no version control provider configured. Configure one at https://interfere.com/~/${failure.config.org.slug}/apps/${failure.config.surface.slug}.`);return}logger.warn(`[interfere] release skipped: this surface has no deployment target configured. Configure one at https://interfere.com/~/${failure.config.org.slug}/apps/${failure.config.surface.slug}.`)}function logCleanupFailures(logger,failures){for(let failure of failures)logger.warn(`[interfere] cleanup left ${failure.absolute} on disk: ${failure.code??failure.message}`)}async function discoverUploadableSourceMaps(state){let discovered;try{discovered=await discoverByPairing({distDir:state.uploadOutDir,projectDir:state.projectDir,publicPathRewrites:[{from:`public`,to:``}],subtrees:[`.`]})}catch(error){return logDiagnostic(state.logger,diagnoseRelease(DIAGNOSTIC_CONTEXT,error)),null}return discovered.missingSourceMapCount>0&&state.logger.warn(`[interfere] ${discovered.missingSourceMapCount} referenced source map(s) not found. Errors in those chunks will not resolve to source.`),discovered.files.length===0?(state.logger.warn(`[interfere] No source maps found; the release remains unready. Enable source-map generation and upload them during the build.`),null):discovered}async function publishRelease(state,discovered){let{apiKey,apiUrl,releaseSlug,commitSha,logger,publicKey}=state,client=new InterfereClient({fetch:sourceMapUploadFetch,baseUrl:apiUrl,apiKey:{token:apiKey},headers:{"user-agent":PRODUCER_VERSION,[PUBLIC_KEY_HEADER]:publicKey},maxRetries:4,timeoutInSeconds:10}),releasesConfig=await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)}),result=await runReleasePipeline({client,buildId:commitSha,bundler:`rollup`,discovery:discovered,environment:state.environment,producerVersion:PRODUCER_VERSION,releaseSlug,releasesConfig,capturePublication:state.receipt!==void 0,uploadSourceMaps:state.uploadSourceMaps});if(!result.ok){logSkippedRelease(logger,result.failure);return}if(logCleanupFailures(logger,result.data.cleanupFailures),result.data.publication&&state.receipt?.recordPublication(result.data.publication),!result.data.timing){logger.info(`[interfere] release ${result.data.releaseSlug} registered without source maps.`);return}logger.info(`[interfere] source-map stages (ms): create=${result.data.timing.createRelease}, sign=${result.data.timing.sign}, session=${result.data.timing.session}, upload=${result.data.timing.upload}, finalize=${result.data.timing.finalize}, receipt=${result.data.timing.receipt}, cleanup=${result.data.timing.cleanup}; bytes=${result.data.timing.totalBytes}.`),state.uploadComplete=!0,logger.info(`[interfere] release ${result.data.releaseSlug} confirmed — ${result.data.fileCount} maps, ${result.data.timing.total}ms.`)}async function releaseBuild(state){let{logger,releaseSlug}=state;logger.info(state.uploadSourceMaps?`[interfere] uploading source maps for release ${releaseSlug}…`:`[interfere] registering release ${releaseSlug} without source maps…`);let discovered=state.uploadSourceMaps?await discoverUploadableSourceMaps(state):{files:[],missingSourceMapCount:0,sourceFileCount:0};if(discovered){try{await publishRelease(state,discovered)}catch(error){logDiagnostic(logger,diagnoseRelease(DIAGNOSTIC_CONTEXT,error))}state.uploadComplete||logCleanupFailures(logger,await cleanupSourceMaps(discovered.files))}}async function releaseBuildWhenReady(state){if(isReleaseReady(state)){if(shouldWaitForServerOutput(state)){state.logger.info(`[interfere] waiting for server build output in ${path.relative(state.projectDir,state.serverSourcemapsDir)} before uploading source maps.`);return}state.releaseStarted=!0,await releaseBuild(state)}}function resolveReleaseEnv(config){return{...loadEnv(config.mode,config.envDir,``),...process.env}}function buildPluginState(options,config,browser){let{logger}=config,isProductionBuild=config.command===`build`,releaseEnv=resolveReleaseEnv(config),apiKey=pickEnvValue(releaseEnv,`INTERFERE_API_KEY`),apiUrl=pickEnvValue(releaseEnv,`INTERFERE_API_URL`)??API_URL,retainSourceMaps=isProductionBuild&&options.sourceMaps?.retain===!0,release=isProductionBuild&&!retainSourceMaps?checkReleaseConfig(DIAGNOSTIC_CONTEXT,{apiKey,commitSha:browser.commitSha,releaseSlug:browser.releaseSlug}):null;if(release?.status===`fail`)throw buildError(release.title,release.details);let releaseEnabled=release?.status===`ok`,uploadOutDir=path.resolve(config.root,config.build.outDir),serverSourcemapsDir=options.serverSourcemapsDir?path.resolve(config.root,options.serverSourcemapsDir):path.join(uploadOutDir,`server`),serverBuild=hasServerRuntimePlugin(config.plugins.map(p=>p.name))||!!config.build.ssr;return{apiHost:browser.apiHost,apiKey,apiUrl,buildStartedAt:Math.floor(Date.now()/1e3)*1e3,commitSha:browser.commitSha,releaseEnabled,retainSourceMaps,skippedRelease:release?.status===`skip`?release:null,uploadSourceMaps:releaseEnabled&&options.sourceMaps?.upload!==!1,environment:browser.environment,isProductionBuild,logger,projectDir:config.root,publicKey:browser.publicKey,releaseSlug:browser.releaseSlug,serverBuild,serverSourcemapsDir,uploadOutDir,releaseStarted:!1,uploadComplete:!1}}function announceSetup(state){if(state.logger.info(`[interfere] ingest → ${state.apiHost??`Interfere (default)`}`),!state.apiHost&&state.serverBuild&&state.logger.warn(`[interfere] Telemetry posts directly to Interfere. On a server-rendered app, set interfere({ apiHost }) to a subdomain of your own site to survive ad-blockers.`),state.isProductionBuild&&!state.publicKey&&state.logger.warn(`[interfere] VITE_INTERFERE_PUBLIC_KEY is not set, so this build sends no events. Set it on builds that you deploy.`),state.retainSourceMaps&&state.logger.info(`[interfere] source maps are retained in the build output for a release step to upload per environment.`),state.skippedRelease)for(let line of[state.skippedRelease.title,...state.skippedRelease.details])state.logger.warn(`[interfere] ${line}`)}function mirrorPublicKeyForDevServer(command,env){if(command!==`serve`)return;let publicKey=env.VITE_INTERFERE_PUBLIC_KEY;publicKey&&!process.env.VITE_INTERFERE_PUBLIC_KEY&&(process.env.VITE_INTERFERE_PUBLIC_KEY=publicKey)}function resolveRoot(userConfig){return userConfig.root?path.resolve(process.cwd(),userConfig.root):process.cwd()}function resolveEnvDir(userConfig){let root=resolveRoot(userConfig);return userConfig.envDir===!1||userConfig.envDir===void 0||userConfig.envDir===``?root:path.resolve(root,userConfig.envDir)}function forceSourcemapForUpload(userConfig,options,command,env){if(!(command===`build`&&(options.sourceMaps?.retain===!0||options.sourceMaps?.upload!==!1&&pickEnvValue(env,`INTERFERE_API_KEY`))))return;let previous=userConfig.build?.sourcemap;return userConfig.build={...userConfig.build,sourcemap:!0},previous===void 0||previous===!0?void 0:previous}function carryOverUploadProgress(previous,next){previous?.serverBuild&&next.serverBuild&&(next.uploadOutDir=previous.uploadOutDir,next.serverSourcemapsDir=previous.serverSourcemapsDir,next.releaseStarted=previous.releaseStarted,next.uploadComplete=previous.uploadComplete,next.buildStartedAt=previous.buildStartedAt)}function warnForcedSourcemap(logger,overridden){overridden!==void 0&&logger.warn(`[interfere] Forced build.sourcemap to true (was ${JSON.stringify(overridden)}). Source maps are required to symbolicate errors; they are uploaded then deleted from the build output, so they are never served. Pass interfere({ sourceMaps: { upload: false } }) to opt out.`)}function disabledPlugin(){return{name:`interfere`,config(){return{define:{"import.meta.env.VITE_INTERFERE_DISABLED":JSON.stringify(`true`)},ssr:{noExternal:[`@interfere/vite`]}}}}}function interfere(options={}){if(rejectRemovedOptions(options),options.disable)return disabledPlugin();let receipt=options.buildReceipt?createBuildReceipt(options.buildReceipt):void 0,remoteConfigFallback=!1,resolveBuildConfig=createBuildConfigResolver(result=>{receipt?.observeConfig(result),remoteConfigFallback||=result.status===`fallback`}),state=null,overriddenSourcemap,announced=!1,uploadAfterAppBuild=!1,writeReceipt=async()=>{try{await receipt?.write()}catch{state?.logger.warn(`[interfere] Private build receipt could not be written.`)}};return{buildApp:{order:`post`,async handler(builder){if(!state)return;uploadAfterAppBuild=!0;let environments=Object.values(builder.environments);if(environments.every(environment=>!environment.isBuilt))for(let environment of environments)await builder.build(environment);isReleaseReady(state)&&(state.releaseStarted=!0,await releaseBuild(state)),await writeReceipt()}},buildStart(){state&&!announced&&(announceSetup(state),warnForcedSourcemap(state.logger,overriddenSourcemap),remoteConfigFallback&&state.logger.warn(`[interfere] Could not load SDK settings from Interfere. The build uses the default settings.`),announced=!0)},async closeBundle(){state&&!uploadAfterAppBuild&&await releaseBuildWhenReady(state),await writeReceipt()},async writeBundle(output,bundle){output.dir&&await receipt?.recordOutput(output.dir,Object.keys(bundle))},outputOptions(output){return state?.uploadSourceMaps||state?.retainSourceMaps?{...output,sourcemapDebugIds:!1}:null},renderChunk:{order:`post`,handler(code){return state?.uploadSourceMaps||state?.retainSourceMaps?renderDebugId(code):null}},async config(userConfig,{command,mode}){let env=loadEnv(mode,resolveEnvDir(userConfig),``);mirrorPublicKeyForDevServer(command,env),overriddenSourcemap=forceSourcemapForUpload(userConfig,options,command,env);let browser=resolveBrowserConfig(options,env,command,resolveRoot(userConfig)),baked=command===`build`&&browser.publicKey&&!isTestEnv()?await resolveBuildConfig(browser.apiHost??API_URL,browser.publicKey):null;return{define:omitUndefined({...browserDefine(browser),"import.meta.env.VITE_INTERFERE_INGEST_URL":baked?.ingestUrl?JSON.stringify(baked.ingestUrl):void 0,"import.meta.env.VITE_INTERFERE_SERVER_TRACING":JSON.stringify(baked?.serverTracing??null),"import.meta.env.VITE_INTERFERE_LOG_SOURCING":JSON.stringify(baked?.logSourcing??null),"import.meta.env.VITE_INTERFERE_PLATFORM":baked?.platform===void 0?`undefined`:JSON.stringify(baked.platform),"import.meta.env.VITE_INTERFERE_PLUGINS":baked?.plugins?JSON.stringify(JSON.stringify(baked.plugins)):void 0}),ssr:{noExternal:[`@interfere/vite`]}}},async configResolved(config){let browser=resolveBrowserConfig(options,config.env,config.command,config.root),next=buildPluginState(options,config,browser);carryOverUploadProgress(state,next),state={...next,receipt},uploadAfterAppBuild||=config.builder!==void 0,config.command===`build`&&await receipt?.configure(config,{commitSha:browser.commitSha,releaseSlug:browser.releaseSlug,environment:browser.environment,publicKey:browser.publicKey,apiOrigin:browser.apiHost??API_URL,producerVersion:PRODUCER_VERSION})},enforce:`pre`,name:`interfere`,sharedDuringBuild:!0}}export{interfere};
1
+ import{PRODUCER_VERSION}from"./version.mjs";import{DIAGNOSTIC_CONTEXT,preflightViteBinding}from"./internal/build/preflight.mjs";import{runGitCommand}from"./internal/build/git.mjs";import{rejectRemovedOptions}from"./internal/plugin-options.mjs";import{createBuildReceipt}from"./internal/build/build-receipt.mjs";import{runReleasePipeline}from"./internal/build/pipeline.mjs";import{remoteConfigDefine}from"./internal/build/remote-config-define.mjs";import{renderDebugId}from"./internal/build/render-debug-id.mjs";import{discoverByPairing}from"./internal/build/source-maps.mjs";import{resolveCommitSha}from"./internal/release-slug.mjs";import{missingReleaseBinding}from"@interfere/build/release-request";import{API_URL,PUBLIC_KEY_HEADER}from"@interfere/constants/api";import{InterfereClient}from"@interfere/javascript";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{resolveBuildServiceName}from"@interfere/build/service-name";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{createBuildConfigResolver}from"@interfere/build/config";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{checkReleaseConfig,diagnoseRelease}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{createHash}from"node:crypto";import{existsSync,readdirSync,statSync}from"node:fs";import path from"node:path";import{loadEnv}from"vite";const SERVER_RUNTIME_PLUGIN_HINTS=[`tanstack-start`,`nitro`,`@tanstack/react-start`];function buildError(title,details){return Error([`Interfere: ${title}`,...details].join(`
2
+ `))}function logDiagnostic(logger,diagnostic){for(let line of[diagnostic.title,...diagnostic.details])logger[diagnostic.severity](`[interfere] ${line}`)}function isTestEnv(){return!!(process.env.VITEST||process.env.VITEST_WORKER_ID)}function pickEnvValue(env,key){let value=env[key];return value&&value.length>0?value:void 0}function hasServerRuntimePlugin(pluginNames){return pluginNames.some(name=>SERVER_RUNTIME_PLUGIN_HINTS.some(hint=>name.includes(hint)))}function normalizeOrigin(host){try{return new URL(host).origin}catch{return null}}function resolveApiHost(options){let host=options.apiHost;if(!host)return;let origin=normalizeOrigin(host);if(!origin)throw Error(`interfere({ apiHost }) requires a full URL, e.g. apiHost: "https://in.yourapp.com"`);return origin}function resolveBrowserConfig(options,env,command,root){let commitSha=options.commitSha??resolveCommitSha(runGitCommand);return{apiHost:resolveApiHost(options),commitSha,environment:command===`build`?resolveDeployEnvironment({...env,...process.env}):null,publicKey:env.VITE_INTERFERE_PUBLIC_KEY,serviceName:resolveBuildServiceName({option:options.serviceName,env,root}),releaseSlug:commitSha?deriveReleaseSlug(options.buildVariant?createHash(`sha256`).update(JSON.stringify([commitSha,options.buildVariant])).digest(`hex`):commitSha):null}}function browserDefine(browser){return{"import.meta.env.VITE_INTERFERE_API_HOST":JSON.stringify(browser.apiHost??null),"import.meta.env.VITE_INTERFERE_ENVIRONMENT":JSON.stringify(browser.environment),"import.meta.env.VITE_INTERFERE_RELEASE_SLUG":JSON.stringify(browser.releaseSlug),"import.meta.env.VITE_INTERFERE_SERVICE_NAME":JSON.stringify(browser.serviceName??null)}}function hasFreshServerSourceMaps(root,sinceMs){if(!existsSync(root))return!1;let entries=readdirSync(root,{withFileTypes:!0});for(let entry of entries){let absolute=path.join(root,entry.name);if(entry.isDirectory()){if(hasFreshServerSourceMaps(absolute,sinceMs))return!0;continue}if(entry.name.endsWith(`.map`)&&statSync(absolute).mtimeMs>=sinceMs)return!0}return!1}function isReleaseReady(state){return!!(state.releaseEnabled&&state.apiKey&&state.releaseSlug&&state.commitSha&&!state.releaseStarted)}function shouldWaitForServerOutput(state){return state.uploadSourceMaps&&state.serverBuild&&!hasFreshServerSourceMaps(state.serverSourcemapsDir,state.buildStartedAt)}function logSkippedRelease(logger,failure){if(failure.kind===`missing_source_provider`){logger.warn(`[interfere] release skipped: this surface has no version control provider configured. Configure one at https://interfere.com/~/${failure.config.org.slug}/apps/${failure.config.surface.slug}.`);return}logger.warn(`[interfere] release skipped: this surface has no deployment target configured. Configure one at https://interfere.com/~/${failure.config.org.slug}/apps/${failure.config.surface.slug}.`)}function logCleanupFailures(logger,failures){for(let failure of failures)logger.warn(`[interfere] cleanup left ${failure.absolute} on disk: ${failure.code??failure.message}`)}async function discoverUploadableSourceMaps(state){let discovered;try{discovered=await discoverByPairing({distDir:state.uploadOutDir,projectDir:state.projectDir,publicPathRewrites:[{from:`public`,to:``}],subtrees:[`.`]})}catch(error){return logDiagnostic(state.logger,diagnoseRelease(DIAGNOSTIC_CONTEXT,error)),null}return discovered.missingSourceMapCount>0&&state.logger.warn(`[interfere] ${discovered.missingSourceMapCount} referenced source map(s) not found. Errors in those chunks will not resolve to source.`),discovered.files.length===0?(state.logger.warn(`[interfere] No source maps found; the release remains unready. Enable source-map generation and upload them during the build.`),null):discovered}async function publishRelease(state,discovered){let{apiKey,apiUrl,releaseSlug,commitSha,logger,publicKey}=state,client=new InterfereClient({fetch:sourceMapUploadFetch,baseUrl:apiUrl,apiKey:{token:apiKey},headers:{"user-agent":PRODUCER_VERSION,[PUBLIC_KEY_HEADER]:publicKey},maxRetries:4,timeoutInSeconds:10}),releasesConfig=state.releaseConfig??await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)}),result=await runReleasePipeline({client,buildId:commitSha,bundler:`rollup`,discovery:discovered,environment:state.environment,producerVersion:PRODUCER_VERSION,releaseSlug,releasesConfig,capturePublication:state.receipt!==void 0,uploadSourceMaps:state.uploadSourceMaps});if(!result.ok){logSkippedRelease(logger,result.failure);return}if(logCleanupFailures(logger,result.data.cleanupFailures),result.data.publication&&state.receipt?.recordPublication(result.data.publication),!result.data.timing){logger.info(`[interfere] release ${result.data.releaseSlug} registered without source maps.`);return}logger.info(`[interfere] source-map stages (ms): create=${result.data.timing.createRelease}, sign=${result.data.timing.sign}, session=${result.data.timing.session}, upload=${result.data.timing.upload}, finalize=${result.data.timing.finalize}, receipt=${result.data.timing.receipt}, cleanup=${result.data.timing.cleanup}; bytes=${result.data.timing.totalBytes}.`),state.uploadComplete=!0,logger.info(`[interfere] release ${result.data.releaseSlug} confirmed — ${result.data.fileCount} maps, ${result.data.timing.total}ms.`)}async function releaseBuild(state){let{logger,releaseSlug}=state;logger.info(state.uploadSourceMaps?`[interfere] uploading source maps for release ${releaseSlug}…`:`[interfere] registering release ${releaseSlug} without source maps…`);let discovered=state.uploadSourceMaps?await discoverUploadableSourceMaps(state):{files:[],missingSourceMapCount:0,sourceFileCount:0};if(discovered){try{await publishRelease(state,discovered)}catch(error){logDiagnostic(logger,diagnoseRelease(DIAGNOSTIC_CONTEXT,error))}state.uploadComplete||logCleanupFailures(logger,await cleanupSourceMaps(discovered.files))}}async function releaseBuildWhenReady(state){if(isReleaseReady(state)){if(shouldWaitForServerOutput(state)){state.logger.info(`[interfere] waiting for server build output in ${path.relative(state.projectDir,state.serverSourcemapsDir)} before uploading source maps.`);return}state.releaseStarted=!0,await releaseBuild(state)}}function resolveReleaseEnv(config){return{...loadEnv(config.mode,config.envDir,``),...process.env}}function buildPluginState(options,config,browser){let{logger}=config,isProductionBuild=config.command===`build`,releaseEnv=resolveReleaseEnv(config),apiKey=pickEnvValue(releaseEnv,`INTERFERE_API_KEY`),apiUrl=pickEnvValue(releaseEnv,`INTERFERE_API_URL`)??API_URL,retainSourceMaps=isProductionBuild&&options.sourceMaps?.retain===!0,release=isProductionBuild&&!retainSourceMaps?checkReleaseConfig(DIAGNOSTIC_CONTEXT,{apiKey,commitSha:browser.commitSha,releaseSlug:browser.releaseSlug}):null;if(release?.status===`fail`)throw buildError(release.title,release.details);let releaseEnabled=release?.status===`ok`,uploadOutDir=path.resolve(config.root,config.build.outDir),serverSourcemapsDir=options.serverSourcemapsDir?path.resolve(config.root,options.serverSourcemapsDir):path.join(uploadOutDir,`server`),serverBuild=hasServerRuntimePlugin(config.plugins.map(p=>p.name))||!!config.build.ssr;return{apiHost:browser.apiHost,apiKey,apiUrl,buildStartedAt:Math.floor(Date.now()/1e3)*1e3,commitSha:browser.commitSha,releaseEnabled,retainSourceMaps,skippedRelease:release?.status===`skip`?release:null,uploadSourceMaps:releaseEnabled&&options.sourceMaps?.upload!==!1,environment:browser.environment,isProductionBuild,logger,projectDir:config.root,publicKey:browser.publicKey,releaseSlug:browser.releaseSlug,serverBuild,serverSourcemapsDir,uploadOutDir,releaseStarted:!1,uploadComplete:!1}}function announceSetup(state){if(state.logger.info(`[interfere] ingest → ${state.apiHost??`Interfere (default)`}`),!state.apiHost&&state.serverBuild&&state.logger.warn(`[interfere] Telemetry posts directly to Interfere. On a server-rendered app, set interfere({ apiHost }) to a subdomain of your own site to survive ad-blockers.`),state.isProductionBuild&&!state.publicKey&&state.logger.warn(`[interfere] VITE_INTERFERE_PUBLIC_KEY is not set, so this build sends no events. Set it on builds that you deploy.`),state.retainSourceMaps&&state.logger.info(`[interfere] source maps are retained in the build output for a release step to upload per environment.`),state.skippedRelease)for(let line of[state.skippedRelease.title,...state.skippedRelease.details])state.logger.warn(`[interfere] ${line}`)}function mirrorPublicKeyForDevServer(command,env){if(command!==`serve`)return;let publicKey=env.VITE_INTERFERE_PUBLIC_KEY;publicKey&&!process.env.VITE_INTERFERE_PUBLIC_KEY&&(process.env.VITE_INTERFERE_PUBLIC_KEY=publicKey)}function resolveRoot(userConfig){return userConfig.root?path.resolve(process.cwd(),userConfig.root):process.cwd()}function resolveEnvDir(userConfig){let root=resolveRoot(userConfig);return userConfig.envDir===!1||userConfig.envDir===void 0||userConfig.envDir===``?root:path.resolve(root,userConfig.envDir)}function forceSourcemapForUpload(userConfig,options,command,env){if(!(command===`build`&&(options.sourceMaps?.retain===!0||options.sourceMaps?.upload!==!1&&pickEnvValue(env,`INTERFERE_API_KEY`))))return;let previous=userConfig.build?.sourcemap;return userConfig.build={...userConfig.build,sourcemap:!0},previous===void 0||previous===!0?void 0:previous}function carryOverUploadProgress(previous,next){previous?.serverBuild&&next.serverBuild&&(next.uploadOutDir=previous.uploadOutDir,next.serverSourcemapsDir=previous.serverSourcemapsDir,next.releaseStarted=previous.releaseStarted,next.uploadComplete=previous.uploadComplete,next.buildStartedAt=previous.buildStartedAt)}function warnForcedSourcemap(logger,overridden){overridden!==void 0&&logger.warn(`[interfere] Forced build.sourcemap to true (was ${JSON.stringify(overridden)}). Source maps are required to symbolicate errors; they are uploaded then deleted from the build output, so they are never served. Pass interfere({ sourceMaps: { upload: false } }) to opt out.`)}function disabledPlugin(){return{name:`interfere`,config(){return{define:{"import.meta.env.VITE_INTERFERE_DISABLED":JSON.stringify(`true`)},ssr:{noExternal:[`@interfere/vite`]}}}}}function interfere(options={}){if(rejectRemovedOptions(options),options.disable)return disabledPlugin();let receipt=options.buildReceipt?createBuildReceipt(options.buildReceipt):void 0,remoteConfigFallback=!1,releaseConfig=null,missingBinding=null,bindingWarningLogged=!1,resolveBuildConfig=createBuildConfigResolver(result=>{receipt?.observeConfig(result),remoteConfigFallback||=result.status===`fallback`}),state=null,overriddenSourcemap,announced=!1,uploadAfterAppBuild=!1,writeReceipt=async()=>{try{await receipt?.write()}catch{state?.logger.warn(`[interfere] Private build receipt could not be written.`)}};return{buildApp:{order:`post`,async handler(builder){if(!state)return;uploadAfterAppBuild=!0;let environments=Object.values(builder.environments);if(environments.every(environment=>!environment.isBuilt))for(let environment of environments)await builder.build(environment);isReleaseReady(state)&&(state.releaseStarted=!0,await releaseBuild(state)),await writeReceipt()}},buildStart(){state&&!announced&&(announceSetup(state),warnForcedSourcemap(state.logger,overriddenSourcemap),remoteConfigFallback&&state.logger.warn(`[interfere] Could not load SDK settings from Interfere. The build uses the default settings.`),announced=!0)},async closeBundle(){state&&!uploadAfterAppBuild&&await releaseBuildWhenReady(state),await writeReceipt()},async writeBundle(output,bundle){output.dir&&await receipt?.recordOutput(output.dir,Object.keys(bundle))},outputOptions(output){return state?.uploadSourceMaps||state?.retainSourceMaps?{...output,sourcemapDebugIds:!1}:null},renderChunk:{order:`post`,handler(code){return state?.uploadSourceMaps||state?.retainSourceMaps?renderDebugId(code):null}},async config(userConfig,{command,mode}){let env=loadEnv(mode,resolveEnvDir(userConfig),``);mirrorPublicKeyForDevServer(command,env);let browser=resolveBrowserConfig(options,env,command,resolveRoot(userConfig)),releaseEnv={...env,...process.env},release=checkReleaseConfig(DIAGNOSTIC_CONTEXT,{apiKey:pickEnvValue(releaseEnv,`INTERFERE_API_KEY`),commitSha:browser.commitSha,releaseSlug:browser.releaseSlug});if(command===`build`&&options.sourceMaps?.retain!==!0&&release.status===`ok`&&browser.publicKey&&(releaseConfig=await preflightViteBinding({apiUrl:pickEnvValue(releaseEnv,`INTERFERE_API_URL`)??API_URL,apiKey:release.apiKey,publicKey:browser.publicKey}),missingBinding=releaseConfig&&missingReleaseBinding(releaseConfig),missingBinding))return state=null,{define:{"import.meta.env.VITE_INTERFERE_DISABLED":`"true"`,"import.meta.env.VITE_INTERFERE_PUBLIC_KEY":`""`,"import.meta.env.VITE_INTERFERE_RELEASE_SLUG":`null`},ssr:{noExternal:[`@interfere/vite`]}};overriddenSourcemap=forceSourcemapForUpload(userConfig,options,command,env);let baked=command===`build`&&browser.publicKey&&!isTestEnv()?await resolveBuildConfig(browser.apiHost??API_URL,browser.publicKey):null;return{define:omitUndefined({...browserDefine(browser),...remoteConfigDefine(baked)}),ssr:{noExternal:[`@interfere/vite`]}}},async configResolved(config){if(missingBinding){bindingWarningLogged||=(config.logger.warn(`[interfere] ${missingBinding}`),!0);return}let browser=resolveBrowserConfig(options,config.env,config.command,config.root),next=buildPluginState(options,config,browser);carryOverUploadProgress(state,next),state={...next,receipt,releaseConfig:releaseConfig??void 0},uploadAfterAppBuild||=config.builder!==void 0,config.command===`build`&&await receipt?.configure(config,{commitSha:browser.commitSha,releaseSlug:browser.releaseSlug,environment:browser.environment,publicKey:browser.publicKey,apiOrigin:browser.apiHost??API_URL,producerVersion:PRODUCER_VERSION})},enforce:`pre`,name:`interfere`,sharedDuringBuild:!0}}export{interfere};
package/dist/release.mjs CHANGED
@@ -1 +1 @@
1
- import{PRODUCER_VERSION}from"./version.mjs";import{runReleasePipeline}from"./internal/build/pipeline.mjs";import{discoverByPairing}from"./internal/build/source-maps.mjs";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{InterfereClient,InterfereError}from"@interfere/javascript";import{API_URL,PUBLIC_KEY_HEADER}from"@interfere/constants/api";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import path from"node:path";import{readFile}from"node:fs/promises";import{z}from"zod";const receiptSchema=z.object({build:z.object({buildId:z.string().nullable(),commitSha:z.string().nullable(),releaseSlug:releaseSlugSchema.nullable()}),schemaVersion:z.literal(1)}),releaseUploadConflictSchema=z.object({code:z.literal(`RELEASE_UPLOAD_CONFLICT`)});async function readReceipt(projectDir,receipt){return receiptSchema.parse(JSON.parse(await readFile(path.resolve(projectDir,receipt),`utf8`)))}async function discover(distDir,projectDir){return discoverByPairing({distDir,projectDir,publicPathRewrites:[{from:`public`,to:``}],subtrees:[`.`]})}async function releaseBuildOutput(options){let logger=options.logger??console,projectDir=options.projectDir??process.cwd(),discovered=await discover(options.distDir,projectDir),finish=async result=>{for(let failure of await cleanupSourceMaps(discovered.files))logger.warn(`[interfere] cleanup left ${failure.absolute} on disk: ${failure.code??failure.message}`);return result};if(!options.apiKey)return finish({reason:`INTERFERE_API_KEY is not set; retained source maps were deleted without a release.`,status:`skipped`});let{build}=await readReceipt(projectDir,options.receipt);if(!build.releaseSlug||!build.commitSha)return finish({reason:`the build receipt has no release identity (no commit SHA at build time).`,status:`skipped`});if(discovered.files.length===0)return finish({reason:`no source maps paired with chunks under ${options.distDir}.`,status:`skipped`});let environment=options.environment===void 0?resolveDeployEnvironment(process.env):options.environment,client=new InterfereClient({apiKey:{token:options.apiKey},baseUrl:options.apiUrl||API_URL,fetch:sourceMapUploadFetch,headers:{"user-agent":PRODUCER_VERSION,[PUBLIC_KEY_HEADER]:options.publicKey},maxRetries:4,timeoutInSeconds:10});try{let releasesConfig=await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)}),result=await runReleasePipeline({buildId:build.buildId??build.commitSha,bundler:`rollup`,client,discovery:discovered,environment,producerVersion:PRODUCER_VERSION,releaseSlug:build.releaseSlug,releasesConfig,uploadSourceMaps:!0});if(!result.ok)return await finish({reason:`release skipped: ${result.failure.kind} for ${result.failure.config.org.slug}/${result.failure.config.surface.slug}.`,status:`skipped`});for(let failure of result.data.cleanupFailures)logger.warn(`[interfere] cleanup left ${failure.absolute} on disk: ${failure.code??failure.message}`);return logger.info(`[interfere] release ${result.data.releaseSlug} confirmed for ${environment??`no environment`}: ${result.data.fileCount} maps, ${result.data.timing?.total??0}ms.`),{fileCount:result.data.fileCount,releaseSlug:result.data.releaseSlug,status:`released`}}catch(error){return error instanceof InterfereError&&error.statusCode===409&&releaseUploadConflictSchema.safeParse(error.body).success?await finish({reason:`release ${build.releaseSlug} for ${environment??`no environment`} already holds source maps from another build of this bundle (receipt commit ${build.commitSha}); keeping the existing release.`,status:`skipped`}):await finish({message:error instanceof Error?error.message:String(error),status:`failed`})}}export{releaseBuildOutput};
1
+ import{PRODUCER_VERSION}from"./version.mjs";import{runReleasePipeline}from"./internal/build/pipeline.mjs";import{discoverByPairing}from"./internal/build/source-maps.mjs";import{API_URL,PUBLIC_KEY_HEADER}from"@interfere/constants/api";import{InterfereClient,InterfereError}from"@interfere/javascript";import{cleanupSourceMaps}from"@interfere/build/cleanup-source-maps";import{sourceMapUploadFetch}from"@interfere/build/source-map-upload-fetch";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import path from"node:path";import{readFile}from"node:fs/promises";import{z}from"zod";const receiptSchema=z.object({build:z.object({buildId:z.string().nullable(),commitSha:z.string().nullable(),releaseSlug:releaseSlugSchema.nullable()}),schemaVersion:z.literal(1)}),releaseUploadConflictSchema=z.object({code:z.literal(`RELEASE_UPLOAD_CONFLICT`)});async function readReceipt(projectDir,receipt){return receiptSchema.parse(JSON.parse(await readFile(path.resolve(projectDir,receipt),`utf8`)))}async function discover(distDir,projectDir){return discoverByPairing({distDir,projectDir,publicPathRewrites:[{from:`public`,to:``}],subtrees:[`.`]})}async function releaseBuildOutput(options){let logger=options.logger??console,projectDir=options.projectDir??process.cwd(),discovered=await discover(options.distDir,projectDir),finish=async result=>{for(let failure of await cleanupSourceMaps(discovered.files))logger.warn(`[interfere] cleanup left ${failure.absolute} on disk: ${failure.code??failure.message}`);return result};if(!options.apiKey)return finish({reason:`INTERFERE_API_KEY is not set; retained source maps were deleted without a release.`,status:`skipped`});let{build}=await readReceipt(projectDir,options.receipt);if(!build.releaseSlug||!build.commitSha)return finish({reason:`the build receipt has no release identity (no commit SHA at build time).`,status:`skipped`});if(discovered.files.length===0)return finish({reason:`no source maps paired with chunks under ${options.distDir}.`,status:`skipped`});let environment=options.environment===void 0?resolveDeployEnvironment(process.env):options.environment,client=new InterfereClient({apiKey:{token:options.apiKey},baseUrl:options.apiUrl||API_URL,fetch:sourceMapUploadFetch,headers:{"user-agent":PRODUCER_VERSION,[PUBLIC_KEY_HEADER]:options.publicKey},maxRetries:4,timeoutInSeconds:10});try{let releasesConfig=await client.releases.getReleasesConfig({abortSignal:AbortSignal.timeout(6e4)}),result=await runReleasePipeline({buildId:build.buildId??build.commitSha,bundler:`rollup`,client,discovery:discovered,environment,producerVersion:PRODUCER_VERSION,releaseSlug:build.releaseSlug,releasesConfig,uploadSourceMaps:!0});if(!result.ok)return await finish({reason:`release skipped: ${result.failure.kind} for ${result.failure.config.org.slug}/${result.failure.config.surface.slug}.`,status:`skipped`});for(let failure of result.data.cleanupFailures)logger.warn(`[interfere] cleanup left ${failure.absolute} on disk: ${failure.code??failure.message}`);return logger.info(`[interfere] release ${result.data.releaseSlug} confirmed for ${environment??`no environment`}: ${result.data.fileCount} maps, ${result.data.timing?.total??0}ms.`),{fileCount:result.data.fileCount,releaseSlug:result.data.releaseSlug,status:`released`}}catch(error){return error instanceof InterfereError&&error.statusCode===409&&releaseUploadConflictSchema.safeParse(error.body).success?await finish({reason:`release ${build.releaseSlug} for ${environment??`no environment`} already holds source maps from another build of this bundle (receipt commit ${build.commitSha}); keeping the existing release.`,status:`skipped`}):await finish({message:error instanceof Error?error.message:String(error),status:`failed`})}}export{releaseBuildOutput};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/vite",
3
- "version": "12.4.2",
3
+ "version": "12.4.4",
4
4
  "description": "Vite build plugin and client + server initialization for Interfere.",
5
5
  "keywords": [
6
6
  "error-tracking",
@@ -68,12 +68,12 @@
68
68
  "typecheck": "bun check"
69
69
  },
70
70
  "dependencies": {
71
- "@interfere/build": "^0.4.3",
72
- "@interfere/constants": "^9.3.0",
73
- "@interfere/helpers": "^9.3.0",
74
- "@interfere/javascript": "^0.3.1",
75
- "@interfere/react": "^12.4.2",
76
- "@interfere/types": "^10.6.1",
71
+ "@interfere/build": "^0.4.5",
72
+ "@interfere/constants": "^9.3.1",
73
+ "@interfere/helpers": "^9.3.1",
74
+ "@interfere/javascript": "^0.3.3",
75
+ "@interfere/react": "^12.4.4",
76
+ "@interfere/types": "^10.6.3",
77
77
  "@opentelemetry/api": "1.9.1",
78
78
  "@opentelemetry/api-logs": "0.222.0",
79
79
  "@opentelemetry/baggage-span-processor": "0.5.0",
@@ -92,7 +92,7 @@
92
92
  "zod": "4.6.6"
93
93
  },
94
94
  "devDependencies": {
95
- "@interfere/test-utils": "^10.0.2",
95
+ "@interfere/test-utils": "^10.0.3",
96
96
  "@interfere/typescript-config": "^9.0.2",
97
97
  "@interfere/vitest-config": "0.0.0",
98
98
  "@jridgewell/trace-mapping": "0.3.31",