@interfere/cli 0.0.2-canary.0 → 0.1.0-canary.2

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
@@ -1,23 +1,143 @@
1
1
  # @interfere/cli
2
2
 
3
- > **Status: canary.** Pre-1.0 surface, expect breaking changes. Pin to a specific `10.0.0-canary.N` in CI.
4
-
5
- Command-line tool for [Interfere](https://interfere.com) release workflows.
3
+ Command-line tool for [Interfere](https://interfere.com) release workflows. Uploads source maps and registers releases so the collector accepts spans from your deployments.
6
4
 
7
5
  ## Install
8
6
 
9
7
  ```sh
8
+ npm install -D @interfere/cli
9
+ # or
10
10
  bun add -d @interfere/cli
11
+ # or
12
+ pnpm add -D @interfere/cli
11
13
  ```
12
14
 
13
15
  ## Usage
14
16
 
15
- After your build step, publish release metadata:
17
+ Run after your build step to upload source maps and preflight the release:
16
18
 
17
19
  ```sh
18
- INTERFERE_API_KEY=interfere_ak_… bunx interfere sourcemaps upload ./dist
20
+ interfere sourcemaps upload ./dist
21
+ ```
22
+
23
+ Typically added as a `postbuild` script in `package.json`:
24
+
25
+ ```json
26
+ {
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "postbuild": "interfere sourcemaps upload ./dist"
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### What it does
35
+
36
+ 1. Auto-detects the CI runner and extracts commit SHA, branch, run ID, and run URL
37
+ 2. Derives a release slug from the commit SHA (same algorithm the SDK uses at runtime)
38
+ 3. Registers the release with the collector (`POST /v1/releases/`)
39
+ 4. Discovers `.js.map` files in the target directory
40
+ 5. Uploads source maps via presigned URLs
41
+ 6. Marks the release as preflight-confirmed so the collector accepts spans for it
42
+
43
+ ### Options
44
+
45
+ ```text
46
+ interfere sourcemaps upload --source-maps <dir> [options]
47
+
48
+ Options:
49
+ --source-maps <dir> Directory containing .js.map files (required)
50
+ --release <slug> Override the release slug (default: derived from commit SHA)
51
+ --url <api-url> Interfere collector URL (default: $INTERFERE_API_URL)
52
+ --api-key <key> Interfere API key (default: $INTERFERE_API_KEY)
53
+ --commit-sha <sha> Commit SHA (default: auto-detected from CI or git)
54
+ --commit-ref <ref> Branch or tag ref (default: auto-detected from CI or git)
55
+ --build-id <id> Build identifier (default: same as --commit-sha)
56
+ --runner <name> CI runner name override (default: auto-detected)
57
+ --run-id <id> CI run ID override (default: auto-detected)
58
+ --run-url <url> CI run URL override (default: auto-detected)
59
+ --skip-preflight Don't mark the release preflight-confirmed after upload
60
+ --dry-run Print what would be sent without making API calls
61
+ --json Output results as JSON
62
+ -h, --help Show help
63
+ --version Show version
19
64
  ```
20
65
 
21
- Slug is auto-derived from the commit SHA (same algorithm the SDK uses at runtime). Full docs at <https://interfere.com/docs>.
66
+ ## CI Auto-Detection
67
+
68
+ The CLI automatically detects the CI runner and extracts metadata from environment variables. No flags are needed when running in a supported CI environment.
69
+
70
+ | Runner | Detection | Commit SHA | Branch | Run ID | Run URL |
71
+ |---|---|---|---|---|---|
72
+ | GitHub Actions | `GITHUB_ACTIONS` | `GITHUB_SHA` | `GITHUB_HEAD_REF` / `GITHUB_REF_NAME` | `GITHUB_RUN_ID` | Constructed from `GITHUB_SERVER_URL` + `GITHUB_REPOSITORY` |
73
+ | CircleCI | `CIRCLECI` | `CIRCLE_SHA1` | `CIRCLE_BRANCH` | `CIRCLE_BUILD_NUM` | `CIRCLE_BUILD_URL` |
74
+ | GitLab CI | `GITLAB_CI` | `CI_COMMIT_SHA` | `CI_COMMIT_REF_NAME` | `CI_PIPELINE_ID` | `CI_PIPELINE_URL` |
75
+ | Buildkite | `BUILDKITE` | `BUILDKITE_COMMIT` | `BUILDKITE_BRANCH` | `BUILDKITE_BUILD_NUMBER` | `BUILDKITE_BUILD_URL` |
76
+ | Manual | (none) | `git rev-parse HEAD` | - | - | - |
77
+
78
+ When no CI is detected, the CLI falls back to `git rev-parse HEAD` for the commit SHA. Pass `--commit-sha` explicitly if running outside a git repository.
79
+
80
+ > **Note (GitHub Actions):** on `pull_request` events, `GITHUB_SHA` is the ephemeral merge commit, not the head commit of your branch. Releases registered from PR builds will carry a SHA that exists on no branch. Run the upload on `push`/deploy workflows, or pass `--commit-sha ${{ github.event.pull_request.head.sha }}` explicitly.
81
+
82
+ ## Environment Variables
83
+
84
+ | Variable | Required | Purpose |
85
+ |---|---|---|
86
+ | `INTERFERE_API_KEY` | Yes | Authenticates with the collector. Format: `interfere_ak_*` |
87
+ | `INTERFERE_SOURCE_ID` | No | Override the commit SHA used to derive the release slug. Falls back to CI-detected SHA or `git rev-parse HEAD`. |
88
+ | `INTERFERE_API_URL` | No | Override the default collector URL |
89
+
90
+ ## How release identity works
91
+
92
+ The CLI and the SDK independently derive the same release slug from the commit SHA using a shared `deriveReleaseSlug()` function. For this to work, both must see the same SHA:
93
+
94
+ - **At build time (CLI):** reads from `INTERFERE_SOURCE_ID`, CI env vars, or `git rev-parse HEAD`
95
+ - **At runtime (SDK):** reads from the same env vars or git
96
+
97
+ In Docker deployments where `.git` isn't in the image, set `INTERFERE_SOURCE_ID` in the runtime environment to the same commit SHA used during the build.
22
98
 
23
- For SDK 0.x migrations, keep the `INTERFERE_API_KEY` env var name but replace old `ak_*` values with the `interfere_ak_*` Interfere API key from the dashboard.
99
+ ## Examples
100
+
101
+ ### GitHub Actions
102
+
103
+ ```yaml
104
+ - name: Build
105
+ run: npm run build
106
+
107
+ - name: Upload source maps
108
+ run: npx interfere sourcemaps upload ./dist
109
+ env:
110
+ INTERFERE_API_KEY: ${{ secrets.INTERFERE_API_KEY }}
111
+ # Runner, commit SHA, branch, run ID, and run URL are auto-detected
112
+ ```
113
+
114
+ ### CircleCI
115
+
116
+ ```yaml
117
+ - run:
118
+ name: Upload source maps
119
+ command: npx interfere sourcemaps upload ./dist
120
+ # CIRCLECI, CIRCLE_SHA1, etc. are auto-detected
121
+ ```
122
+
123
+ ### Dry Run
124
+
125
+ Preview the release payload and the source maps that would be uploaded, without making API calls:
126
+
127
+ ```sh
128
+ interfere sourcemaps upload ./dist --dry-run --json
129
+ ```
130
+
131
+ ### Docker
132
+
133
+ ```dockerfile
134
+ ARG GIT_SHA
135
+ ENV INTERFERE_SOURCE_ID=$GIT_SHA
136
+
137
+ COPY dist/ ./dist/
138
+ CMD ["node", "dist/main.js"]
139
+ ```
140
+
141
+ ```sh
142
+ docker build --build-arg GIT_SHA=$(git rev-parse HEAD) .
143
+ ```
package/dist/bin.mjs ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import{API_URL}from"@interfere/constants/api";import{join,relative,resolve}from"node:path";import{readFileSync}from"node:fs";import{readdir}from"node:fs/promises";import{parseArgs}from"node:util";import{createReleaseResponse}from"@interfere/types/releases/definition";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{execFileSync}from"node:child_process";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{createHash,randomUUID}from"node:crypto";async function discoverSourceMaps(root){let found=[];return await walk(root,root,found),found}async function walk(root,dir,out){let entries=await readdir(dir,{withFileTypes:!0});for(let entry of entries){let abs=join(dir,entry.name);if(entry.isDirectory()){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;await walk(root,abs,out);continue}entry.name.endsWith(`.js.map`)&&out.push({absPath:abs,relPath:relative(root,abs),content:readFileSync(abs,`utf8`)})}}var name=`@interfere/cli`,version=`0.1.0-canary.2`;const VERSION=version,PRODUCER_VERSION=`${name}@${version}`,HELP=`
3
+ interfere ${VERSION}
4
+
5
+ Usage:
6
+ interfere sourcemaps upload --source-maps <dir> [options]
7
+ interfere --help
8
+ interfere --version
9
+
10
+ Options:
11
+ --source-maps <dir> Directory containing .js.map files (required)
12
+ --release <slug> Release slug (default: derived from commit SHA)
13
+ --url <api-url> Interfere collector URL (default: $INTERFERE_API_URL)
14
+ --api-key <key> Interfere API key (default: $INTERFERE_API_KEY)
15
+ --auth-token <key> Deprecated alias for --api-key
16
+ --commit-sha <sha> Commit SHA (default: auto-detected from CI or git)
17
+ --commit-ref <ref> Branch or tag ref (default: auto-detected from CI or git)
18
+ --build-id <id> Build identifier (default: same as --commit-sha)
19
+ --runner <name> CI runner name override (default: auto-detected)
20
+ --run-id <id> CI run ID override (default: auto-detected)
21
+ --run-url <url> CI run URL override (default: auto-detected)
22
+ --environment <env> Deployment environment (e.g. production, preview)
23
+ --deployment-url <u> URL of the deployed application (optional)
24
+ --skip-preflight Don't mark the release preflight-confirmed after publishing metadata
25
+ --dry-run Print what would be sent without making API calls
26
+ --json Output results as JSON
27
+ -h, --help Show this help
28
+ --version Show version
29
+
30
+ Environment:
31
+ INTERFERE_API_KEY Interfere API key in the interfere_ak_* format
32
+ INTERFERE_API_URL Override the default collector URL
33
+ INTERFERE_SOURCE_ID Override the commit SHA used to derive the slug.
34
+ Falls back to VERCEL_GIT_COMMIT_SHA, GITHUB_SHA,
35
+ or \`git rev-parse HEAD\`.
36
+
37
+ CI Auto-Detection:
38
+ GitHub Actions GITHUB_ACTIONS, GITHUB_SHA, GITHUB_REF_NAME, GITHUB_RUN_ID
39
+ CircleCI CIRCLECI, CIRCLE_SHA1, CIRCLE_BRANCH, CIRCLE_BUILD_NUM
40
+ GitLab CI GITLAB_CI, CI_COMMIT_SHA, CI_COMMIT_REF_NAME, CI_PIPELINE_ID
41
+ Buildkite BUILDKITE, BUILDKITE_COMMIT, BUILDKITE_BRANCH, BUILDKITE_BUILD_NUMBER
42
+ `,OPTIONS={release:{type:`string`},url:{type:`string`},"api-key":{type:`string`},"auth-token":{type:`string`},"commit-sha":{type:`string`},"commit-ref":{type:`string`},"build-id":{type:`string`},runner:{type:`string`},"run-id":{type:`string`},"run-url":{type:`string`},"source-maps":{type:`string`},environment:{type:`string`},"deployment-url":{type:`string`},"skip-preflight":{type:`boolean`},"dry-run":{type:`boolean`},json:{type:`boolean`},help:{type:`boolean`,short:`h`},version:{type:`boolean`}};function parseFlags(argv){let values,positionals;try{({values,positionals}=parseArgs({args:[...argv],options:OPTIONS,allowPositionals:!0}))}catch(error){process.stderr.write(`interfere: ${error instanceof Error?error.message:String(error)}\n`),process.exit(1)}values.help&&(process.stdout.write(`${HELP.trim()}\n`),process.exit(0)),values.version&&(process.stdout.write(`${VERSION}\n`),process.exit(0));let str=key=>typeof values[key]==`string`?values[key]:null;return{apiKey:str(`api-key`)??str(`auth-token`),buildId:str(`build-id`),commitRef:str(`commit-ref`),commitSha:str(`commit-sha`),deploymentUrl:str(`deployment-url`),dryRun:values[`dry-run`]===!0,environment:str(`environment`),json:values.json===!0,positional:positionals,release:str(`release`),runId:str(`run-id`),runner:str(`runner`),runUrl:str(`run-url`),skipPreflight:values[`skip-preflight`]===!0,sourceMaps:str(`source-maps`),url:str(`url`)}}async function confirmPreflight({apiKey,apiUrl,releaseSlug}){let url=`${apiUrl}/v1/releases/${encodeURIComponent(releaseSlug)}/preflight`,response=await fetch(url,{method:`POST`,headers:{"content-type":`application/json`,"user-agent":PRODUCER_VERSION,Authorization:`Bearer ${apiKey}`}});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`POST ${url} → ${response.status} ${detail}`)}}function git(args){try{let out=execFileSync(`git`,[...args],{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return out.length>0?out:null}catch{return null}}function gitHead(){return git([`rev-parse`,`HEAD`])}async function ensureRelease({apiKey,apiUrl,buildId,commitSha,deploymentUrl,dryRun,environment,releaseSlug,runner}){let body={source:{provider:`cli`,commitSha,commitRef:runner.commitRef??resolveBranchFallback(),commitMessage:resolveCommitMessage(),commitUrl:null,runnerName:runner.runner===`manual`?null:runner.runner,runId:runner.runId,runUrl:runner.runUrl,environment:environment??null,deploymentUrl:deploymentUrl??null},destination:null,buildId,slug:releaseSlug,producerVersion:PRODUCER_VERSION};if(dryRun)return{body,response:null};let url=`${apiUrl}/v1/releases/`,res=await fetch(url,{method:`POST`,headers:{"content-type":`application/json`,accept:`application/json`,"user-agent":PRODUCER_VERSION,Authorization:`Bearer ${apiKey}`},body:JSON.stringify(body)});if(!res.ok){let detail=await res.text().catch(()=>``);throw Error(`POST ${url} → ${res.status} ${detail}`)}let parsed=createReleaseResponse.safeParse(await res.json());if(!parsed.success)throw Error(`POST ${url} → unexpected response shape: ${parsed.error.message}`);return{body,response:parsed.data}}function resolveCommitMessage(){return readFirstEnvValue(process.env,[`VERCEL_GIT_COMMIT_MESSAGE`])??git([`log`,`-1`,`--format=%s`])??``}function resolveBranchFallback(){return readFirstEnvValue(process.env,[`VERCEL_GIT_COMMIT_REF`])??git([`rev-parse`,`--abbrev-ref`,`HEAD`])??`unknown`}const detectors=[detectGitHubActions,detectSimpleRunner],SIMPLE_RUNNERS=[{signal:`CIRCLECI`,runner:`circleci`,runId:`CIRCLE_BUILD_NUM`,runUrl:`CIRCLE_BUILD_URL`,commitSha:`CIRCLE_SHA1`,commitRef:`CIRCLE_BRANCH`},{signal:`GITLAB_CI`,runner:`gitlab-ci`,runId:`CI_PIPELINE_ID`,runUrl:`CI_PIPELINE_URL`,commitSha:`CI_COMMIT_SHA`,commitRef:`CI_COMMIT_REF_NAME`},{signal:`BUILDKITE`,runner:`buildkite`,runId:`BUILDKITE_BUILD_NUMBER`,runUrl:`BUILDKITE_BUILD_URL`,commitSha:`BUILDKITE_COMMIT`,commitRef:`BUILDKITE_BRANCH`}];function detectRunner({env,gitFallback,overrides}){let result;for(let detect of detectors){let detected=detect(env);if(detected){result=detected;break}}let base=result??{runner:`manual`,runId:null,runUrl:null,commitSha:gitFallback(),commitRef:null,environment:null};return overrides?{...base,...overrides.runner?{runner:overrides.runner}:{},...overrides.runId?{runId:overrides.runId}:{},...overrides.runUrl?{runUrl:overrides.runUrl}:{},...overrides.commitSha?{commitSha:overrides.commitSha}:{},...overrides.commitRef?{commitRef:overrides.commitRef}:{}}:base}function detectGitHubActions(env){if(!env.GITHUB_ACTIONS)return null;let serverUrl=env.GITHUB_SERVER_URL??`https://github.com`,repo=env.GITHUB_REPOSITORY,runId=env.GITHUB_RUN_ID??null;return{runner:`github-actions`,runId,runUrl:repo&&runId?`${serverUrl}/${repo}/actions/runs/${runId}`:null,commitSha:env.GITHUB_SHA??null,commitRef:env.GITHUB_HEAD_REF||env.GITHUB_REF_NAME||null,environment:null}}function detectSimpleRunner(env){for(let spec of SIMPLE_RUNNERS)if(env[spec.signal])return{runner:spec.runner,runId:env[spec.runId]??null,runUrl:env[spec.runUrl]??null,commitSha:env[spec.commitSha]??null,commitRef:env[spec.commitRef]??null,environment:null};return null}function resolveReleaseSlug(commitSha){return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}function resolveApiKey(flags){let apiKey=flags.apiKey??process.env.INTERFERE_API_KEY;if(!apiKey){if(flags.dryRun)return``;die$1(`Missing API key. Set INTERFERE_API_KEY or pass --api-key.`)}return apiKey}function resolveRunner(flags){return detectRunner({env:process.env,gitFallback:gitHead,overrides:{runner:flags.runner,runId:flags.runId,runUrl:flags.runUrl,commitSha:flags.commitSha,commitRef:flags.commitRef}})}function resolveIdentity(flags,runner){let commitSha=flags.commitSha?runner.commitSha:readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runner.commitSha;commitSha||die$1(`Could not determine commit SHA. Pass --commit-sha, set INTERFERE_SOURCE_ID, or run inside a git repo.`);let releaseSlug=flags.release;if(!releaseSlug){let resolved=resolveReleaseSlug(commitSha);resolved.slug||die$1(`Could not derive a release slug from commit SHA. Pass --release explicitly.`),releaseSlug=resolved.slug}return{releaseSlug,commitSha,buildId:flags.buildId??commitSha}}function die$1(message){process.stderr.write(`interfere: ${message}\n`),process.exit(1)}const MAP_SUFFIX_RE=/\.map$/;function prepareEntries(maps){return maps.map(map=>{let debugId=randomUUID(),content=injectDebugId(map.content,debugId);return{relPath:map.relPath,chunkUrl:map.relPath.replace(MAP_SUFFIX_RE,``),debugId,content,contentBytes:Buffer.byteLength(content,`utf8`),hash:createHash(`sha256`).update(content).digest(`hex`)}})}function injectDebugId(rawMap,debugId){let parsed=JSON.parse(rawMap);return JSON.stringify({...parsed,debugId})}async function uploadSourceMaps({apiKey,apiUrl,maps,releaseSlug}){let entries=prepareEntries(maps),slug=encodeURIComponent(releaseSlug),signReq={files:entries.map(entry=>({path:entry.relPath,sizeBytes:entry.contentBytes,...richness(entry.content)}))},signRes=await postJson({apiKey,url:`${apiUrl}/v1/releases/${slug}/source-maps/sign`,body:signReq}),byPath=new Map(entries.map(entry=>[entry.relPath,entry])),totalBytes=0;await mapWithConcurrency(signRes.uploads,8,async upload=>{let entry=byPath.get(upload.path);if(!entry)throw Error(`Sign response referenced unknown path "${upload.path}"`);totalBytes+=entry.contentBytes,await putToR2(upload.presignedUrl,entry.content)});let completeReq={files:entries.map(entry=>({path:entry.relPath,hash:entry.hash,debugId:entry.debugId,chunkUrl:entry.chunkUrl})),sourceFileCount:entries.length,bundler:`tsc`};return{fileCount:(await postJson({apiKey,url:`${apiUrl}/v1/releases/${slug}/source-maps/complete`,body:completeReq})).fileCount,totalBytes}}function richness(content){let parsed=JSON.parse(content);return{hasSourcesContent:Array.isArray(parsed.sourcesContent)&&parsed.sourcesContent.length>0,hasNames:Array.isArray(parsed.names)&&parsed.names.length>0,hasFile:typeof parsed.file==`string`&&parsed.file.length>0,mappingsPresent:typeof parsed.mappings==`string`&&parsed.mappings.length>0}}async function postJson({apiKey,url,body}){let response=await fetch(url,{method:`POST`,headers:{"content-type":`application/json`,accept:`application/json`,"user-agent":PRODUCER_VERSION,Authorization:`Bearer ${apiKey}`},body:JSON.stringify(body)});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`POST ${url} → ${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(`PUT R2 → ${response.status} ${detail}`)}}async function mapWithConcurrency(items,concurrency,fn){let cursor=0;async function worker(){for(;cursor<items.length;){let item=items[cursor++];item!==void 0&&await fn(item)}}await Promise.all(Array.from({length:Math.min(concurrency,items.length)},()=>worker()))}function log(flags,message){flags.json||console.log(message)}function formatBytes(n){return n<1024?`${n} B`:n<1024*1024?`${(n/1024).toFixed(1)} KB`:`${(n/(1024*1024)).toFixed(1)} MB`}function die(message){process.stderr.write(`interfere: ${message}\n`),process.exit(1)}async function discoverMaps(absDir){try{return await discoverSourceMaps(absDir)}catch(error){die(`Could not read source maps directory ${absDir}: ${error instanceof Error?error.message:String(error)}`)}}function printSummary(flags,result){log(flags,`Done. Release ${result.slug} — ${result.files} file(s), ${formatBytes(result.bytes)}.`),result.url&&log(flags,result.url),flags.json&&process.stdout.write(`${JSON.stringify(result,null,2)}\n`)}async function sourcemapsUpload(dir,flags){let apiKey=resolveApiKey(flags),apiUrl=flags.url??process.env.INTERFERE_API_URL??API_URL,runner=resolveRunner(flags),{releaseSlug,commitSha,buildId}=resolveIdentity(flags,runner);flags.release||log(flags,`Derived release ${releaseSlug} from commit ${commitSha}`);let absDir=resolve(dir),maps=await discoverMaps(absDir),{body:releaseBody,response:releaseRes}=await ensureRelease({apiKey,apiUrl,buildId,commitSha,deploymentUrl:flags.deploymentUrl,dryRun:flags.dryRun,environment:flags.environment,releaseSlug,runner}),dashboardUrl=releaseRes?`https://app.interfere.com/~/${releaseRes.org.slug}/surfaces/${releaseRes.surface.slug}`:null;if(flags.dryRun){if(flags.json){let output={release:releaseBody,runner,files:maps.map(map=>map.relPath),dryRun:!0};process.stdout.write(`${JSON.stringify(output,null,2)}\n`)}else log(flags,`[dry-run] Release payload:`),log(flags,JSON.stringify(releaseBody,null,2)),log(flags,`[dry-run] Runner: ${runner.runner}`),log(flags,`[dry-run] Would upload ${maps.length} source map(s) from ${absDir}`);return}if(maps.length===0){log(flags,`No .js.map files found under ${absDir}`),flags.skipPreflight||await confirmPreflight({apiKey,apiUrl,releaseSlug}),printSummary(flags,{slug:releaseSlug,buildId,commitSha,runner:runner.runner,files:0,bytes:0,url:dashboardUrl});return}log(flags,`Uploading ${maps.length} source map(s) to ${releaseSlug}…`);let summary=await uploadSourceMaps({apiKey,apiUrl,maps,releaseSlug});flags.skipPreflight||await confirmPreflight({apiKey,apiUrl,releaseSlug}),printSummary(flags,{slug:releaseSlug,buildId,commitSha,runner:runner.runner,files:summary.fileCount,bytes:summary.totalBytes,url:dashboardUrl})}async function runCli(argv=process.argv.slice(2)){if(argv.length===0){parseFlags([`--help`]);return}let flags=parseFlags(argv),[command,sub,...rest]=flags.positional;if(command===`sourcemaps`&&sub===`upload`){let dir=flags.sourceMaps??rest[0];dir||die(`Missing source maps directory. Pass --source-maps <dir> or: interfere sourcemaps upload <dir>`),await sourcemapsUpload(dir,flags);return}die(`Unknown command: ${flags.positional.join(` `)||`(none)`}. Try --help.`)}await runCli();export{};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.mjs","names":["pkg.version","pkg.name","die"],"sources":["../src/discover.ts","../package.json","../src/internal/version.ts","../src/parse.ts","../src/preflight.ts","../src/internal/git.ts","../src/release.ts","../src/detect-runner.ts","../src/release-slug.ts","../src/resolve.ts","../src/upload.ts","../src/index.ts","../src/bin.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\nexport interface DiscoveredMap {\n /** Absolute path on disk. */\n readonly absPath: string;\n /** Raw .map file content. */\n readonly content: string;\n /** Path relative to the upload root, used for `chunkUrl` suffix-matching. */\n readonly relPath: string;\n}\n\n/**\n * Recursively walks `root` for `.js.map` files. We only handle JS source\n * maps here — `nest build` doesn't produce maps for any other asset class.\n */\nexport async function discoverSourceMaps(\n root: string\n): Promise<DiscoveredMap[]> {\n const found: DiscoveredMap[] = [];\n await walk(root, root, found);\n return found;\n}\n\nasync function walk(\n root: string,\n dir: string,\n out: DiscoveredMap[]\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) {\n continue;\n }\n await walk(root, abs, out);\n continue;\n }\n if (!entry.name.endsWith(\".js.map\")) {\n continue;\n }\n out.push({\n absPath: abs,\n relPath: relative(root, abs),\n content: readFileSync(abs, \"utf8\"),\n });\n }\n}\n","","import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const VERSION = pkg.version;\nexport const PRODUCER_VERSION = `${pkg.name}@${pkg.version}`;\n","import { parseArgs } from \"node:util\";\n\nimport { VERSION } from \"./internal/version.js\";\n\nconst HELP = `\ninterfere ${VERSION}\n\nUsage:\n interfere sourcemaps upload --source-maps <dir> [options]\n interfere --help\n interfere --version\n\nOptions:\n --source-maps <dir> Directory containing .js.map files (required)\n --release <slug> Release slug (default: derived from commit SHA)\n --url <api-url> Interfere collector URL (default: $INTERFERE_API_URL)\n --api-key <key> Interfere API key (default: $INTERFERE_API_KEY)\n --auth-token <key> Deprecated alias for --api-key\n --commit-sha <sha> Commit SHA (default: auto-detected from CI or git)\n --commit-ref <ref> Branch or tag ref (default: auto-detected from CI or git)\n --build-id <id> Build identifier (default: same as --commit-sha)\n --runner <name> CI runner name override (default: auto-detected)\n --run-id <id> CI run ID override (default: auto-detected)\n --run-url <url> CI run URL override (default: auto-detected)\n --environment <env> Deployment environment (e.g. production, preview)\n --deployment-url <u> URL of the deployed application (optional)\n --skip-preflight Don't mark the release preflight-confirmed after publishing metadata\n --dry-run Print what would be sent without making API calls\n --json Output results as JSON\n -h, --help Show this help\n --version Show version\n\nEnvironment:\n INTERFERE_API_KEY Interfere API key in the interfere_ak_* format\n INTERFERE_API_URL Override the default collector URL\n INTERFERE_SOURCE_ID Override the commit SHA used to derive the slug.\n Falls back to VERCEL_GIT_COMMIT_SHA, GITHUB_SHA,\n or \\`git rev-parse HEAD\\`.\n\nCI Auto-Detection:\n GitHub Actions GITHUB_ACTIONS, GITHUB_SHA, GITHUB_REF_NAME, GITHUB_RUN_ID\n CircleCI CIRCLECI, CIRCLE_SHA1, CIRCLE_BRANCH, CIRCLE_BUILD_NUM\n GitLab CI GITLAB_CI, CI_COMMIT_SHA, CI_COMMIT_REF_NAME, CI_PIPELINE_ID\n Buildkite BUILDKITE, BUILDKITE_COMMIT, BUILDKITE_BRANCH, BUILDKITE_BUILD_NUMBER\n`;\n\nexport interface Flags {\n apiKey: string | null;\n buildId: string | null;\n commitRef: string | null;\n commitSha: string | null;\n deploymentUrl: string | null;\n dryRun: boolean;\n environment: string | null;\n json: boolean;\n positional: string[];\n release: string | null;\n runId: string | null;\n runner: string | null;\n runUrl: string | null;\n skipPreflight: boolean;\n sourceMaps: string | null;\n url: string | null;\n}\n\nconst OPTIONS = {\n release: { type: \"string\" },\n url: { type: \"string\" },\n \"api-key\": { type: \"string\" },\n \"auth-token\": { type: \"string\" },\n \"commit-sha\": { type: \"string\" },\n \"commit-ref\": { type: \"string\" },\n \"build-id\": { type: \"string\" },\n runner: { type: \"string\" },\n \"run-id\": { type: \"string\" },\n \"run-url\": { type: \"string\" },\n \"source-maps\": { type: \"string\" },\n environment: { type: \"string\" },\n \"deployment-url\": { type: \"string\" },\n \"skip-preflight\": { type: \"boolean\" },\n \"dry-run\": { type: \"boolean\" },\n json: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\" },\n} as const;\n\nexport function parseFlags(argv: readonly string[]): Flags {\n let values: Record<string, string | boolean | undefined>;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: [...argv],\n options: OPTIONS,\n allowPositionals: true,\n }));\n } catch (error) {\n process.stderr.write(\n `interfere: ${error instanceof Error ? error.message : String(error)}\\n`\n );\n process.exit(1);\n }\n\n if (values[\"help\"]) {\n process.stdout.write(`${HELP.trim()}\\n`);\n process.exit(0);\n }\n if (values[\"version\"]) {\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n }\n\n const str = (key: string): string | null =>\n typeof values[key] === \"string\" ? (values[key] as string) : null;\n\n return {\n apiKey: str(\"api-key\") ?? str(\"auth-token\"),\n buildId: str(\"build-id\"),\n commitRef: str(\"commit-ref\"),\n commitSha: str(\"commit-sha\"),\n deploymentUrl: str(\"deployment-url\"),\n dryRun: values[\"dry-run\"] === true,\n environment: str(\"environment\"),\n json: values[\"json\"] === true,\n positional: positionals,\n release: str(\"release\"),\n runId: str(\"run-id\"),\n runner: str(\"runner\"),\n runUrl: str(\"run-url\"),\n skipPreflight: values[\"skip-preflight\"] === true,\n sourceMaps: str(\"source-maps\"),\n url: str(\"url\"),\n };\n}\n","import { PRODUCER_VERSION } from \"./internal/version.js\";\n\ninterface PreflightParams {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly releaseSlug: string;\n}\n\n/**\n * Marks the release as preflight-confirmed at the collector. After this\n * returns, OTLP traces and exception events for `releaseSlug` pass the\n * release-gate; before, they're rejected with `COLLECTOR_RELEASE_PREFLIGHT_UNCONFIRMED`.\n */\nexport async function confirmPreflight({\n apiKey,\n apiUrl,\n releaseSlug,\n}: PreflightParams): Promise<void> {\n const url = `${apiUrl}/v1/releases/${encodeURIComponent(releaseSlug)}/preflight`;\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"user-agent\": PRODUCER_VERSION,\n Authorization: `Bearer ${apiKey}`,\n },\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`POST ${url} → ${response.status} ${detail}`);\n }\n}\n","import { execFileSync } from \"node:child_process\";\n\nexport function git(args: readonly string[]): string | null {\n try {\n const out = execFileSync(\"git\", [...args], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return out.length > 0 ? out : null;\n } catch {\n return null;\n }\n}\n\nexport function gitHead(): string | null {\n return git([\"rev-parse\", \"HEAD\"]);\n}\n","import type {\n CreateReleaseRequest,\n CreateReleaseResponse,\n} from \"@interfere/types/releases/definition\";\nimport { createReleaseResponse } from \"@interfere/types/releases/definition\";\nimport { readFirstEnvValue } from \"@interfere/types/sdk/env\";\n\nimport type { RunnerInfo } from \"./detect-runner.js\";\nimport { git } from \"./internal/git.js\";\nimport { PRODUCER_VERSION } from \"./internal/version.js\";\n\ninterface EnsureReleaseParams {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly buildId: string;\n readonly commitSha: string;\n readonly deploymentUrl?: string | null;\n readonly dryRun?: boolean;\n readonly environment?: string | null;\n readonly releaseSlug: string;\n readonly runner: RunnerInfo;\n}\n\nexport interface EnsureReleaseResult {\n body: CreateReleaseRequest;\n response: CreateReleaseResponse | null;\n}\n\nexport async function ensureRelease({\n apiKey,\n apiUrl,\n buildId,\n commitSha,\n deploymentUrl,\n dryRun,\n environment,\n releaseSlug,\n runner,\n}: EnsureReleaseParams): Promise<EnsureReleaseResult> {\n const body: CreateReleaseRequest = {\n source: {\n provider: \"cli\",\n commitSha,\n commitRef: runner.commitRef ?? resolveBranchFallback(),\n commitMessage: resolveCommitMessage(),\n commitUrl: null,\n runnerName: runner.runner === \"manual\" ? null : runner.runner,\n runId: runner.runId,\n runUrl: runner.runUrl,\n environment: environment ?? null,\n deploymentUrl: deploymentUrl ?? null,\n },\n destination: null,\n buildId,\n slug: releaseSlug,\n producerVersion: PRODUCER_VERSION,\n };\n\n if (dryRun) {\n return { body, response: null };\n }\n\n const url = `${apiUrl}/v1/releases/`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n \"user-agent\": PRODUCER_VERSION,\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(`POST ${url} → ${res.status} ${detail}`);\n }\n const parsed = createReleaseResponse.safeParse(await res.json());\n if (!parsed.success) {\n throw new Error(\n `POST ${url} → unexpected response shape: ${parsed.error.message}`\n );\n }\n return { body, response: parsed.data };\n}\n\nfunction resolveCommitMessage(): string {\n return (\n readFirstEnvValue(process.env, [\"VERCEL_GIT_COMMIT_MESSAGE\"]) ??\n git([\"log\", \"-1\", \"--format=%s\"]) ??\n \"\"\n );\n}\n\nfunction resolveBranchFallback(): string {\n return (\n readFirstEnvValue(process.env, [\"VERCEL_GIT_COMMIT_REF\"]) ??\n git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]) ??\n \"unknown\"\n );\n}\n","export interface RunnerInfo {\n commitRef: string | null;\n commitSha: string | null;\n environment: null;\n runId: string | null;\n runner: string;\n runUrl: string | null;\n}\n\ntype Env = Record<string, string | undefined>;\ntype GitFallback = () => string | null;\n\nexport interface RunnerOverrides {\n commitRef?: string | null;\n commitSha?: string | null;\n runId?: string | null;\n runner?: string | null;\n runUrl?: string | null;\n}\n\ninterface DetectRunnerInput {\n env: Env;\n gitFallback: GitFallback;\n overrides?: RunnerOverrides;\n}\n\nconst detectors: readonly ((env: Env) => RunnerInfo | null)[] = [\n detectGitHubActions,\n detectSimpleRunner,\n];\n\nconst SIMPLE_RUNNERS: readonly {\n signal: string;\n runner: string;\n runId: string;\n runUrl: string;\n commitSha: string;\n commitRef: string;\n}[] = [\n {\n signal: \"CIRCLECI\",\n runner: \"circleci\",\n runId: \"CIRCLE_BUILD_NUM\",\n runUrl: \"CIRCLE_BUILD_URL\",\n commitSha: \"CIRCLE_SHA1\",\n commitRef: \"CIRCLE_BRANCH\",\n },\n {\n signal: \"GITLAB_CI\",\n runner: \"gitlab-ci\",\n runId: \"CI_PIPELINE_ID\",\n runUrl: \"CI_PIPELINE_URL\",\n commitSha: \"CI_COMMIT_SHA\",\n commitRef: \"CI_COMMIT_REF_NAME\",\n },\n {\n signal: \"BUILDKITE\",\n runner: \"buildkite\",\n runId: \"BUILDKITE_BUILD_NUMBER\",\n runUrl: \"BUILDKITE_BUILD_URL\",\n commitSha: \"BUILDKITE_COMMIT\",\n commitRef: \"BUILDKITE_BRANCH\",\n },\n];\n\nexport function detectRunner({\n env,\n gitFallback,\n overrides,\n}: DetectRunnerInput): RunnerInfo {\n let result: RunnerInfo | undefined;\n for (const detect of detectors) {\n const detected = detect(env);\n if (detected) {\n result = detected;\n break;\n }\n }\n const base: RunnerInfo = result ?? {\n runner: \"manual\",\n runId: null,\n runUrl: null,\n commitSha: gitFallback(),\n commitRef: null,\n environment: null,\n };\n if (!overrides) {\n return base;\n }\n return {\n ...base,\n ...(overrides.runner ? { runner: overrides.runner } : {}),\n ...(overrides.runId ? { runId: overrides.runId } : {}),\n ...(overrides.runUrl ? { runUrl: overrides.runUrl } : {}),\n ...(overrides.commitSha ? { commitSha: overrides.commitSha } : {}),\n ...(overrides.commitRef ? { commitRef: overrides.commitRef } : {}),\n };\n}\n\nfunction detectGitHubActions(env: Env): RunnerInfo | null {\n if (!env[\"GITHUB_ACTIONS\"]) {\n return null;\n }\n const serverUrl = env[\"GITHUB_SERVER_URL\"] ?? \"https://github.com\";\n const repo = env[\"GITHUB_REPOSITORY\"];\n const runId = env[\"GITHUB_RUN_ID\"] ?? null;\n return {\n runner: \"github-actions\",\n runId,\n runUrl: repo && runId ? `${serverUrl}/${repo}/actions/runs/${runId}` : null,\n commitSha: env[\"GITHUB_SHA\"] ?? null,\n commitRef: env[\"GITHUB_HEAD_REF\"] || env[\"GITHUB_REF_NAME\"] || null,\n environment: null,\n };\n}\n\nfunction detectSimpleRunner(env: Env): RunnerInfo | null {\n for (const spec of SIMPLE_RUNNERS) {\n if (!env[spec.signal]) {\n continue;\n }\n return {\n runner: spec.runner,\n runId: env[spec.runId] ?? null,\n runUrl: env[spec.runUrl] ?? null,\n commitSha: env[spec.commitSha] ?? null,\n commitRef: env[spec.commitRef] ?? null,\n environment: null,\n };\n }\n return null;\n}\n","import { deriveReleaseSlug } from \"@interfere/types/releases/slug\";\n\nexport function resolveReleaseSlug(commitSha: string | null): {\n commitSha: string | null;\n slug: string | null;\n} {\n return {\n commitSha,\n slug: commitSha ? deriveReleaseSlug(commitSha) : null,\n };\n}\n","import { releaseSourceIdEnvKeys } from \"@interfere/types/integrations\";\nimport { readFirstEnvValue } from \"@interfere/types/sdk/env\";\n\nimport { detectRunner } from \"./detect-runner.js\";\nimport { gitHead } from \"./internal/git.js\";\nimport type { Flags } from \"./parse.js\";\nimport { resolveReleaseSlug } from \"./release-slug.js\";\n\nexport function resolveApiKey(flags: Flags): string {\n const apiKey = flags.apiKey ?? process.env[\"INTERFERE_API_KEY\"];\n if (!apiKey) {\n if (flags.dryRun) {\n return \"\";\n }\n die(\"Missing API key. Set INTERFERE_API_KEY or pass --api-key.\");\n }\n return apiKey;\n}\n\nexport function resolveRunner(flags: Flags) {\n return detectRunner({\n env: process.env,\n gitFallback: gitHead,\n overrides: {\n runner: flags.runner,\n runId: flags.runId,\n runUrl: flags.runUrl,\n commitSha: flags.commitSha,\n commitRef: flags.commitRef,\n },\n });\n}\n\nexport function resolveIdentity(\n flags: Flags,\n runner: ReturnType<typeof resolveRunner>\n) {\n const commitSha = flags.commitSha\n ? runner.commitSha\n : (readFirstEnvValue(process.env, releaseSourceIdEnvKeys) ??\n runner.commitSha);\n\n if (!commitSha) {\n die(\n \"Could not determine commit SHA. Pass --commit-sha, set INTERFERE_SOURCE_ID, or run inside a git repo.\"\n );\n }\n\n let releaseSlug = flags.release;\n if (!releaseSlug) {\n const resolved = resolveReleaseSlug(commitSha);\n if (!resolved.slug) {\n die(\n \"Could not derive a release slug from commit SHA. Pass --release explicitly.\"\n );\n }\n releaseSlug = resolved.slug;\n }\n\n return { releaseSlug, commitSha, buildId: flags.buildId ?? commitSha };\n}\n\nfunction die(message: string): never {\n process.stderr.write(`interfere: ${message}\\n`);\n process.exit(1);\n}\n","import type {\n CompleteSourceMapsRequest,\n CompleteSourceMapsResponse,\n SignSourceMapsRequest,\n SignSourceMapsResponse,\n} from \"@interfere/types/data/source-maps\";\n\nimport { createHash, randomUUID } from \"node:crypto\";\n\nimport type { DiscoveredMap } from \"./discover.js\";\nimport { PRODUCER_VERSION } from \"./internal/version.js\";\n\nconst PUT_CONCURRENCY = 8;\nconst MAP_SUFFIX_RE = /\\.map$/;\n\ninterface PreparedEntry {\n /** Path to the sibling .js — `main.js.map` → `main.js`. Drives `chunkUrl`. */\n readonly chunkUrl: string;\n readonly content: string;\n readonly contentBytes: number;\n readonly debugId: string;\n readonly hash: string;\n readonly relPath: string;\n}\n\n/**\n * Generates a `debugId` per map and injects it into the JSON. The\n * collector's symbolicator suffix-matches incoming `frame.fileName`\n * against the manifest's `chunkUrl` to look up the debugId server-side,\n * so we don't need to also write the debugId comment back into the .js\n * file at the cost of mutating the deploy artifact.\n */\nfunction prepareEntries(maps: DiscoveredMap[]): PreparedEntry[] {\n return maps.map((map) => {\n const debugId = randomUUID();\n const content = injectDebugId(map.content, debugId);\n return {\n relPath: map.relPath,\n chunkUrl: map.relPath.replace(MAP_SUFFIX_RE, \"\"),\n debugId,\n content,\n contentBytes: Buffer.byteLength(content, \"utf8\"),\n hash: createHash(\"sha256\").update(content).digest(\"hex\"),\n };\n });\n}\n\nfunction injectDebugId(rawMap: string, debugId: string): string {\n const parsed = JSON.parse(rawMap) as Record<string, unknown>;\n return JSON.stringify({ ...parsed, debugId });\n}\n\ninterface UploadParams {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly maps: DiscoveredMap[];\n readonly releaseSlug: string;\n}\n\nexport interface UploadSummary {\n readonly fileCount: number;\n readonly totalBytes: number;\n}\n\nexport async function uploadSourceMaps({\n apiKey,\n apiUrl,\n maps,\n releaseSlug,\n}: UploadParams): Promise<UploadSummary> {\n const entries = prepareEntries(maps);\n const slug = encodeURIComponent(releaseSlug);\n\n const signReq: SignSourceMapsRequest = {\n files: entries.map((entry) => ({\n path: entry.relPath,\n sizeBytes: entry.contentBytes,\n ...richness(entry.content),\n })),\n };\n\n const signRes = await postJson<SignSourceMapsResponse>({\n apiKey,\n url: `${apiUrl}/v1/releases/${slug}/source-maps/sign`,\n body: signReq,\n });\n\n const byPath = new Map(entries.map((entry) => [entry.relPath, entry]));\n let totalBytes = 0;\n\n await mapWithConcurrency(signRes.uploads, PUT_CONCURRENCY, async (upload) => {\n const entry = byPath.get(upload.path);\n if (!entry) {\n throw new Error(`Sign response referenced unknown path \"${upload.path}\"`);\n }\n totalBytes += entry.contentBytes;\n await putToR2(upload.presignedUrl, entry.content);\n });\n\n const completeReq: CompleteSourceMapsRequest = {\n files: entries.map((entry) => ({\n path: entry.relPath,\n hash: entry.hash,\n debugId: entry.debugId,\n chunkUrl: entry.chunkUrl,\n })),\n sourceFileCount: entries.length,\n bundler: \"tsc\",\n };\n\n const completeRes = await postJson<CompleteSourceMapsResponse>({\n apiKey,\n url: `${apiUrl}/v1/releases/${slug}/source-maps/complete`,\n body: completeReq,\n });\n\n return { fileCount: completeRes.fileCount, totalBytes };\n}\n\ninterface RichnessFields {\n readonly hasFile: boolean;\n readonly hasNames: boolean;\n readonly hasSourcesContent: boolean;\n readonly mappingsPresent: boolean;\n}\n\nfunction richness(content: string): RichnessFields {\n const parsed = JSON.parse(content) as Record<string, unknown>;\n return {\n hasSourcesContent:\n Array.isArray(parsed[\"sourcesContent\"]) &&\n (parsed[\"sourcesContent\"] as unknown[]).length > 0,\n hasNames:\n Array.isArray(parsed[\"names\"]) &&\n (parsed[\"names\"] as unknown[]).length > 0,\n hasFile:\n typeof parsed[\"file\"] === \"string\" &&\n (parsed[\"file\"] as string).length > 0,\n mappingsPresent:\n typeof parsed[\"mappings\"] === \"string\" &&\n (parsed[\"mappings\"] as string).length > 0,\n };\n}\n\nasync function postJson<T>({\n apiKey,\n url,\n body,\n}: {\n apiKey: string;\n url: string;\n body: unknown;\n}): Promise<T> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n \"user-agent\": PRODUCER_VERSION,\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`POST ${url} → ${response.status} ${detail}`);\n }\n return (await response.json()) as T;\n}\n\nasync function putToR2(presignedUrl: string, content: string): Promise<void> {\n const response = await fetch(presignedUrl, {\n method: \"PUT\",\n headers: { \"content-type\": \"application/json\" },\n body: content,\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`PUT R2 → ${response.status} ${detail}`);\n }\n}\n\nasync function mapWithConcurrency<T>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T) => Promise<void>\n): Promise<void> {\n let cursor = 0;\n async function worker(): Promise<void> {\n while (cursor < items.length) {\n const i = cursor++;\n const item = items[i];\n if (item !== undefined) {\n await fn(item);\n }\n }\n }\n await Promise.all(\n Array.from({ length: Math.min(concurrency, items.length) }, () => worker())\n );\n}\n","import { API_URL } from \"@interfere/constants/api\";\n\nimport { resolve } from \"node:path\";\n\nimport type { DiscoveredMap } from \"./discover.js\";\nimport { discoverSourceMaps } from \"./discover.js\";\nimport type { Flags } from \"./parse.js\";\nimport { parseFlags } from \"./parse.js\";\nimport { confirmPreflight } from \"./preflight.js\";\nimport { ensureRelease } from \"./release.js\";\nimport { resolveApiKey, resolveIdentity, resolveRunner } from \"./resolve.js\";\nimport { uploadSourceMaps } from \"./upload.js\";\n\nfunction log(flags: Flags, message: string): void {\n if (!flags.json) {\n console.log(message);\n }\n}\n\nfunction formatBytes(n: number): string {\n if (n < 1024) {\n return `${n} B`;\n }\n if (n < 1024 * 1024) {\n return `${(n / 1024).toFixed(1)} KB`;\n }\n return `${(n / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction die(message: string): never {\n process.stderr.write(`interfere: ${message}\\n`);\n process.exit(1);\n}\n\nasync function discoverMaps(absDir: string): Promise<DiscoveredMap[]> {\n try {\n return await discoverSourceMaps(absDir);\n } catch (error) {\n die(\n `Could not read source maps directory ${absDir}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\ninterface UploadResult {\n buildId: string;\n bytes: number;\n commitSha: string;\n files: number;\n runner: string;\n slug: string;\n url: string | null;\n}\n\nfunction printSummary(flags: Flags, result: UploadResult): void {\n log(\n flags,\n `Done. Release ${result.slug} — ${result.files} file(s), ${formatBytes(result.bytes)}.`\n );\n if (result.url) {\n log(flags, result.url);\n }\n if (flags.json) {\n process.stdout.write(`${JSON.stringify(result, null, 2)}\\n`);\n }\n}\n\nasync function sourcemapsUpload(dir: string, flags: Flags): Promise<void> {\n const apiKey = resolveApiKey(flags);\n const apiUrl = flags.url ?? process.env[\"INTERFERE_API_URL\"] ?? API_URL;\n const runner = resolveRunner(flags);\n const { releaseSlug, commitSha, buildId } = resolveIdentity(flags, runner);\n\n if (!flags.release) {\n log(flags, `Derived release ${releaseSlug} from commit ${commitSha}`);\n }\n\n const absDir = resolve(dir);\n const maps = await discoverMaps(absDir);\n\n const { body: releaseBody, response: releaseRes } = await ensureRelease({\n apiKey,\n apiUrl,\n buildId,\n commitSha,\n deploymentUrl: flags.deploymentUrl,\n dryRun: flags.dryRun,\n environment: flags.environment,\n releaseSlug,\n runner,\n });\n\n const dashboardUrl = releaseRes\n ? `https://app.interfere.com/~/${releaseRes.org.slug}/surfaces/${releaseRes.surface.slug}`\n : null;\n\n if (flags.dryRun) {\n if (flags.json) {\n const output = {\n release: releaseBody,\n runner,\n files: maps.map((map) => map.relPath),\n dryRun: true,\n };\n process.stdout.write(`${JSON.stringify(output, null, 2)}\\n`);\n } else {\n log(flags, \"[dry-run] Release payload:\");\n log(flags, JSON.stringify(releaseBody, null, 2));\n log(flags, `[dry-run] Runner: ${runner.runner}`);\n log(\n flags,\n `[dry-run] Would upload ${maps.length} source map(s) from ${absDir}`\n );\n }\n return;\n }\n\n if (maps.length === 0) {\n log(flags, `No .js.map files found under ${absDir}`);\n if (!flags.skipPreflight) {\n await confirmPreflight({ apiKey, apiUrl, releaseSlug });\n }\n printSummary(flags, {\n slug: releaseSlug,\n buildId,\n commitSha,\n runner: runner.runner,\n files: 0,\n bytes: 0,\n url: dashboardUrl,\n });\n return;\n }\n\n log(flags, `Uploading ${maps.length} source map(s) to ${releaseSlug}…`);\n const summary = await uploadSourceMaps({ apiKey, apiUrl, maps, releaseSlug });\n\n if (!flags.skipPreflight) {\n await confirmPreflight({ apiKey, apiUrl, releaseSlug });\n }\n\n printSummary(flags, {\n slug: releaseSlug,\n buildId,\n commitSha,\n runner: runner.runner,\n files: summary.fileCount,\n bytes: summary.totalBytes,\n url: dashboardUrl,\n });\n}\n\nexport async function runCli(\n argv: readonly string[] = process.argv.slice(2)\n): Promise<void> {\n if (argv.length === 0) {\n parseFlags([\"--help\"]);\n return;\n }\n const flags = parseFlags(argv);\n const [command, sub, ...rest] = flags.positional;\n\n if (command === \"sourcemaps\" && sub === \"upload\") {\n const dir = flags.sourceMaps ?? rest[0];\n if (!dir) {\n die(\n \"Missing source maps directory. Pass --source-maps <dir> or: interfere sourcemaps upload <dir>\"\n );\n }\n await sourcemapsUpload(dir, flags);\n return;\n }\n\n die(\n `Unknown command: ${flags.positional.join(\" \") || \"(none)\"}. Try --help.`\n );\n}\n","#!/usr/bin/env node\nimport { runCli } from \"./index.js\";\n\nawait runCli();\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/cli",
3
- "version": "0.0.2-canary.0",
3
+ "version": "0.1.0-canary.2",
4
4
  "license": "MIT",
5
5
  "description": "Command-line tool for Interfere. Uploads source maps and confirms releases as part of your CI deploy.",
6
6
  "keywords": [
@@ -17,7 +17,7 @@
17
17
  "author": "Interfere <support@interfere.com> (https://interfere.com)",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/interfere-inc/interfere.git",
20
+ "url": "git+https://interfere.ghe.com/interfere/interfere.git",
21
21
  "directory": "src/packages/public/cli"
22
22
  },
23
23
  "files": [
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "type": "module",
27
27
  "bin": {
28
- "interfere": "./dist/index.mjs"
28
+ "interfere": "./dist/bin.mjs"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"
@@ -33,19 +33,19 @@
33
33
  "scripts": {
34
34
  "build": "tsdown",
35
35
  "test": "vitest run --passWithNoTests",
36
- "typecheck": "tsc --noEmit --incremental"
36
+ "typecheck": "tsc --noEmit"
37
37
  },
38
38
  "dependencies": {
39
39
  "@interfere/constants": "^9.0.2-canary.0",
40
- "@interfere/types": "^9.0.3-canary.0"
40
+ "@interfere/types": "^9.0.3-canary.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@interfere/test-utils": "^9.0.0",
44
44
  "@interfere/typescript-config": "^9.0.0",
45
45
  "@types/node": "^24.12.0",
46
- "@vitest/coverage-v8": "^4.1.6",
47
- "tsdown": "^0.22.0",
46
+ "@vitest/coverage-v8": "4.1.8",
47
+ "tsdown": "0.22.1",
48
48
  "typescript": "^6.0.3",
49
- "vitest": "^4.1.6"
49
+ "vitest": "4.1.8"
50
50
  }
51
51
  }
@@ -1,16 +0,0 @@
1
- //#region src/discover.d.ts
2
- interface DiscoveredMap {
3
- /** Absolute path on disk. */
4
- readonly absPath: string;
5
- /** Raw .map file content. */
6
- readonly content: string;
7
- /** Path relative to the upload root, used for `chunkUrl` suffix-matching. */
8
- readonly relPath: string;
9
- }
10
- /**
11
- * Recursively walks `root` for `.js.map` files. We only handle JS source
12
- * maps here — `nest build` doesn't produce maps for any other asset class.
13
- */
14
- declare function discoverSourceMaps(root: string): Promise<DiscoveredMap[]>;
15
- //#endregion
16
- export { DiscoveredMap, discoverSourceMaps };
@@ -1 +0,0 @@
1
- {"version":3,"file":"discover.d.mts","names":[],"sources":["../src/discover.ts"],"mappings":";UAIiB,aAAA;EAAA;EAAA,SAEN,OAAA;;WAEA,OAAA;EAFA;EAAA,SAIA,OAAA;AAAA;;AAAO;AAOlB;;iBAAsB,kBAAA,CACpB,IAAA,WACC,OAAO,CAAC,aAAA"}
package/dist/discover.mjs DELETED
@@ -1 +0,0 @@
1
- import{readFileSync}from"node:fs";import{readdir}from"node:fs/promises";import{join,relative}from"node:path";async function discoverSourceMaps(root){let found=[];return await walk(root,root,found),found}async function walk(root,dir,out){let entries=await readdir(dir,{withFileTypes:!0});for(let entry of entries){let abs=join(dir,entry.name);if(entry.isDirectory()){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;await walk(root,abs,out);continue}entry.name.endsWith(`.js.map`)&&out.push({absPath:abs,relPath:relative(root,abs),content:readFileSync(abs,`utf8`)})}}export{discoverSourceMaps};
@@ -1 +0,0 @@
1
- {"version":3,"file":"discover.mjs","names":[],"sources":["../src/discover.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\nexport interface DiscoveredMap {\n /** Absolute path on disk. */\n readonly absPath: string;\n /** Raw .map file content. */\n readonly content: string;\n /** Path relative to the upload root, used for `chunkUrl` suffix-matching. */\n readonly relPath: string;\n}\n\n/**\n * Recursively walks `root` for `.js.map` files. We only handle JS source\n * maps here — `nest build` doesn't produce maps for any other asset class.\n */\nexport async function discoverSourceMaps(\n root: string\n): Promise<DiscoveredMap[]> {\n const found: DiscoveredMap[] = [];\n await walk(root, root, found);\n return found;\n}\n\nasync function walk(\n root: string,\n dir: string,\n out: DiscoveredMap[]\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) {\n continue;\n }\n await walk(root, abs, out);\n continue;\n }\n if (!entry.name.endsWith(\".js.map\")) {\n continue;\n }\n out.push({\n absPath: abs,\n relPath: relative(root, abs),\n content: readFileSync(abs, \"utf8\"),\n });\n }\n}\n"],"mappings":"6GAiBA,eAAsB,mBACpB,KAC0B,CAC1B,IAAM,MAAyB,CAAC,EAEhC,OADA,MAAM,KAAK,KAAM,KAAM,KAAK,EACrB,KACT,CAEA,eAAe,KACb,KACA,IACA,IACe,CACf,IAAM,QAAU,MAAM,QAAQ,IAAK,CAAE,cAAe,EAAK,CAAC,EAC1D,IAAK,IAAM,SAAS,QAAS,CAC3B,IAAM,IAAM,KAAK,IAAK,MAAM,IAAI,EAChC,GAAI,MAAM,YAAY,EAAG,CACvB,GAAI,MAAM,OAAS,gBAAkB,MAAM,KAAK,WAAW,GAAG,EAC5D,SAEF,MAAM,KAAK,KAAM,IAAK,GAAG,EACzB,QACF,CACK,MAAM,KAAK,SAAS,SAAS,GAGlC,IAAI,KAAK,CACP,QAAS,IACT,QAAS,SAAS,KAAM,GAAG,EAC3B,QAAS,aAAa,IAAK,MAAM,CACnC,CAAC,CACH,CACF"}
package/dist/index.d.mts DELETED
@@ -1 +0,0 @@
1
- export { };
package/dist/index.mjs DELETED
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
- import{discoverSourceMaps}from"./discover.mjs";import{VERSION}from"./internal/version.mjs";import{confirmPreflight}from"./preflight.mjs";import{ensureRelease}from"./release.mjs";import{resolveReleaseSlug}from"./release-slug.mjs";import{uploadSourceMaps}from"./upload.mjs";import{resolve}from"node:path";import{API_URL}from"@interfere/constants/api";const HELP=`
3
- interfere ${VERSION}
4
-
5
- Usage:
6
- interfere sourcemaps upload <dir> [options]
7
- interfere --help
8
- interfere --version
9
-
10
- Options:
11
- --release <slug> Release slug (default: derived from commit SHA)
12
- --url <api-url> Interfere collector URL (default: $INTERFERE_API_URL or ${API_URL})
13
- --auth-token <key> Interfere API key (default: $INTERFERE_API_KEY)
14
- --skip-preflight Don't mark the release preflight-confirmed after publishing metadata
15
- -h, --help Show this help
16
- --version Show version
17
-
18
- Environment:
19
- INTERFERE_API_KEY Interfere API key in the interfere_ak_* format
20
- INTERFERE_API_URL Override the default collector URL
21
- INTERFERE_SOURCE_ID Override the commit SHA used to derive the slug.
22
- Falls back to VERCEL_GIT_COMMIT_SHA, GITHUB_SHA,
23
- or \`git rev-parse HEAD\`.
24
- `;function parseFlags(argv){(argv.includes(`--help`)||argv.includes(`-h`))&&(process.stdout.write(`${HELP.trim()}\n`),process.exit(0)),argv.includes(`--version`)&&(process.stdout.write(`${VERSION}\n`),process.exit(0));let flags={release:null,url:null,authToken:null,skipPreflight:!1,positional:[]},i=0;for(;i<argv.length;){let arg=argv[i++];switch(arg){case`--release`:flags.release=argv[i++]??null;break;case`--url`:flags.url=argv[i++]??null;break;case`--auth-token`:flags.authToken=argv[i++]??null;break;case`--skip-preflight`:flags.skipPreflight=!0;break;default:arg!==void 0&&flags.positional.push(arg)}}return flags}async function sourcemapsUpload(dir,flags){let apiKey=flags.authToken??process.env.INTERFERE_API_KEY;apiKey||die(`Missing API key. Set INTERFERE_API_KEY or pass --auth-token.`);let apiUrl=flags.url??process.env.INTERFERE_API_URL??API_URL,releaseSlug=flags.release,commitSha=null;if(releaseSlug)commitSha=resolveReleaseSlug().commitSha;else{let resolved=resolveReleaseSlug();resolved.slug||die(`Could not derive a release slug. Set INTERFERE_SOURCE_ID (or run inside a git repo with at least one commit), or pass --release.`),releaseSlug=resolved.slug,commitSha=resolved.commitSha,console.log(`Derived release ${releaseSlug} from commit ${resolved.commitSha}`)}commitSha&&await ensureRelease({apiKey,apiUrl,commitSha,releaseSlug});let absDir=resolve(dir),maps=await discoverSourceMaps(absDir);if(maps.length===0){console.log(`No .js.map files found under ${absDir}`),flags.skipPreflight||(await confirmPreflight({apiKey,apiUrl,releaseSlug}),console.log(`Release ${releaseSlug} preflight-confirmed (no maps).`));return}console.log(`Uploading ${maps.length} source map(s) to ${releaseSlug}…`);let summary=await uploadSourceMaps({apiKey,apiUrl,maps,releaseSlug});console.log(`Uploaded ${summary.fileCount} file(s), ${formatBytes(summary.totalBytes)} total.`),flags.skipPreflight||(await confirmPreflight({apiKey,apiUrl,releaseSlug}),console.log(`Release ${releaseSlug} preflight-confirmed.`))}function formatBytes(n){return n<1024?`${n} B`:n<1024*1024?`${(n/1024).toFixed(1)} KB`:`${(n/(1024*1024)).toFixed(1)} MB`}function die(message){process.stderr.write(`interfere: ${message}\n`),process.exit(1)}async function main(){let argv=process.argv.slice(2);argv.length===0&&(process.stdout.write(`${HELP.trim()}\n`),process.exit(0));let flags=parseFlags(argv),[command,sub,...rest]=flags.positional;if(command===`sourcemaps`&&sub===`upload`){let dir=rest[0];dir||die(`Missing <dir>. Usage: interfere sourcemaps upload <dir>`),await sourcemapsUpload(dir,flags);return}die(`Unknown command: ${flags.positional.join(` `)||`(none)`}. Try --help.`)}await main();export{};
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { API_URL } from \"@interfere/constants/api\";\n\nimport { resolve } from \"node:path\";\n\nimport { discoverSourceMaps } from \"./discover.js\";\nimport { VERSION } from \"./internal/version.js\";\nimport { confirmPreflight } from \"./preflight.js\";\nimport { ensureRelease } from \"./release.js\";\nimport { resolveReleaseSlug } from \"./release-slug.js\";\nimport { uploadSourceMaps } from \"./upload.js\";\n\nconst HELP = `\ninterfere ${VERSION}\n\nUsage:\n interfere sourcemaps upload <dir> [options]\n interfere --help\n interfere --version\n\nOptions:\n --release <slug> Release slug (default: derived from commit SHA)\n --url <api-url> Interfere collector URL (default: $INTERFERE_API_URL or ${API_URL})\n --auth-token <key> Interfere API key (default: $INTERFERE_API_KEY)\n --skip-preflight Don't mark the release preflight-confirmed after publishing metadata\n -h, --help Show this help\n --version Show version\n\nEnvironment:\n INTERFERE_API_KEY Interfere API key in the interfere_ak_* format\n INTERFERE_API_URL Override the default collector URL\n INTERFERE_SOURCE_ID Override the commit SHA used to derive the slug.\n Falls back to VERCEL_GIT_COMMIT_SHA, GITHUB_SHA,\n or \\`git rev-parse HEAD\\`.\n`;\n\ninterface Flags {\n authToken: string | null;\n positional: string[];\n release: string | null;\n skipPreflight: boolean;\n url: string | null;\n}\n\nfunction parseFlags(argv: readonly string[]): Flags {\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) {\n process.stdout.write(`${HELP.trim()}\\n`);\n process.exit(0);\n }\n if (argv.includes(\"--version\")) {\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n }\n const flags: Flags = {\n release: null,\n url: null,\n authToken: null,\n skipPreflight: false,\n positional: [],\n };\n let i = 0;\n while (i < argv.length) {\n const arg = argv[i++];\n switch (arg) {\n case \"--release\":\n flags.release = argv[i++] ?? null;\n break;\n case \"--url\":\n flags.url = argv[i++] ?? null;\n break;\n case \"--auth-token\":\n flags.authToken = argv[i++] ?? null;\n break;\n case \"--skip-preflight\":\n flags.skipPreflight = true;\n break;\n default:\n if (arg !== undefined) {\n flags.positional.push(arg);\n }\n }\n }\n return flags;\n}\n\nasync function sourcemapsUpload(dir: string, flags: Flags): Promise<void> {\n const apiKey = flags.authToken ?? process.env[\"INTERFERE_API_KEY\"];\n if (!apiKey) {\n die(\"Missing API key. Set INTERFERE_API_KEY or pass --auth-token.\");\n }\n const apiUrl = flags.url ?? process.env[\"INTERFERE_API_URL\"] ?? API_URL;\n\n let releaseSlug = flags.release;\n let commitSha: string | null = null;\n if (releaseSlug) {\n commitSha = resolveReleaseSlug().commitSha;\n } else {\n const resolved = resolveReleaseSlug();\n if (!resolved.slug) {\n die(\n \"Could not derive a release slug. Set INTERFERE_SOURCE_ID (or run inside a git repo with at least one commit), or pass --release.\"\n );\n }\n releaseSlug = resolved.slug;\n commitSha = resolved.commitSha;\n console.log(\n `Derived release ${releaseSlug} from commit ${resolved.commitSha}`\n );\n }\n\n // Upsert the release row before signing. Deployment-webhook integrations\n // (Vercel, GitHub Actions) create it for us in their flow, but customers\n // deploying to bare-metal / Fly / Render / AWS / etc have no webhook, so\n // without this the CLI's first `sign` call would 500 on an unknown slug.\n if (commitSha) {\n await ensureRelease({ apiKey, apiUrl, commitSha, releaseSlug });\n }\n\n const absDir = resolve(dir);\n const maps = await discoverSourceMaps(absDir);\n if (maps.length === 0) {\n console.log(`No .js.map files found under ${absDir}`);\n if (!flags.skipPreflight) {\n await confirmPreflight({ apiKey, apiUrl, releaseSlug });\n console.log(`Release ${releaseSlug} preflight-confirmed (no maps).`);\n }\n return;\n }\n\n console.log(`Uploading ${maps.length} source map(s) to ${releaseSlug}…`);\n const summary = await uploadSourceMaps({ apiKey, apiUrl, maps, releaseSlug });\n console.log(\n `Uploaded ${summary.fileCount} file(s), ${formatBytes(summary.totalBytes)} total.`\n );\n\n if (!flags.skipPreflight) {\n await confirmPreflight({ apiKey, apiUrl, releaseSlug });\n console.log(`Release ${releaseSlug} preflight-confirmed.`);\n }\n}\n\nfunction formatBytes(n: number): string {\n if (n < 1024) {\n return `${n} B`;\n }\n if (n < 1024 * 1024) {\n return `${(n / 1024).toFixed(1)} KB`;\n }\n return `${(n / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction die(message: string): never {\n process.stderr.write(`interfere: ${message}\\n`);\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n if (argv.length === 0) {\n process.stdout.write(`${HELP.trim()}\\n`);\n process.exit(0);\n }\n const flags = parseFlags(argv);\n const [command, sub, ...rest] = flags.positional;\n\n if (command === \"sourcemaps\" && sub === \"upload\") {\n const dir = rest[0];\n if (!dir) {\n die(\"Missing <dir>. Usage: interfere sourcemaps upload <dir>\");\n }\n await sourcemapsUpload(dir, flags);\n return;\n }\n\n die(\n `Unknown command: ${flags.positional.join(\" \") || \"(none)\"}. Try --help.`\n );\n}\n\nawait main();\n"],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- //#region src/internal/version.d.ts
2
- declare const VERSION: string;
3
- declare const PRODUCER_VERSION: string;
4
- //#endregion
5
- export { PRODUCER_VERSION, VERSION };
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.d.mts","names":[],"sources":["../../src/internal/version.ts"],"mappings":";cAEa,OAAA;AAAA,cACA,gBAAA"}
@@ -1 +0,0 @@
1
- import{name,version}from"../package.mjs";const VERSION=version,PRODUCER_VERSION=`${name}@${version}`;export{PRODUCER_VERSION,VERSION};
@@ -1 +0,0 @@
1
- {"version":3,"file":"version.mjs","names":["pkg.version","pkg.name"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const VERSION = pkg.version;\nexport const PRODUCER_VERSION = `${pkg.name}@${pkg.version}`;\n"],"mappings":"yCAEA,MAAa,QAAUA,QACV,iBAAmB,GAAGC,KAAS,GAAGD"}
package/dist/package.mjs DELETED
@@ -1 +0,0 @@
1
- var name=`@interfere/cli`,version=`0.0.2-canary.0`;export{name,version};
@@ -1 +0,0 @@
1
- {"version":3,"file":"package.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
@@ -1,18 +0,0 @@
1
- //#region src/preflight.d.ts
2
- interface PreflightParams {
3
- readonly apiKey: string;
4
- readonly apiUrl: string;
5
- readonly releaseSlug: string;
6
- }
7
- /**
8
- * Marks the release as preflight-confirmed at the collector. After this
9
- * returns, OTLP traces and exception events for `releaseSlug` pass the
10
- * release-gate; before, they're rejected with `COLLECTOR_RELEASE_PREFLIGHT_UNCONFIRMED`.
11
- */
12
- declare function confirmPreflight({
13
- apiKey,
14
- apiUrl,
15
- releaseSlug
16
- }: PreflightParams): Promise<void>;
17
- //#endregion
18
- export { confirmPreflight };
@@ -1 +0,0 @@
1
- {"version":3,"file":"preflight.d.mts","names":[],"sources":["../src/preflight.ts"],"mappings":";UAEU,eAAA;EAAA,SACC,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;AAAA;;;;;AAAW;iBAQA,gBAAA,CAAA;EACpB,MAAA;EACA,MAAA;EACA;AAAA,GACC,eAAA,GAAkB,OAAA"}
@@ -1 +0,0 @@
1
- import{PRODUCER_VERSION}from"./internal/version.mjs";async function confirmPreflight({apiKey,apiUrl,releaseSlug}){let url=`${apiUrl}/v1/releases/${encodeURIComponent(releaseSlug)}/preflight`,response=await fetch(url,{method:`POST`,headers:{"content-type":`application/json`,"user-agent":PRODUCER_VERSION,Authorization:`Bearer ${apiKey}`}});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`POST ${url} → ${response.status} ${detail}`)}}export{confirmPreflight};
@@ -1 +0,0 @@
1
- {"version":3,"file":"preflight.mjs","names":[],"sources":["../src/preflight.ts"],"sourcesContent":["import { PRODUCER_VERSION } from \"./internal/version.js\";\n\ninterface PreflightParams {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly releaseSlug: string;\n}\n\n/**\n * Marks the release as preflight-confirmed at the collector. After this\n * returns, OTLP traces and exception events for `releaseSlug` pass the\n * release-gate; before, they're rejected with `COLLECTOR_RELEASE_PREFLIGHT_UNCONFIRMED`.\n */\nexport async function confirmPreflight({\n apiKey,\n apiUrl,\n releaseSlug,\n}: PreflightParams): Promise<void> {\n const url = `${apiUrl}/v1/releases/${encodeURIComponent(releaseSlug)}/preflight`;\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"user-agent\": PRODUCER_VERSION,\n Authorization: `Bearer ${apiKey}`,\n },\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`POST ${url} → ${response.status} ${detail}`);\n }\n}\n"],"mappings":"qDAaA,eAAsB,iBAAiB,CACrC,OACA,OACA,aACiC,CACjC,IAAM,IAAM,GAAG,OAAO,eAAe,mBAAmB,WAAW,EAAE,YAC/D,SAAW,MAAM,MAAM,IAAK,CAChC,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,aAAc,iBACd,cAAe,UAAU,QAC3B,CACF,CAAC,EACD,GAAI,CAAC,SAAS,GAAI,CAChB,IAAM,OAAS,MAAM,SAAS,KAAK,EAAE,UAAY,EAAE,EACnD,MAAU,MAAM,QAAQ,IAAI,KAAK,SAAS,OAAO,GAAG,QAAQ,CAC9D,CACF"}
@@ -1,7 +0,0 @@
1
- //#region src/release-slug.d.ts
2
- declare function resolveReleaseSlug(): {
3
- commitSha: string | null;
4
- slug: string | null;
5
- };
6
- //#endregion
7
- export { resolveReleaseSlug };
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-slug.d.mts","names":[],"sources":["../src/release-slug.ts"],"mappings":";iBAkBgB,kBAAA,CAAA;EACd,SAAA;EACA,IAAI;AAAA"}
@@ -1 +0,0 @@
1
- import{execSync}from"node:child_process";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";function gitHead(){try{let out=execSync(`git rev-parse HEAD`,{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return out.length>0?out:null}catch{return null}}function resolveReleaseSlug(){let commitSha=readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??gitHead();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveReleaseSlug};
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-slug.mjs","names":[],"sources":["../src/release-slug.ts"],"sourcesContent":["import { releaseSourceIdEnvKeys } from \"@interfere/types/integrations\";\nimport { deriveReleaseSlug } from \"@interfere/types/releases/slug\";\nimport { readFirstEnvValue } from \"@interfere/types/sdk/env\";\n\nimport { execSync } from \"node:child_process\";\n\nfunction gitHead(): string | null {\n try {\n const out = execSync(\"git rev-parse HEAD\", {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return out.length > 0 ? out : null;\n } catch {\n return null;\n }\n}\n\nexport function resolveReleaseSlug(): {\n commitSha: string | null;\n slug: string | null;\n} {\n const commitSha =\n readFirstEnvValue(process.env, releaseSourceIdEnvKeys) ?? gitHead();\n return {\n commitSha,\n slug: commitSha ? deriveReleaseSlug(commitSha) : null,\n };\n}\n"],"mappings":"iOAMA,SAAS,SAAyB,CAChC,GAAI,CACF,IAAM,IAAM,SAAS,qBAAsB,CACzC,SAAU,OACV,MAAO,CAAC,SAAU,OAAQ,QAAQ,CACpC,CAAC,EAAE,KAAK,EACR,OAAO,IAAI,OAAS,EAAI,IAAM,IAChC,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAAgB,oBAGd,CACA,IAAM,UACJ,kBAAkB,QAAQ,IAAK,sBAAsB,GAAK,QAAQ,EACpE,MAAO,CACL,UACA,KAAM,UAAY,kBAAkB,SAAS,EAAI,IACnD,CACF"}
@@ -1,27 +0,0 @@
1
- //#region src/release.d.ts
2
- interface EnsureReleaseParams {
3
- readonly apiKey: string;
4
- readonly apiUrl: string;
5
- readonly commitSha: string;
6
- readonly releaseSlug: string;
7
- }
8
- /**
9
- * Idempotently create the release row at the collector. `POST /v1/releases`
10
- * is upsert-shaped — if a row already exists for `(surface, slug)` it's
11
- * updated in place rather than duplicated, so this is safe to call before
12
- * every release metadata publish regardless of whether a deployment webhook
13
- * (Vercel, GitHub Actions) beat us there.
14
- *
15
- * Required because the CLI's `sourcemaps upload` flow assumes the release
16
- * already exists when it calls `/source-maps/sign`; without this, customers
17
- * deploying outside an integration-provider webhook (bare-metal, Render,
18
- * Fly, AWS, etc) get a 500 on first upload.
19
- */
20
- declare function ensureRelease({
21
- apiKey,
22
- apiUrl,
23
- commitSha,
24
- releaseSlug
25
- }: EnsureReleaseParams): Promise<void>;
26
- //#endregion
27
- export { ensureRelease };
@@ -1 +0,0 @@
1
- {"version":3,"file":"release.d.mts","names":[],"sources":["../src/release.ts"],"mappings":";UAMU,mBAAA;EAAA,SACC,MAAA;EAAA,SACA,MAAA;EAAA,SACA,SAAA;EAAA,SACA,WAAA;AAAA;;;;;AAAW;AAetB;;;;;;;iBAAsB,aAAA,CAAA;EACpB,MAAA;EACA,MAAA;EACA,SAAA;EACA;AAAA,GACC,mBAAA,GAAsB,OAAA"}
package/dist/release.mjs DELETED
@@ -1 +0,0 @@
1
- import{PRODUCER_VERSION}from"./internal/version.mjs";import{execSync}from"node:child_process";async function ensureRelease({apiKey,apiUrl,commitSha,releaseSlug}){let body={source:{provider:`github`,commitSha,commitMessage:gitCommitMessage()??``,branch:gitBranch()??``},destination:null,buildId:commitSha,slug:releaseSlug,producerVersion:PRODUCER_VERSION},url=`${apiUrl}/v1/releases/`,response=await fetch(url,{method:`POST`,headers:{"content-type":`application/json`,accept:`application/json`,"user-agent":PRODUCER_VERSION,Authorization:`Bearer ${apiKey}`},body:JSON.stringify(body)});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`POST ${url} → ${response.status} ${detail}`)}}function gitCommitMessage(){return runGit([`log`,`-1`,`--format=%s`])}function gitBranch(){return runGit([`rev-parse`,`--abbrev-ref`,`HEAD`])}function runGit(args){try{let out=execSync(`git ${args.join(` `)}`,{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim();return out.length>0?out:null}catch{return null}}export{ensureRelease};
@@ -1 +0,0 @@
1
- {"version":3,"file":"release.mjs","names":[],"sources":["../src/release.ts"],"sourcesContent":["import type { CreateReleaseRequest } from \"@interfere/types/releases/definition\";\n\nimport { execSync } from \"node:child_process\";\n\nimport { PRODUCER_VERSION } from \"./internal/version.js\";\n\ninterface EnsureReleaseParams {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly commitSha: string;\n readonly releaseSlug: string;\n}\n\n/**\n * Idempotently create the release row at the collector. `POST /v1/releases`\n * is upsert-shaped — if a row already exists for `(surface, slug)` it's\n * updated in place rather than duplicated, so this is safe to call before\n * every release metadata publish regardless of whether a deployment webhook\n * (Vercel, GitHub Actions) beat us there.\n *\n * Required because the CLI's `sourcemaps upload` flow assumes the release\n * already exists when it calls `/source-maps/sign`; without this, customers\n * deploying outside an integration-provider webhook (bare-metal, Render,\n * Fly, AWS, etc) get a 500 on first upload.\n */\nexport async function ensureRelease({\n apiKey,\n apiUrl,\n commitSha,\n releaseSlug,\n}: EnsureReleaseParams): Promise<void> {\n const body: CreateReleaseRequest = {\n source: {\n provider: \"github\",\n commitSha,\n commitMessage: gitCommitMessage() ?? \"\",\n branch: gitBranch() ?? \"\",\n },\n destination: null,\n buildId: commitSha,\n slug: releaseSlug,\n producerVersion: PRODUCER_VERSION,\n };\n const url = `${apiUrl}/v1/releases/`;\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n \"user-agent\": PRODUCER_VERSION,\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`POST ${url} → ${response.status} ${detail}`);\n }\n}\n\nfunction gitCommitMessage(): string | null {\n return runGit([\"log\", \"-1\", \"--format=%s\"]);\n}\n\nfunction gitBranch(): string | null {\n return runGit([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n}\n\nfunction runGit(args: readonly string[]): string | null {\n try {\n const out = execSync(`git ${args.join(\" \")}`, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n return out.length > 0 ? out : null;\n } catch {\n return null;\n }\n}\n"],"mappings":"8FAyBA,eAAsB,cAAc,CAClC,OACA,OACA,UACA,aACqC,CACrC,IAAM,KAA6B,CACjC,OAAQ,CACN,SAAU,SACV,UACA,cAAe,iBAAiB,GAAK,GACrC,OAAQ,UAAU,GAAK,EACzB,EACA,YAAa,KACb,QAAS,UACT,KAAM,YACN,gBAAiB,gBACnB,EACM,IAAM,GAAG,OAAO,eAChB,SAAW,MAAM,MAAM,IAAK,CAChC,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,OAAQ,mBACR,aAAc,iBACd,cAAe,UAAU,QAC3B,EACA,KAAM,KAAK,UAAU,IAAI,CAC3B,CAAC,EACD,GAAI,CAAC,SAAS,GAAI,CAChB,IAAM,OAAS,MAAM,SAAS,KAAK,EAAE,UAAY,EAAE,EACnD,MAAU,MAAM,QAAQ,IAAI,KAAK,SAAS,OAAO,GAAG,QAAQ,CAC9D,CACF,CAEA,SAAS,kBAAkC,CACzC,OAAO,OAAO,CAAC,MAAO,KAAM,aAAa,CAAC,CAC5C,CAEA,SAAS,WAA2B,CAClC,OAAO,OAAO,CAAC,YAAa,eAAgB,MAAM,CAAC,CACrD,CAEA,SAAS,OAAO,KAAwC,CACtD,GAAI,CACF,IAAM,IAAM,SAAS,OAAO,KAAK,KAAK,GAAG,IAAK,CAC5C,SAAU,OACV,MAAO,CAAC,SAAU,OAAQ,QAAQ,CACpC,CAAC,EAAE,KAAK,EACR,OAAO,IAAI,OAAS,EAAI,IAAM,IAChC,MAAQ,CACN,OAAO,IACT,CACF"}
package/dist/upload.d.mts DELETED
@@ -1,21 +0,0 @@
1
- import { DiscoveredMap } from "./discover.mjs";
2
-
3
- //#region src/upload.d.ts
4
- interface UploadParams {
5
- readonly apiKey: string;
6
- readonly apiUrl: string;
7
- readonly maps: DiscoveredMap[];
8
- readonly releaseSlug: string;
9
- }
10
- interface UploadSummary {
11
- readonly fileCount: number;
12
- readonly totalBytes: number;
13
- }
14
- declare function uploadSourceMaps({
15
- apiKey,
16
- apiUrl,
17
- maps,
18
- releaseSlug
19
- }: UploadParams): Promise<UploadSummary>;
20
- //#endregion
21
- export { UploadSummary, uploadSourceMaps };
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.d.mts","names":[],"sources":["../src/upload.ts"],"mappings":";;;UAoDU,YAAA;EAAA,SACC,MAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA,EAAM,aAAa;EAAA,SACnB,WAAA;AAAA;AAAA,UAGM,aAAA;EAAA,SACN,SAAA;EAAA,SACA,UAAU;AAAA;AAAA,iBAGC,gBAAA,CAAA;EACpB,MAAA;EACA,MAAA;EACA,IAAA;EACA;AAAA,GACC,YAAA,GAAe,OAAA,CAAQ,aAAA"}
package/dist/upload.mjs DELETED
@@ -1 +0,0 @@
1
- import{PRODUCER_VERSION}from"./internal/version.mjs";import{createHash,randomUUID}from"node:crypto";const MAP_SUFFIX_RE=/\.map$/;function prepareEntries(maps){return maps.map(map=>{let debugId=randomUUID(),content=injectDebugId(map.content,debugId);return{relPath:map.relPath,chunkUrl:map.relPath.replace(MAP_SUFFIX_RE,``),debugId,content,contentBytes:Buffer.byteLength(content,`utf8`),hash:createHash(`sha256`).update(content).digest(`hex`)}})}function injectDebugId(rawMap,debugId){let parsed=JSON.parse(rawMap);return JSON.stringify({...parsed,debugId})}async function uploadSourceMaps({apiKey,apiUrl,maps,releaseSlug}){let entries=prepareEntries(maps),slug=encodeURIComponent(releaseSlug),signReq={files:entries.map(entry=>({path:entry.relPath,sizeBytes:entry.contentBytes,...richness(entry.content)}))},signRes=await postJson({apiKey,url:`${apiUrl}/v1/releases/${slug}/source-maps/sign`,body:signReq}),byPath=new Map(entries.map(entry=>[entry.relPath,entry])),totalBytes=0;await mapWithConcurrency(signRes.uploads,8,async upload=>{let entry=byPath.get(upload.path);if(!entry)throw Error(`Sign response referenced unknown path "${upload.path}"`);totalBytes+=entry.contentBytes,await putToR2(upload.presignedUrl,entry.content)});let completeReq={files:entries.map(entry=>({path:entry.relPath,hash:entry.hash,debugId:entry.debugId,chunkUrl:entry.chunkUrl})),sourceFileCount:entries.length,bundler:`tsc`};return{fileCount:(await postJson({apiKey,url:`${apiUrl}/v1/releases/${slug}/source-maps/complete`,body:completeReq})).fileCount,totalBytes}}function richness(content){let parsed=JSON.parse(content);return{hasSourcesContent:Array.isArray(parsed.sourcesContent)&&parsed.sourcesContent.length>0,hasNames:Array.isArray(parsed.names)&&parsed.names.length>0,hasFile:typeof parsed.file==`string`&&parsed.file.length>0,mappingsPresent:typeof parsed.mappings==`string`&&parsed.mappings.length>0}}async function postJson({apiKey,url,body}){let response=await fetch(url,{method:`POST`,headers:{"content-type":`application/json`,accept:`application/json`,"user-agent":PRODUCER_VERSION,Authorization:`Bearer ${apiKey}`},body:JSON.stringify(body)});if(!response.ok){let detail=await response.text().catch(()=>``);throw Error(`POST ${url} → ${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(`PUT R2 → ${response.status} ${detail}`)}}async function mapWithConcurrency(items,concurrency,fn){let cursor=0;async function worker(){for(;cursor<items.length;){let item=items[cursor++];item!==void 0&&await fn(item)}}await Promise.all(Array.from({length:Math.min(concurrency,items.length)},()=>worker()))}export{uploadSourceMaps};
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.mjs","names":[],"sources":["../src/upload.ts"],"sourcesContent":["import type {\n CompleteSourceMapsRequest,\n CompleteSourceMapsResponse,\n SignSourceMapsRequest,\n SignSourceMapsResponse,\n} from \"@interfere/types/data/source-maps\";\n\nimport { createHash, randomUUID } from \"node:crypto\";\n\nimport type { DiscoveredMap } from \"./discover.js\";\nimport { PRODUCER_VERSION } from \"./internal/version.js\";\n\nconst PUT_CONCURRENCY = 8;\nconst MAP_SUFFIX_RE = /\\.map$/;\n\ninterface PreparedEntry {\n /** Path to the sibling .js — `main.js.map` → `main.js`. Drives `chunkUrl`. */\n readonly chunkUrl: string;\n readonly content: string;\n readonly contentBytes: number;\n readonly debugId: string;\n readonly hash: string;\n readonly relPath: string;\n}\n\n/**\n * Generates a `debugId` per map and injects it into the JSON. The\n * collector's symbolicator suffix-matches incoming `frame.fileName`\n * against the manifest's `chunkUrl` to look up the debugId server-side,\n * so we don't need to also write the debugId comment back into the .js\n * file at the cost of mutating the deploy artifact.\n */\nfunction prepareEntries(maps: DiscoveredMap[]): PreparedEntry[] {\n return maps.map((map) => {\n const debugId = randomUUID();\n const content = injectDebugId(map.content, debugId);\n return {\n relPath: map.relPath,\n chunkUrl: map.relPath.replace(MAP_SUFFIX_RE, \"\"),\n debugId,\n content,\n contentBytes: Buffer.byteLength(content, \"utf8\"),\n hash: createHash(\"sha256\").update(content).digest(\"hex\"),\n };\n });\n}\n\nfunction injectDebugId(rawMap: string, debugId: string): string {\n const parsed = JSON.parse(rawMap) as Record<string, unknown>;\n return JSON.stringify({ ...parsed, debugId });\n}\n\ninterface UploadParams {\n readonly apiKey: string;\n readonly apiUrl: string;\n readonly maps: DiscoveredMap[];\n readonly releaseSlug: string;\n}\n\nexport interface UploadSummary {\n readonly fileCount: number;\n readonly totalBytes: number;\n}\n\nexport async function uploadSourceMaps({\n apiKey,\n apiUrl,\n maps,\n releaseSlug,\n}: UploadParams): Promise<UploadSummary> {\n const entries = prepareEntries(maps);\n const slug = encodeURIComponent(releaseSlug);\n\n const signReq: SignSourceMapsRequest = {\n files: entries.map((entry) => ({\n path: entry.relPath,\n sizeBytes: entry.contentBytes,\n ...richness(entry.content),\n })),\n };\n\n const signRes = await postJson<SignSourceMapsResponse>({\n apiKey,\n url: `${apiUrl}/v1/releases/${slug}/source-maps/sign`,\n body: signReq,\n });\n\n const byPath = new Map(entries.map((entry) => [entry.relPath, entry]));\n let totalBytes = 0;\n\n await mapWithConcurrency(signRes.uploads, PUT_CONCURRENCY, async (upload) => {\n const entry = byPath.get(upload.path);\n if (!entry) {\n throw new Error(`Sign response referenced unknown path \"${upload.path}\"`);\n }\n totalBytes += entry.contentBytes;\n await putToR2(upload.presignedUrl, entry.content);\n });\n\n const completeReq: CompleteSourceMapsRequest = {\n files: entries.map((entry) => ({\n path: entry.relPath,\n hash: entry.hash,\n debugId: entry.debugId,\n chunkUrl: entry.chunkUrl,\n })),\n sourceFileCount: entries.length,\n bundler: \"tsc\",\n };\n\n const completeRes = await postJson<CompleteSourceMapsResponse>({\n apiKey,\n url: `${apiUrl}/v1/releases/${slug}/source-maps/complete`,\n body: completeReq,\n });\n\n return { fileCount: completeRes.fileCount, totalBytes };\n}\n\ninterface RichnessFields {\n readonly hasFile: boolean;\n readonly hasNames: boolean;\n readonly hasSourcesContent: boolean;\n readonly mappingsPresent: boolean;\n}\n\nfunction richness(content: string): RichnessFields {\n const parsed = JSON.parse(content) as Record<string, unknown>;\n return {\n hasSourcesContent:\n Array.isArray(parsed[\"sourcesContent\"]) &&\n (parsed[\"sourcesContent\"] as unknown[]).length > 0,\n hasNames:\n Array.isArray(parsed[\"names\"]) &&\n (parsed[\"names\"] as unknown[]).length > 0,\n hasFile:\n typeof parsed[\"file\"] === \"string\" &&\n (parsed[\"file\"] as string).length > 0,\n mappingsPresent:\n typeof parsed[\"mappings\"] === \"string\" &&\n (parsed[\"mappings\"] as string).length > 0,\n };\n}\n\nasync function postJson<T>({\n apiKey,\n url,\n body,\n}: {\n apiKey: string;\n url: string;\n body: unknown;\n}): Promise<T> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n \"user-agent\": PRODUCER_VERSION,\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`POST ${url} → ${response.status} ${detail}`);\n }\n return (await response.json()) as T;\n}\n\nasync function putToR2(presignedUrl: string, content: string): Promise<void> {\n const response = await fetch(presignedUrl, {\n method: \"PUT\",\n headers: { \"content-type\": \"application/json\" },\n body: content,\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`PUT R2 → ${response.status} ${detail}`);\n }\n}\n\nasync function mapWithConcurrency<T>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T) => Promise<void>\n): Promise<void> {\n let cursor = 0;\n async function worker(): Promise<void> {\n while (cursor < items.length) {\n const i = cursor++;\n const item = items[i];\n if (item !== undefined) {\n await fn(item);\n }\n }\n }\n await Promise.all(\n Array.from({ length: Math.min(concurrency, items.length) }, () => worker())\n );\n}\n"],"mappings":"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"}