@interfere/cli 0.0.1-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/discover.d.mts +16 -0
- package/dist/discover.d.mts.map +1 -0
- package/dist/discover.mjs +32 -0
- package/dist/discover.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +139 -0
- package/dist/index.mjs.map +1 -0
- package/dist/preflight.d.mts +18 -0
- package/dist/preflight.d.mts.map +1 -0
- package/dist/preflight.mjs +23 -0
- package/dist/preflight.mjs.map +1 -0
- package/dist/release-slug.d.mts +7 -0
- package/dist/release-slug.d.mts.map +1 -0
- package/dist/release-slug.mjs +29 -0
- package/dist/release-slug.mjs.map +1 -0
- package/dist/upload.d.mts +21 -0
- package/dist/upload.d.mts.map +1 -0
- package/dist/upload.mjs +122 -0
- package/dist/upload.mjs.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @interfere/cli
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add -d @interfere/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
After your build step, upload source maps:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
INTERFERE_API_KEY=ak_… bunx interfere sourcemaps upload ./dist
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Slug is auto-derived from the commit SHA (same algorithm the SDK uses at runtime). Full docs at <https://interfere.com/docs>.
|
|
@@ -0,0 +1,16 @@
|
|
|
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 };
|
|
@@ -0,0 +1 @@
|
|
|
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;;;AAOX;;iBAAsB,kBAAA,CACpB,IAAA,WACC,OAAA,CAAQ,aAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
//#region src/discover.ts
|
|
5
|
+
/**
|
|
6
|
+
* Recursively walks `root` for `.js.map` files. We only handle JS source
|
|
7
|
+
* maps here — `nest build` doesn't produce maps for any other asset class.
|
|
8
|
+
*/
|
|
9
|
+
async function discoverSourceMaps(root) {
|
|
10
|
+
const found = [];
|
|
11
|
+
await walk(root, root, found);
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
async function walk(root, dir, out) {
|
|
15
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const abs = join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
20
|
+
await walk(root, abs, out);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (!entry.name.endsWith(".js.map")) continue;
|
|
24
|
+
out.push({
|
|
25
|
+
absPath: abs,
|
|
26
|
+
relPath: relative(root, abs),
|
|
27
|
+
content: readFileSync(abs, "utf8")
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { discoverSourceMaps };
|
|
@@ -0,0 +1 @@
|
|
|
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":";;;;;;;;AAiBA,eAAsB,mBACpB,MAC0B;CAC1B,MAAM,QAAyB,EAAE;CACjC,MAAM,KAAK,MAAM,MAAM,MAAM;CAC7B,OAAO;;AAGT,eAAe,KACb,MACA,KACA,KACe;CACf,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;CAC3D,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,MAAM,KAAK,KAAK,MAAM,KAAK;EACjC,IAAI,MAAM,aAAa,EAAE;GACvB,IAAI,MAAM,SAAS,kBAAkB,MAAM,KAAK,WAAW,IAAI,EAC7D;GAEF,MAAM,KAAK,MAAM,KAAK,IAAI;GAC1B;;EAEF,IAAI,CAAC,MAAM,KAAK,SAAS,UAAU,EACjC;EAEF,IAAI,KAAK;GACP,SAAS;GACT,SAAS,SAAS,MAAM,IAAI;GAC5B,SAAS,aAAa,KAAK,OAAO;GACnC,CAAC"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { discoverSourceMaps } from "./discover.mjs";
|
|
3
|
+
import { confirmPreflight } from "./preflight.mjs";
|
|
4
|
+
import { resolveReleaseSlug } from "./release-slug.mjs";
|
|
5
|
+
import { uploadSourceMaps } from "./upload.mjs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { API_URL } from "@interfere/constants/api";
|
|
8
|
+
//#region src/index.ts
|
|
9
|
+
const VERSION = "10.0.0-canary.0";
|
|
10
|
+
const HELP = `
|
|
11
|
+
interfere ${VERSION}
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
interfere sourcemaps upload <dir> [options]
|
|
15
|
+
interfere --help
|
|
16
|
+
interfere --version
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--release <slug> Release slug (default: derived from commit SHA)
|
|
20
|
+
--url <api-url> Interfere collector URL (default: $INTERFERE_API_URL or ${API_URL})
|
|
21
|
+
--auth-token <key> API key (default: $INTERFERE_API_KEY)
|
|
22
|
+
--skip-preflight Don't mark the release preflight-confirmed after upload
|
|
23
|
+
-h, --help Show this help
|
|
24
|
+
--version Show version
|
|
25
|
+
|
|
26
|
+
Environment:
|
|
27
|
+
INTERFERE_API_KEY Authentication
|
|
28
|
+
INTERFERE_API_URL Override the default collector URL
|
|
29
|
+
INTERFERE_SOURCE_ID Override the commit SHA used to derive the slug.
|
|
30
|
+
Falls back to VERCEL_GIT_COMMIT_SHA, GITHUB_SHA,
|
|
31
|
+
or \`git rev-parse HEAD\`.
|
|
32
|
+
`;
|
|
33
|
+
function parseFlags(argv) {
|
|
34
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
35
|
+
process.stdout.write(`${HELP.trim()}\n`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
if (argv.includes("--version")) {
|
|
39
|
+
process.stdout.write(`${VERSION}\n`);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
const flags = {
|
|
43
|
+
release: null,
|
|
44
|
+
url: null,
|
|
45
|
+
authToken: null,
|
|
46
|
+
skipPreflight: false,
|
|
47
|
+
positional: []
|
|
48
|
+
};
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (i < argv.length) {
|
|
51
|
+
const arg = argv[i++];
|
|
52
|
+
switch (arg) {
|
|
53
|
+
case "--release":
|
|
54
|
+
flags.release = argv[i++] ?? null;
|
|
55
|
+
break;
|
|
56
|
+
case "--url":
|
|
57
|
+
flags.url = argv[i++] ?? null;
|
|
58
|
+
break;
|
|
59
|
+
case "--auth-token":
|
|
60
|
+
flags.authToken = argv[i++] ?? null;
|
|
61
|
+
break;
|
|
62
|
+
case "--skip-preflight":
|
|
63
|
+
flags.skipPreflight = true;
|
|
64
|
+
break;
|
|
65
|
+
default: if (arg !== void 0) flags.positional.push(arg);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return flags;
|
|
69
|
+
}
|
|
70
|
+
async function sourcemapsUpload(dir, flags) {
|
|
71
|
+
const apiKey = flags.authToken ?? process.env["INTERFERE_API_KEY"];
|
|
72
|
+
if (!apiKey) die("Missing API key. Set INTERFERE_API_KEY or pass --auth-token.");
|
|
73
|
+
const apiUrl = flags.url ?? process.env["INTERFERE_API_URL"] ?? API_URL;
|
|
74
|
+
let releaseSlug = flags.release;
|
|
75
|
+
if (!releaseSlug) {
|
|
76
|
+
const resolved = resolveReleaseSlug();
|
|
77
|
+
if (!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.");
|
|
78
|
+
releaseSlug = resolved.slug;
|
|
79
|
+
console.log(`Derived release ${releaseSlug} from commit ${resolved.commitSha}`);
|
|
80
|
+
}
|
|
81
|
+
const absDir = resolve(dir);
|
|
82
|
+
const maps = await discoverSourceMaps(absDir);
|
|
83
|
+
if (maps.length === 0) {
|
|
84
|
+
console.log(`No .js.map files found under ${absDir}`);
|
|
85
|
+
if (!flags.skipPreflight) {
|
|
86
|
+
await confirmPreflight({
|
|
87
|
+
apiKey,
|
|
88
|
+
apiUrl,
|
|
89
|
+
releaseSlug
|
|
90
|
+
});
|
|
91
|
+
console.log(`Release ${releaseSlug} preflight-confirmed (no maps).`);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(`Uploading ${maps.length} source map(s) to ${releaseSlug}…`);
|
|
96
|
+
const summary = await uploadSourceMaps({
|
|
97
|
+
apiKey,
|
|
98
|
+
apiUrl,
|
|
99
|
+
maps,
|
|
100
|
+
releaseSlug
|
|
101
|
+
});
|
|
102
|
+
console.log(`Uploaded ${summary.fileCount} file(s), ${formatBytes(summary.totalBytes)} total.`);
|
|
103
|
+
if (!flags.skipPreflight) {
|
|
104
|
+
await confirmPreflight({
|
|
105
|
+
apiKey,
|
|
106
|
+
apiUrl,
|
|
107
|
+
releaseSlug
|
|
108
|
+
});
|
|
109
|
+
console.log(`Release ${releaseSlug} preflight-confirmed.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function formatBytes(n) {
|
|
113
|
+
if (n < 1024) return `${n} B`;
|
|
114
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
115
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
116
|
+
}
|
|
117
|
+
function die(message) {
|
|
118
|
+
process.stderr.write(`interfere: ${message}\n`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
async function main() {
|
|
122
|
+
const argv = process.argv.slice(2);
|
|
123
|
+
if (argv.length === 0) {
|
|
124
|
+
process.stdout.write(`${HELP.trim()}\n`);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
const flags = parseFlags(argv);
|
|
128
|
+
const [command, sub, ...rest] = flags.positional;
|
|
129
|
+
if (command === "sourcemaps" && sub === "upload") {
|
|
130
|
+
const dir = rest[0];
|
|
131
|
+
if (!dir) die("Missing <dir>. Usage: interfere sourcemaps upload <dir>");
|
|
132
|
+
await sourcemapsUpload(dir, flags);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
die(`Unknown command: ${flags.positional.join(" ") || "(none)"}. Try --help.`);
|
|
136
|
+
}
|
|
137
|
+
await main();
|
|
138
|
+
//#endregion
|
|
139
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
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 { confirmPreflight } from \"./preflight.js\";\nimport { resolveReleaseSlug } from \"./release-slug.js\";\nimport { uploadSourceMaps } from \"./upload.js\";\n\nconst VERSION = \"10.0.0-canary.0\";\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> API key (default: $INTERFERE_API_KEY)\n --skip-preflight Don't mark the release preflight-confirmed after upload\n -h, --help Show this help\n --version Show version\n\nEnvironment:\n INTERFERE_API_KEY Authentication\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 if (!releaseSlug) {\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 console.log(\n `Derived release ${releaseSlug} from commit ${resolved.commitSha}`\n );\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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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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 };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.mts","names":[],"sources":["../src/preflight.ts"],"mappings":";UAAU,eAAA;EAAA,SACC,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;AAAA;;;;;;iBAQW,gBAAA,CAAA;EACpB,MAAA;EACA,MAAA;EACA;AAAA,GACC,eAAA,GAAkB,OAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/preflight.ts
|
|
2
|
+
/**
|
|
3
|
+
* Marks the release as preflight-confirmed at the collector. After this
|
|
4
|
+
* returns, OTLP traces and exception events for `releaseSlug` pass the
|
|
5
|
+
* release-gate; before, they're rejected with `COLLECTOR_RELEASE_PREFLIGHT_UNCONFIRMED`.
|
|
6
|
+
*/
|
|
7
|
+
async function confirmPreflight({ apiKey, apiUrl, releaseSlug }) {
|
|
8
|
+
const url = `${apiUrl}/v1/releases/${encodeURIComponent(releaseSlug)}/preflight`;
|
|
9
|
+
const response = await fetch(url, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
"content-type": "application/json",
|
|
13
|
+
"user-agent": "@interfere/cli@10.0.0-canary.0",
|
|
14
|
+
"x-api-key": apiKey
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const detail = await response.text().catch(() => "");
|
|
19
|
+
throw new Error(`POST ${url} → ${response.status} ${detail}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { confirmPreflight };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.mjs","names":[],"sources":["../src/preflight.ts"],"sourcesContent":["interface 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\": \"@interfere/cli@10.0.0-canary.0\",\n \"x-api-key\": 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":";;;;;;AAWA,eAAsB,iBAAiB,EACrC,QACA,QACA,eACiC;CACjC,MAAM,MAAM,GAAG,OAAO,eAAe,mBAAmB,YAAY,CAAC;CACrE,MAAM,WAAW,MAAM,MAAM,KAAK;EAChC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,cAAc;GACd,aAAa;GACd;EACF,CAAC;CACF,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,SAAS,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;EACpD,MAAM,IAAI,MAAM,QAAQ,IAAI,KAAK,SAAS,OAAO,GAAG,SAAS"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-slug.d.mts","names":[],"sources":["../src/release-slug.ts"],"mappings":";iBAkBgB,kBAAA,CAAA;EACd,SAAA;EACA,IAAA;AAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { releaseSourceIdEnvKeys } from "@interfere/types/integrations";
|
|
2
|
+
import { deriveReleaseSlug } from "@interfere/types/releases/slug";
|
|
3
|
+
import { readFirstEnvValue } from "@interfere/types/sdk/env";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
//#region src/release-slug.ts
|
|
6
|
+
function gitHead() {
|
|
7
|
+
try {
|
|
8
|
+
const out = execSync("git rev-parse HEAD", {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: [
|
|
11
|
+
"ignore",
|
|
12
|
+
"pipe",
|
|
13
|
+
"ignore"
|
|
14
|
+
]
|
|
15
|
+
}).trim();
|
|
16
|
+
return out.length > 0 ? out : null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function resolveReleaseSlug() {
|
|
22
|
+
const commitSha = readFirstEnvValue(process.env, releaseSourceIdEnvKeys) ?? gitHead();
|
|
23
|
+
return {
|
|
24
|
+
commitSha,
|
|
25
|
+
slug: commitSha ? deriveReleaseSlug(commitSha) : null
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { resolveReleaseSlug };
|
|
@@ -0,0 +1 @@
|
|
|
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":";;;;;AAMA,SAAS,UAAyB;CAChC,IAAI;EACF,MAAM,MAAM,SAAS,sBAAsB;GACzC,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CAAC,MAAM;EACT,OAAO,IAAI,SAAS,IAAI,MAAM;SACxB;EACN,OAAO;;;AAIX,SAAgB,qBAGd;CACA,MAAM,YACJ,kBAAkB,QAAQ,KAAK,uBAAuB,IAAI,SAAS;CACrE,OAAO;EACL;EACA,MAAM,YAAY,kBAAkB,UAAU,GAAG;EAClD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
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 };
|
|
@@ -0,0 +1 @@
|
|
|
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,SACN,WAAA;AAAA;AAAA,UAGM,aAAA;EAAA,SACN,SAAA;EAAA,SACA,UAAA;AAAA;AAAA,iBAGW,gBAAA,CAAA;EACpB,MAAA;EACA,MAAA;EACA,IAAA;EACA;AAAA,GACC,YAAA,GAAe,OAAA,CAAQ,aAAA"}
|
package/dist/upload.mjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
//#region src/upload.ts
|
|
3
|
+
const PUT_CONCURRENCY = 8;
|
|
4
|
+
const CLI_USER_AGENT = "@interfere/cli@10.0.0-canary.0";
|
|
5
|
+
const MAP_SUFFIX_RE = /\.map$/;
|
|
6
|
+
/**
|
|
7
|
+
* Generates a `debugId` per map and injects it into the JSON. The
|
|
8
|
+
* collector's symbolicator suffix-matches incoming `frame.fileName`
|
|
9
|
+
* against the manifest's `chunkUrl` to look up the debugId server-side,
|
|
10
|
+
* so we don't need to also write the debugId comment back into the .js
|
|
11
|
+
* file at the cost of mutating the deploy artifact.
|
|
12
|
+
*/
|
|
13
|
+
function prepareEntries(maps) {
|
|
14
|
+
return maps.map((map) => {
|
|
15
|
+
const debugId = randomUUID();
|
|
16
|
+
const content = injectDebugId(map.content, debugId);
|
|
17
|
+
return {
|
|
18
|
+
relPath: map.relPath,
|
|
19
|
+
chunkUrl: map.relPath.replace(MAP_SUFFIX_RE, ""),
|
|
20
|
+
debugId,
|
|
21
|
+
content,
|
|
22
|
+
contentBytes: Buffer.byteLength(content, "utf8"),
|
|
23
|
+
hash: createHash("sha256").update(content).digest("hex")
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function injectDebugId(rawMap, debugId) {
|
|
28
|
+
const parsed = JSON.parse(rawMap);
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
...parsed,
|
|
31
|
+
debugId
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function uploadSourceMaps({ apiKey, apiUrl, maps, releaseSlug }) {
|
|
35
|
+
const entries = prepareEntries(maps);
|
|
36
|
+
const slug = encodeURIComponent(releaseSlug);
|
|
37
|
+
const signReq = { files: entries.map((entry) => ({
|
|
38
|
+
path: entry.relPath,
|
|
39
|
+
sizeBytes: entry.contentBytes,
|
|
40
|
+
...richness(entry.content)
|
|
41
|
+
})) };
|
|
42
|
+
const signRes = await postJson({
|
|
43
|
+
apiKey,
|
|
44
|
+
url: `${apiUrl}/v1/releases/${slug}/source-maps/sign`,
|
|
45
|
+
body: signReq
|
|
46
|
+
});
|
|
47
|
+
const byPath = new Map(entries.map((entry) => [entry.relPath, entry]));
|
|
48
|
+
let totalBytes = 0;
|
|
49
|
+
await mapWithConcurrency(signRes.uploads, PUT_CONCURRENCY, async (upload) => {
|
|
50
|
+
const entry = byPath.get(upload.path);
|
|
51
|
+
if (!entry) throw new Error(`Sign response referenced unknown path "${upload.path}"`);
|
|
52
|
+
totalBytes += entry.contentBytes;
|
|
53
|
+
await putToR2(upload.presignedUrl, entry.content);
|
|
54
|
+
});
|
|
55
|
+
const completeReq = {
|
|
56
|
+
files: entries.map((entry) => ({
|
|
57
|
+
path: entry.relPath,
|
|
58
|
+
hash: entry.hash,
|
|
59
|
+
debugId: entry.debugId,
|
|
60
|
+
chunkUrl: entry.chunkUrl
|
|
61
|
+
})),
|
|
62
|
+
sourceFileCount: entries.length,
|
|
63
|
+
bundler: "tsc"
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
fileCount: (await postJson({
|
|
67
|
+
apiKey,
|
|
68
|
+
url: `${apiUrl}/v1/releases/${slug}/source-maps/complete`,
|
|
69
|
+
body: completeReq
|
|
70
|
+
})).fileCount,
|
|
71
|
+
totalBytes
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function richness(content) {
|
|
75
|
+
const parsed = JSON.parse(content);
|
|
76
|
+
return {
|
|
77
|
+
hasSourcesContent: Array.isArray(parsed["sourcesContent"]) && parsed["sourcesContent"].length > 0,
|
|
78
|
+
hasNames: Array.isArray(parsed["names"]) && parsed["names"].length > 0,
|
|
79
|
+
hasFile: typeof parsed["file"] === "string" && parsed["file"].length > 0,
|
|
80
|
+
mappingsPresent: typeof parsed["mappings"] === "string" && parsed["mappings"].length > 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function postJson({ apiKey, url, body }) {
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"content-type": "application/json",
|
|
88
|
+
accept: "application/json",
|
|
89
|
+
"user-agent": CLI_USER_AGENT,
|
|
90
|
+
"x-api-key": apiKey
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(body)
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const detail = await response.text().catch(() => "");
|
|
96
|
+
throw new Error(`POST ${url} → ${response.status} ${detail}`);
|
|
97
|
+
}
|
|
98
|
+
return await response.json();
|
|
99
|
+
}
|
|
100
|
+
async function putToR2(presignedUrl, content) {
|
|
101
|
+
const response = await fetch(presignedUrl, {
|
|
102
|
+
method: "PUT",
|
|
103
|
+
headers: { "content-type": "application/json" },
|
|
104
|
+
body: content
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const detail = await response.text().catch(() => "");
|
|
108
|
+
throw new Error(`PUT R2 → ${response.status} ${detail}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
112
|
+
let cursor = 0;
|
|
113
|
+
async function worker() {
|
|
114
|
+
while (cursor < items.length) {
|
|
115
|
+
const item = items[cursor++];
|
|
116
|
+
if (item !== void 0) await fn(item);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { uploadSourceMaps };
|
|
@@ -0,0 +1 @@
|
|
|
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\";\n\nconst PUT_CONCURRENCY = 8;\nconst CLI_USER_AGENT = \"@interfere/cli@10.0.0-canary.0\";\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\": CLI_USER_AGENT,\n \"x-api-key\": 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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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@interfere/cli",
|
|
3
|
+
"version": "0.0.1-canary.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Command-line tool for Interfere. Uploads source maps and confirms releases as part of your CI deploy.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"observability",
|
|
8
|
+
"cli",
|
|
9
|
+
"source-maps",
|
|
10
|
+
"release-management",
|
|
11
|
+
"interfere"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://interfere.com",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "mailto:support@interfere.com"
|
|
16
|
+
},
|
|
17
|
+
"author": "Interfere <support@interfere.com> (https://interfere.com)",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/interfere-inc/interfere.git",
|
|
21
|
+
"directory": "src/packages/public/cli"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"interfere": "./dist/index.mjs"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"tag": "canary"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"test": "vitest run --passWithNoTests",
|
|
37
|
+
"typecheck": "tsc --noEmit --incremental"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@interfere/constants": "^9.0.1",
|
|
41
|
+
"@interfere/types": "^9.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@interfere/test-utils": "^9.0.0",
|
|
45
|
+
"@interfere/typescript-config": "^9.0.0",
|
|
46
|
+
"@types/node": "^24.12.0",
|
|
47
|
+
"@vitest/coverage-v8": "^4.1.6",
|
|
48
|
+
"tsdown": "^0.22.0",
|
|
49
|
+
"typescript": "^6.0.3",
|
|
50
|
+
"vitest": "^4.1.6"
|
|
51
|
+
}
|
|
52
|
+
}
|