@getargus/build-tools 0.0.0-stage → 0.3.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/dist/index.cjs ADDED
@@ -0,0 +1,1548 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ ARTIFACT_SET_GLOBAL: () => ARTIFACT_SET_GLOBAL,
24
+ NEXT_PUBLIC_ENV: () => NEXT_PUBLIC_ENV,
25
+ NEXT_SERVER_ENV: () => NEXT_SERVER_ENV,
26
+ applyInit: () => applyInit,
27
+ artifactSetPrelude: () => artifactSetPrelude,
28
+ assertArtifactSetId: () => assertArtifactSetId,
29
+ browserSourceMaps: () => browserSourceMaps,
30
+ buildManifest: () => buildManifest,
31
+ classifyArtifact: () => classifyArtifact,
32
+ detectPackageManager: () => detectPackageManager,
33
+ detectProject: () => detectProject,
34
+ formatCommand: () => formatCommand,
35
+ injectArtifactSetId: () => injectArtifactSetId,
36
+ isJsOnlyPlan: () => isJsOnlyPlan,
37
+ isValidArtifactSetId: () => isValidArtifactSetId,
38
+ manifestName: () => manifestName,
39
+ newArtifactSetId: () => newArtifactSetId,
40
+ nextArtifactSetIds: () => nextArtifactSetIds,
41
+ nextUrlPrefix: () => nextUrlPrefix,
42
+ planExpoExport: () => planExpoExport,
43
+ planFiles: () => planFiles,
44
+ planInit: () => planInit,
45
+ planNative: () => planNative,
46
+ planNext: () => planNext,
47
+ renderInit: () => renderInit,
48
+ resolveArtifactSetId: () => resolveArtifactSetId,
49
+ runCli: () => main,
50
+ runInit: () => runInit,
51
+ runUploads: () => runUploads,
52
+ uploadArgs: () => uploadArgs,
53
+ uploadNextBuild: () => uploadNextBuild,
54
+ uploadPlanOverHttp: () => uploadPlanOverHttp,
55
+ walkFiles: () => walkFiles,
56
+ withArgusMetro: () => withArgus,
57
+ withArgusNext: () => withArgus2,
58
+ writeArtifactSetModule: () => writeArtifactSetModule
59
+ });
60
+ module.exports = __toCommonJS(src_exports);
61
+
62
+ // src/ids.ts
63
+ var import_node_crypto = require("crypto");
64
+ var import_node_fs = require("fs");
65
+ var import_node_path = require("path");
66
+ var ARTIFACT_SET_GLOBAL = "__ARGUS_ARTIFACT_SET_ID__";
67
+ var NEXT_PUBLIC_ENV = "NEXT_PUBLIC_ARGUS_ARTIFACT_SET_ID";
68
+ var NEXT_SERVER_ENV = "ARGUS_SERVER_ARTIFACT_SET_ID";
69
+ var RE_ARTIFACT_SET_ID = /^[A-Za-z0-9._:-]{1,128}$/;
70
+ var GENERATED_MARK = "/* generated by @getargus/build-tools \u2014 do not edit, do not commit */";
71
+ function isValidArtifactSetId(id) {
72
+ return typeof id === "string" && RE_ARTIFACT_SET_ID.test(id);
73
+ }
74
+ function assertArtifactSetId(id, what = "artifact set id") {
75
+ if (!isValidArtifactSetId(id)) {
76
+ throw new Error(`Argus: invalid ${what} ${JSON.stringify(id)} \u2014 expected 1..128 chars of [A-Za-z0-9._:-]`);
77
+ }
78
+ return id;
79
+ }
80
+ function newArtifactSetId(prefix = "build", now = Date.now()) {
81
+ const clean = Array.from(String(prefix)).filter((c) => /[A-Za-z0-9._-]/.test(c)).slice(0, 80).join("");
82
+ const b = (0, import_node_crypto.randomBytes)(16);
83
+ b.writeUIntBE(now, 0, 6);
84
+ b[6] = b[6] & 15 | 112;
85
+ b[8] = b[8] & 63 | 128;
86
+ return `${clean || "build"}-${b.toString("hex")}`;
87
+ }
88
+ function resolveArtifactSetId(input, platform, env = process.env) {
89
+ const fromEnv = env[`ARGUS_ARTIFACT_SET_ID_${platform.toUpperCase()}`] || env.ARGUS_ARTIFACT_SET_ID;
90
+ const fromOption = typeof input === "function" ? input(platform) : typeof input === "string" ? input : input?.[platform];
91
+ const id = fromEnv || fromOption;
92
+ return id ? assertArtifactSetId(id, `artifact set id for ${platform}`) : void 0;
93
+ }
94
+ function artifactSetPrelude(artifactSetId) {
95
+ const id = JSON.stringify(assertArtifactSetId(artifactSetId));
96
+ return `${GENERATED_MARK}
97
+ (function (g) {
98
+ g.${ARTIFACT_SET_GLOBAL} = ${id};
99
+ })(
100
+ typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this,
101
+ );
102
+ `;
103
+ }
104
+ function writeArtifactSetModule(options) {
105
+ const file = (0, import_node_path.resolve)(options.file);
106
+ const content = artifactSetPrelude(options.artifactSetId);
107
+ if ((0, import_node_fs.existsSync)(file)) {
108
+ const current = (0, import_node_fs.readFileSync)(file, "utf8");
109
+ if (current === content) return { file, changed: false };
110
+ if (!current.startsWith(GENERATED_MARK)) {
111
+ throw new Error(`Argus: refusing to overwrite ${file}: it was not generated by @getargus/build-tools (built output is never patched)`);
112
+ }
113
+ }
114
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(file), { recursive: true });
115
+ (0, import_node_fs.writeFileSync)(file, content);
116
+ return { file, changed: true };
117
+ }
118
+ var injectArtifactSetId = writeArtifactSetModule;
119
+
120
+ // src/metro.ts
121
+ var import_node_module = require("module");
122
+ var import_node_path2 = require("path");
123
+ var PLATFORMS = ["ios", "android", "web"];
124
+ function withArgus(metroConfig, options = {}) {
125
+ const projectRoot = (0, import_node_path2.resolve)(options.projectRoot ?? metroConfig.projectRoot ?? process.cwd());
126
+ const outDir = (0, import_node_path2.resolve)(options.outDir ?? (0, import_node_path2.join)(projectRoot, ".argus"));
127
+ const env = options.env ?? process.env;
128
+ const files = {};
129
+ for (const platform of PLATFORMS) {
130
+ const id = resolveArtifactSetId(options.artifactSetId, platform, env);
131
+ if (id) files[platform] = writeArtifactSetModule({ file: (0, import_node_path2.join)(outDir, `argus-artifact-set.${platform}.js`), artifactSetId: id }).file;
132
+ }
133
+ const previous = metroConfig.serializer?.getPolyfills;
134
+ const warned = /* @__PURE__ */ new Set();
135
+ const basePolyfills = (ctx) => {
136
+ if (typeof previous === "function") return previous(ctx);
137
+ try {
138
+ const rnPolyfills = (0, import_node_module.createRequire)((0, import_node_path2.join)(projectRoot, "package.json"))("@react-native/js-polyfills");
139
+ return rnPolyfills();
140
+ } catch {
141
+ return [];
142
+ }
143
+ };
144
+ const getPolyfills = (ctx) => {
145
+ const base = basePolyfills(ctx);
146
+ const platform = String(ctx?.platform ?? "");
147
+ const file = files[platform];
148
+ if (!file) {
149
+ if (!warned.has(platform)) {
150
+ warned.add(platform);
151
+ (options.onMissing ?? ((p) => console.warn(`[argus] no artifact set id for platform "${p}": this bundle will not be matched to its source map (set ARGUS_ARTIFACT_SET_ID${p ? `_${p.toUpperCase()}` : ""})`)))(platform);
152
+ }
153
+ return base;
154
+ }
155
+ return [...base, file];
156
+ };
157
+ return { ...metroConfig, serializer: { ...metroConfig.serializer, getPolyfills } };
158
+ }
159
+
160
+ // src/next.ts
161
+ var import_node_module2 = require("module");
162
+ var import_node_path5 = require("path");
163
+
164
+ // src/next-upload.ts
165
+ var import_node_fs4 = require("fs");
166
+
167
+ // src/collect.ts
168
+ var import_node_fs2 = require("fs");
169
+ var import_node_path3 = require("path");
170
+ function classifyArtifact(path) {
171
+ const lower = (0, import_node_path3.basename)(path).toLowerCase();
172
+ if (lower.endsWith(".map")) return "sourcemap";
173
+ if (lower === "mapping.txt" || lower.endsWith(".mapping.txt")) return "proguard";
174
+ if (lower.endsWith(".so") || lower.endsWith(".so.dbg")) return "elf";
175
+ const parts = path.split(/[\\/]/);
176
+ if (parts.includes("DWARF") && parts.some((p) => p.endsWith(".dSYM"))) return "dsym";
177
+ if (/\.(js|mjs|cjs|bundle|jsbundle|hbc)$/.test(lower)) return "js_bundle";
178
+ return void 0;
179
+ }
180
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "cache", ".cache", "Pods"]);
181
+ function walkFiles(dir, opts = {}) {
182
+ const skip = opts.skipDirs ?? SKIP_DIRS;
183
+ const out = [];
184
+ const visit = (d) => {
185
+ let entries;
186
+ try {
187
+ entries = (0, import_node_fs2.readdirSync)(d, { withFileTypes: true });
188
+ } catch {
189
+ return;
190
+ }
191
+ for (const e of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
192
+ const p = (0, import_node_path3.join)(d, e.name);
193
+ if (e.isDirectory()) {
194
+ if (!skip.has(e.name)) visit(p);
195
+ } else if (e.isFile()) out.push(p);
196
+ }
197
+ };
198
+ visit(dir);
199
+ return out;
200
+ }
201
+ function describe(paths) {
202
+ const out = [];
203
+ for (const p of paths) {
204
+ const files = (0, import_node_fs2.statSync)(p).isDirectory() ? walkFiles(p, { skipDirs: /* @__PURE__ */ new Set() }) : [p];
205
+ for (const f of files) {
206
+ const kind = classifyArtifact(f);
207
+ if (kind) out.push({ path: f, kind, size: (0, import_node_fs2.statSync)(f).size });
208
+ }
209
+ }
210
+ return out;
211
+ }
212
+ function meta(m) {
213
+ const out = {};
214
+ for (const k of ["release", "dist", "nativeRelease", "nativeBuild", "runtimeVersion", "gitSha"]) {
215
+ if (m[k]) out[k] = m[k];
216
+ }
217
+ return out;
218
+ }
219
+ var RE_JS = /\.(js|mjs|cjs|bundle|jsbundle|hbc)$/;
220
+ var RE_JS_MAP = /\.(js|mjs|cjs|bundle|jsbundle|hbc)\.map$/;
221
+ function referencedMap(file) {
222
+ let tail = "";
223
+ try {
224
+ const size = (0, import_node_fs2.statSync)(file).size;
225
+ const buf = Buffer.alloc(Math.min(size, 1024));
226
+ const fd = (0, import_node_fs2.openSync)(file, "r");
227
+ try {
228
+ (0, import_node_fs2.readSync)(fd, buf, 0, buf.length, size - buf.length);
229
+ } finally {
230
+ (0, import_node_fs2.closeSync)(fd);
231
+ }
232
+ tail = buf.toString("utf8");
233
+ } catch {
234
+ return void 0;
235
+ }
236
+ const ref = /\/\/[#@] sourceMappingURL=([^\s'"]+)\s*$/.exec(tail)?.[1];
237
+ if (!ref || /^[a-z][a-z0-9+.-]*:/i.test(ref)) return void 0;
238
+ return (0, import_node_path3.resolve)((0, import_node_path3.dirname)(file), ref.split(/[?#]/)[0]);
239
+ }
240
+ function jsPairs(dir, includeBundles, warnings, label) {
241
+ const files = walkFiles(dir);
242
+ const set = new Set(files);
243
+ const orphans = new Set(files.filter((f) => RE_JS_MAP.test(f.toLowerCase()) && !set.has(f.slice(0, -4))));
244
+ const generatedFor = {};
245
+ const referenced = /* @__PURE__ */ new Set();
246
+ if (orphans.size > 0) {
247
+ for (const f of files) {
248
+ if (!RE_JS.test(f.toLowerCase()) || set.has(`${f}.map`)) continue;
249
+ const map = referencedMap(f);
250
+ if (map && orphans.has(map) && !generatedFor[map]) {
251
+ generatedFor[map] = f;
252
+ referenced.add(f);
253
+ }
254
+ }
255
+ }
256
+ const out = [];
257
+ let unmapped = 0;
258
+ for (const f of files) {
259
+ const lower = f.toLowerCase();
260
+ if (RE_JS_MAP.test(lower)) {
261
+ const generated = generatedFor[f] ?? (set.has(f.slice(0, -4)) ? f.slice(0, -4) : void 0);
262
+ if (!generated) warnings.push(`${label}: ${f} has no generated file next to it`);
263
+ out.push(f);
264
+ if (includeBundles && generated) out.push(generated);
265
+ } else if (RE_JS.test(lower) && !set.has(`${f}.map`) && !referenced.has(f)) unmapped++;
266
+ }
267
+ if (unmapped > 0) warnings.push(`${label}: ${unmapped} JS file(s) without a source map were skipped`);
268
+ return { paths: [...new Set(out)].sort(), generatedFor };
269
+ }
270
+ var NO_PAIRS = { paths: [], generatedFor: {} };
271
+ var renamed = (pairs) => Object.keys(pairs.generatedFor).length > 0 ? { generatedFor: pairs.generatedFor } : {};
272
+ function planNext(options) {
273
+ const projectDir = (0, import_node_path3.resolve)(options.projectDir ?? process.cwd());
274
+ const dist = (0, import_node_path3.resolve)(projectDir, options.distDir ?? ".next");
275
+ if (!(0, import_node_fs2.existsSync)(dist)) throw new Error(`Argus: Next build output not found at ${dist} (run \`next build\` first)`);
276
+ const web = assertArtifactSetId(options.artifactSetId);
277
+ const node = assertArtifactSetId(options.serverArtifactSetId ?? `${web}-node`, "server artifact set id");
278
+ if (web === node) throw new Error("Argus: browser and server chunks must use different artifact set ids");
279
+ const include = options.includeBundles !== false;
280
+ const plans = [];
281
+ const notes = [];
282
+ const browserWarnings = [];
283
+ const browser = (0, import_node_fs2.existsSync)((0, import_node_path3.join)(dist, "static")) ? jsPairs((0, import_node_path3.join)(dist, "static"), include, browserWarnings, "next:browser") : NO_PAIRS;
284
+ if (browser.paths.some((f) => f.endsWith(".map"))) {
285
+ plans.push({
286
+ label: "next:browser",
287
+ artifactSetId: web,
288
+ platform: "web",
289
+ paths: browser.paths,
290
+ files: describe(browser.paths),
291
+ stripPrefix: dist,
292
+ urlPrefix: options.urlPrefix ?? "/_next/",
293
+ ...renamed(browser),
294
+ ...meta(options),
295
+ warnings: browserWarnings
296
+ });
297
+ } else notes.push("next:browser: no source maps under static/ (is productionBrowserSourceMaps enabled?)");
298
+ const serverWarnings = [];
299
+ const server = (0, import_node_fs2.existsSync)((0, import_node_path3.join)(dist, "server")) ? jsPairs((0, import_node_path3.join)(dist, "server"), include, serverWarnings, "next:server") : NO_PAIRS;
300
+ if (server.paths.some((f) => f.endsWith(".map"))) {
301
+ plans.push({
302
+ label: "next:server",
303
+ artifactSetId: node,
304
+ platform: "node",
305
+ paths: server.paths,
306
+ files: describe(server.paths),
307
+ // Server frames are filesystem paths ending in `.next/server/...`.
308
+ stripPrefix: projectDir,
309
+ urlPrefix: "",
310
+ ...renamed(server),
311
+ ...meta(options),
312
+ warnings: serverWarnings
313
+ });
314
+ } else notes.push("next:server: no source maps under server/ (server source maps are not emitted by default)");
315
+ if (plans.length === 0) throw new Error(`Argus: no source maps found in ${dist}. ${notes.join("; ")}`);
316
+ plans[0].warnings.push(...notes);
317
+ return plans;
318
+ }
319
+ function browserSourceMaps(projectDir, distDir = ".next") {
320
+ const dir = (0, import_node_path3.resolve)(projectDir, distDir, "static");
321
+ return (0, import_node_fs2.existsSync)(dir) ? walkFiles(dir).filter((f) => f.endsWith(".map")) : [];
322
+ }
323
+ function str(v) {
324
+ return typeof v === "string" && v ? v : void 0;
325
+ }
326
+ function planExpoExport(options) {
327
+ const dir = (0, import_node_path3.resolve)(options.exportDir ?? "dist");
328
+ const metadataPath = (0, import_node_path3.join)(dir, "metadata.json");
329
+ if (!(0, import_node_fs2.existsSync)(metadataPath)) throw new Error(`Argus: ${metadataPath} not found (run \`expo export\` first)`);
330
+ const metadata = JSON.parse((0, import_node_fs2.readFileSync)(metadataPath, "utf8"));
331
+ const available = Object.keys(metadata.fileMetadata ?? {}).filter((p) => p === "ios" || p === "android");
332
+ const wanted = options.platforms ?? available.filter((p) => options.artifactSetIds[p]);
333
+ if (wanted.length === 0) throw new Error("Argus: no platform to upload \u2014 pass an artifact set id for ios and/or android");
334
+ const ids = wanted.map((p) => options.artifactSetIds[p]);
335
+ if (new Set(ids).size !== ids.length) throw new Error("Argus: iOS and Android updates need different artifact set ids");
336
+ const plans = [];
337
+ for (const platform of wanted) {
338
+ const label = `expo-export:${platform}`;
339
+ const artifactSetId = assertArtifactSetId(options.artifactSetIds[platform], `artifact set id for ${platform}`);
340
+ const entry = metadata.fileMetadata?.[platform];
341
+ const bundleRel = str(entry?.bundle);
342
+ if (!bundleRel) throw new Error(`Argus: metadata.json has no bundle for ${platform}`);
343
+ const bundle = (0, import_node_path3.resolve)(dir, bundleRel);
344
+ if (!bundle.startsWith(dir + import_node_path3.sep)) throw new Error(`Argus: metadata.json bundle path escapes the export directory: ${bundleRel}`);
345
+ const map = `${bundle}.map`;
346
+ if (!(0, import_node_fs2.existsSync)(bundle)) throw new Error(`Argus: ${bundle} is missing`);
347
+ if (!(0, import_node_fs2.existsSync)(map)) throw new Error(`Argus: ${map} is missing \u2014 export with source maps (\`expo export --dump-sourcemap\`)`);
348
+ const warnings = [];
349
+ const given = typeof options.updateId === "string" ? wanted.length === 1 ? options.updateId : void 0 : options.updateId?.[platform];
350
+ if (typeof options.updateId === "string" && wanted.length > 1) warnings.push(`${label}: a single --update-id cannot apply to several platforms; pass one per platform`);
351
+ const updateId = given ?? str(entry?.updateId) ?? str(entry?.id) ?? str(metadata.updateId) ?? str(metadata.id);
352
+ if (!updateId) warnings.push(`${label}: no update id (not in metadata.json, none given) \u2014 the set is still matched by artifact_set_id`);
353
+ const parentSetId = options.parentSetIds?.[platform];
354
+ if (!parentSetId) warnings.push(`${label}: no parent set id \u2014 native frames of crashes during this update cannot be tied to the native build's symbols`);
355
+ const paths = [bundle, map];
356
+ plans.push({
357
+ label,
358
+ artifactSetId,
359
+ platform,
360
+ paths,
361
+ files: describe(paths),
362
+ // Named by file name: `entry-<hash>.hbc` ← `entry-<hash>.hbc.map`.
363
+ stripPrefix: (0, import_node_path3.dirname)(bundle),
364
+ urlPrefix: "",
365
+ ota: true,
366
+ updateId,
367
+ parentSetId: parentSetId ? assertArtifactSetId(parentSetId, `parent set id for ${platform}`) : void 0,
368
+ ...meta(options),
369
+ warnings
370
+ });
371
+ }
372
+ return plans;
373
+ }
374
+ function findDsyms(root) {
375
+ const out = [];
376
+ const visit = (d, depth) => {
377
+ if (depth > 12) return;
378
+ let entries;
379
+ try {
380
+ entries = (0, import_node_fs2.readdirSync)(d, { withFileTypes: true });
381
+ } catch {
382
+ return;
383
+ }
384
+ for (const e of entries) {
385
+ if (!e.isDirectory() || e.name === "node_modules" || e.name === ".git") continue;
386
+ const p = (0, import_node_path3.join)(d, e.name);
387
+ if (e.name.endsWith(".dSYM")) out.push(p);
388
+ else visit(p, depth + 1);
389
+ }
390
+ };
391
+ if (root.endsWith(".dSYM")) out.push(root);
392
+ else visit(root, 0);
393
+ return out.sort();
394
+ }
395
+ function planNative(options) {
396
+ const label = `native:${options.platform}`;
397
+ const artifactSetId = assertArtifactSetId(options.artifactSetId);
398
+ const warnings = [];
399
+ const paths = /* @__PURE__ */ new Set();
400
+ const roots = options.searchPaths.map((p) => (0, import_node_path3.resolve)(p));
401
+ for (const root of roots) if (!(0, import_node_fs2.existsSync)(root)) throw new Error(`Argus: ${root} does not exist`);
402
+ const jsMaps = [];
403
+ for (const root of roots) {
404
+ const isDir2 = (0, import_node_fs2.statSync)(root).isDirectory();
405
+ if (options.platform === "ios") {
406
+ if (isDir2) for (const d of findDsyms(root)) paths.add(d);
407
+ }
408
+ const files2 = isDir2 ? root.endsWith(".dSYM") ? [] : walkFiles(root, { skipDirs: /* @__PURE__ */ new Set(["node_modules", ".git", "Pods"]) }) : [root];
409
+ for (const f of files2) {
410
+ const name = (0, import_node_path3.basename)(f).toLowerCase();
411
+ if (f.split(import_node_path3.sep).some((part) => part.endsWith(".dSYM"))) continue;
412
+ if (options.platform === "android") {
413
+ if (name === "mapping.txt") paths.add(f);
414
+ else if (options.includeNativeLibs && name.endsWith(".so")) paths.add(f);
415
+ }
416
+ if (/\.(jsbundle|bundle)\.map$/.test(name)) jsMaps.push(f);
417
+ }
418
+ }
419
+ if (options.sourcemap) jsMaps.unshift((0, import_node_path3.resolve)(options.sourcemap));
420
+ const uniqueMaps = [...new Set(jsMaps)];
421
+ if (uniqueMaps.length > 1 && !options.sourcemap) {
422
+ warnings.push(`${label}: several JS source maps found, using ${uniqueMaps[0]} (pass --sourcemap to choose): ${uniqueMaps.slice(1).join(", ")}`);
423
+ }
424
+ const map = uniqueMaps[0];
425
+ if (map) {
426
+ if (!(0, import_node_fs2.existsSync)(map)) throw new Error(`Argus: ${map} does not exist`);
427
+ paths.add(map);
428
+ const bundle = options.bundle ? (0, import_node_path3.resolve)(options.bundle) : map.slice(0, -4);
429
+ if ((0, import_node_fs2.existsSync)(bundle)) {
430
+ if ((0, import_node_path3.basename)(bundle) !== (0, import_node_path3.basename)(map).slice(0, -4)) {
431
+ warnings.push(`${label}: bundle ${(0, import_node_path3.basename)(bundle)} and map ${(0, import_node_path3.basename)(map)} have different names; frames are matched against "${(0, import_node_path3.basename)(map).slice(0, -4)}"`);
432
+ }
433
+ paths.add(bundle);
434
+ }
435
+ } else warnings.push(`${label}: no JS source map found (*.bundle.map / *.jsbundle.map) \u2014 JS frames of this build will stay unsymbolicated`);
436
+ const list = [...paths].sort();
437
+ const files = describe(list);
438
+ if (options.platform === "ios" && !files.some((f) => f.kind === "dsym")) warnings.push(`${label}: no .dSYM found \u2014 native iOS frames will stay unsymbolicated`);
439
+ if (options.platform === "android") {
440
+ const mappings = files.filter((f) => f.kind === "proguard");
441
+ if (mappings.length === 0) warnings.push(`${label}: no mapping.txt found \u2014 fine only if R8/ProGuard is disabled for this build`);
442
+ if (mappings.length > 1) throw new Error(`Argus: several mapping.txt files found (${mappings.map((m) => m.path).join(", ")}); point --dir at one build variant`);
443
+ }
444
+ if (list.length === 0) throw new Error(`Argus: nothing to upload for ${label} under ${roots.join(", ")}`);
445
+ return { label, artifactSetId, platform: options.platform, paths: list, files, ...meta(options), warnings };
446
+ }
447
+ function planFiles(options) {
448
+ const label = `files:${options.platform}`;
449
+ const warnings = [];
450
+ const paths = [];
451
+ const generatedFor = {};
452
+ for (const p of options.paths.map((x) => (0, import_node_path3.resolve)(x))) {
453
+ if (!(0, import_node_fs2.existsSync)(p)) throw new Error(`Argus: ${p} does not exist`);
454
+ if ((0, import_node_fs2.statSync)(p).isDirectory()) {
455
+ const pairs = jsPairs(p, options.includeBundles !== false, warnings, label);
456
+ paths.push(...pairs.paths);
457
+ Object.assign(generatedFor, pairs.generatedFor);
458
+ } else paths.push(p);
459
+ }
460
+ const unique = [...new Set(paths)].sort();
461
+ if (unique.length === 0) throw new Error(`Argus: no artifacts found in ${options.paths.join(", ")}`);
462
+ const plan = {
463
+ label,
464
+ artifactSetId: assertArtifactSetId(options.artifactSetId),
465
+ platform: options.platform,
466
+ paths: unique,
467
+ files: describe(unique),
468
+ ...meta(options),
469
+ warnings
470
+ };
471
+ if (options.stripPrefix !== void 0) plan.stripPrefix = (0, import_node_path3.resolve)(options.stripPrefix);
472
+ if (options.urlPrefix !== void 0) plan.urlPrefix = options.urlPrefix;
473
+ if (Object.keys(generatedFor).length > 0) plan.generatedFor = generatedFor;
474
+ return plan;
475
+ }
476
+ function uploadArgs(plan, opts = {}) {
477
+ const args = ["artifacts", "upload", "--set-id", plan.artifactSetId, "--platform", plan.platform];
478
+ const flag = (name, value) => {
479
+ if (value !== void 0 && value !== "") args.push(name, value);
480
+ };
481
+ flag("--url", opts.url);
482
+ flag("--release", plan.release);
483
+ flag("--dist", plan.dist);
484
+ flag("--native-release", plan.nativeRelease);
485
+ flag("--native-build", plan.nativeBuild);
486
+ flag("--runtime-version", plan.runtimeVersion);
487
+ if (plan.ota) args.push("--ota");
488
+ flag("--update-id", plan.updateId);
489
+ flag("--parent-set-id", plan.parentSetId);
490
+ flag("--git-sha", plan.gitSha);
491
+ flag("--strip-prefix", plan.stripPrefix);
492
+ flag("--url-prefix", plan.urlPrefix);
493
+ if (opts.dryRun) args.push("--dry-run");
494
+ args.push("--", ...plan.paths);
495
+ return args;
496
+ }
497
+ function manifestName(plan, file) {
498
+ const generated = file.endsWith(".map") ? plan.generatedFor?.[file] ?? file.slice(0, -4) : file;
499
+ const prefix = plan.stripPrefix;
500
+ const rel = prefix && (generated === prefix || generated.startsWith(prefix.endsWith(import_node_path3.sep) ? prefix : prefix + import_node_path3.sep)) ? (0, import_node_path3.relative)(prefix, generated) : (0, import_node_path3.basename)(generated);
501
+ return `${plan.urlPrefix ?? ""}${rel.split(import_node_path3.sep).join("/").replace(/^\/+/, "")}`;
502
+ }
503
+ function formatCommand(bin, args, cwd = process.cwd()) {
504
+ const quote = (s2) => /^[A-Za-z0-9_./:@=+-]+$/.test(s2) ? s2 : `'${s2.replace(/'/g, `'\\''`)}'`;
505
+ const short = (s2) => {
506
+ if (!s2.startsWith(cwd + import_node_path3.sep)) return s2;
507
+ return (0, import_node_path3.relative)(cwd, s2);
508
+ };
509
+ return [bin, ...args.map((a) => quote(short(a)))].join(" ");
510
+ }
511
+
512
+ // src/upload.ts
513
+ var import_node_child_process = require("child_process");
514
+
515
+ // src/http-upload.ts
516
+ var import_node_crypto2 = require("crypto");
517
+ var import_node_fs3 = require("fs");
518
+ var import_node_path4 = require("path");
519
+ var NATIVE_KINDS = /* @__PURE__ */ new Set(["dsym", "elf", "proguard"]);
520
+ function isJsOnlyPlan(plan) {
521
+ return plan.files.length > 0 && plan.files.every((f) => !NATIVE_KINDS.has(f.kind));
522
+ }
523
+ function sha256File(path) {
524
+ return new Promise((resolvePromise, reject) => {
525
+ const hash = (0, import_node_crypto2.createHash)("sha256");
526
+ (0, import_node_fs3.createReadStream)(path).on("data", (chunk) => hash.update(chunk)).on("error", reject).on("end", () => resolvePromise(hash.digest("hex")));
527
+ });
528
+ }
529
+ function isHermesMap(path) {
530
+ const needle = "x_facebook_sources";
531
+ const head = Buffer.alloc(256 * 1024);
532
+ const fd = (0, import_node_fs3.openSync)(path, "r");
533
+ let n = 0;
534
+ try {
535
+ n = (0, import_node_fs3.readSync)(fd, head, 0, head.length, 0);
536
+ } finally {
537
+ (0, import_node_fs3.closeSync)(fd);
538
+ }
539
+ if (head.subarray(0, n).includes(needle)) return true;
540
+ return (0, import_node_fs3.statSync)(path).size > n && (0, import_node_fs3.readFileSync)(path).includes(needle);
541
+ }
542
+ function setName(plan, file) {
543
+ const prefix = plan.stripPrefix;
544
+ const inside = prefix && (file === prefix || file.startsWith(prefix.endsWith(import_node_path4.sep) ? prefix : prefix + import_node_path4.sep));
545
+ return (inside ? (0, import_node_path4.relative)(prefix, file) : file).split(import_node_path4.sep).join("/").replace(/^\/+/, "");
546
+ }
547
+ async function buildManifest(plan) {
548
+ const found = [];
549
+ const paths = [...new Set(plan.files.map((f) => f.path))].sort();
550
+ for (const path of paths) {
551
+ const kind = plan.files.find((f) => f.path === path).kind;
552
+ if (NATIVE_KINDS.has(kind)) throw new Error(`Argus: ${(0, import_node_path4.basename)(path)} is a native symbol file; upload it with the argus CLI`);
553
+ const size = (0, import_node_fs3.statSync)(path).size;
554
+ if (size === 0) throw new Error(`Argus: ${path} is empty`);
555
+ const sha256 = await sha256File(path);
556
+ if (kind === "sourcemap") {
557
+ found.push({
558
+ path,
559
+ file: { kind: isHermesMap(path) ? "hermes_sourcemap" : "sourcemap", name: setName(plan, path), bundle_name: manifestName(plan, path), sha256, size }
560
+ });
561
+ } else {
562
+ found.push({ path, file: { kind: "js_bundle", name: manifestName(plan, path), sha256, size } });
563
+ }
564
+ }
565
+ found.sort((a, b) => a.file.name < b.file.name ? -1 : a.file.name > b.file.name ? 1 : 0);
566
+ const unique = found.filter((f, i) => i === 0 || found[i - 1].file.name !== f.file.name);
567
+ if (unique.length === 0) throw new Error("Argus: no artifacts found in the given paths");
568
+ const manifest = {
569
+ schema_version: 1,
570
+ artifact_set_id: plan.artifactSetId,
571
+ platform: plan.platform,
572
+ is_embedded: !plan.ota,
573
+ files: unique.map((f) => f.file)
574
+ };
575
+ const set = (key, value) => {
576
+ if (value !== void 0 && value !== "") manifest[key] = value;
577
+ };
578
+ set("release", plan.release);
579
+ set("dist", plan.dist);
580
+ set("native_release", plan.nativeRelease);
581
+ set("native_build", plan.nativeBuild);
582
+ set("runtime_version", plan.runtimeVersion);
583
+ set("update_id", plan.updateId);
584
+ set("git_sha", plan.gitSha);
585
+ set("parent_artifact_set_id", plan.parentSetId);
586
+ return { manifest, found: unique };
587
+ }
588
+ var defaultFetch = async (url, init) => {
589
+ const res = await fetch(url, init);
590
+ return { status: res.status, text: () => res.text() };
591
+ };
592
+ async function call(doFetch, method, url, key, body) {
593
+ const headers = { authorization: `Bearer ${key}` };
594
+ let payload;
595
+ if (body?.json !== void 0) {
596
+ headers["content-type"] = "application/json";
597
+ payload = JSON.stringify(body.json);
598
+ } else if (body?.bytes) {
599
+ headers["content-type"] = "application/octet-stream";
600
+ payload = body.bytes;
601
+ }
602
+ const init = { method, headers };
603
+ if (payload !== void 0) init.body = payload;
604
+ let res;
605
+ try {
606
+ res = await doFetch(url, init);
607
+ } catch (e) {
608
+ throw new Error(`Argus: cannot reach ${new URL(url).origin} (${e instanceof Error ? e.message : String(e)})`);
609
+ }
610
+ const text = await res.text();
611
+ let parsed;
612
+ try {
613
+ parsed = text ? JSON.parse(text) : {};
614
+ } catch {
615
+ parsed = void 0;
616
+ }
617
+ if (res.status < 200 || res.status >= 300) {
618
+ const detail = parsed && typeof parsed === "object" ? JSON.stringify(parsed) : text.slice(0, 300);
619
+ throw new Error(`Argus: ${method} ${new URL(url).pathname} \u2192 HTTP ${res.status} ${detail}`);
620
+ }
621
+ if (!parsed || typeof parsed !== "object") throw new Error(`Argus: ${method} ${new URL(url).pathname} did not return JSON (is ARGUS_URL the server's base URL?)`);
622
+ return parsed;
623
+ }
624
+ async function uploadPlanOverHttp(plan, options) {
625
+ const log = options.log ?? ((line) => console.error(line));
626
+ const { manifest, found } = await buildManifest(plan);
627
+ if (options.dryRun) {
628
+ log(JSON.stringify(manifest, null, 2));
629
+ return { status: "dry_run", uploaded: 0, files: found.length };
630
+ }
631
+ const base = options.url.replace(/\/+$/, "");
632
+ if (!/^https?:\/\//i.test(base)) throw new Error("Argus: ARGUS_URL must be the server base URL (https://host)");
633
+ const doFetch = options.fetch ?? defaultFetch;
634
+ const id = encodeURIComponent(plan.artifactSetId);
635
+ const created = await call(doFetch, "POST", `${base}/v1/artifact-sets`, options.uploadKey, { json: manifest });
636
+ const states = Array.isArray(created.files) ? created.files : [];
637
+ const done = /* @__PURE__ */ new Set();
638
+ let uploaded = 0;
639
+ for (const f of states) {
640
+ if (!f.sha256 || f.state === "uploaded" || done.has(f.sha256)) continue;
641
+ done.add(f.sha256);
642
+ const local = found.find((x) => x.file.sha256 === f.sha256);
643
+ if (!local) throw new Error(`Argus: server asks for unknown blob ${f.sha256}`);
644
+ log(`[argus] uploading ${local.file.name} (${local.file.size} bytes)`);
645
+ await call(doFetch, "PUT", `${base}/v1/artifact-sets/${id}/files/${f.sha256}`, options.uploadKey, { bytes: (0, import_node_fs3.readFileSync)(local.path) });
646
+ uploaded++;
647
+ }
648
+ const final = await call(doFetch, "POST", `${base}/v1/artifact-sets/${id}/finalize`, options.uploadKey);
649
+ const status = typeof final.status === "string" ? final.status : "unknown";
650
+ const queued = typeof final.reprocess_queued === "number" ? final.reprocess_queued : 0;
651
+ log(`[argus] set ${plan.artifactSetId}: ${status}, ${found.length} file(s), ${uploaded} uploaded${queued > 0 ? `, ${queued} waiting event(s) queued for reprocessing` : ""}`);
652
+ if (status !== "ready") throw new Error(`Argus: artifact set ${plan.artifactSetId} is not ready (${status})`);
653
+ return { status, uploaded, files: found.length };
654
+ }
655
+
656
+ // src/upload.ts
657
+ function defaultExec(bin, args, opts) {
658
+ return new Promise((resolvePromise, reject) => {
659
+ const child = (0, import_node_child_process.spawn)(bin, args, { stdio: "inherit", env: opts.env, cwd: opts.cwd });
660
+ child.on("error", (e) => {
661
+ reject(e.code === "ENOENT" ? new Error(`Argus: cannot run "${bin}" \u2014 install the argus CLI or set ARGUS_BIN`) : e);
662
+ });
663
+ child.on("close", (code) => resolvePromise(code ?? 1));
664
+ });
665
+ }
666
+ async function runUploads(plans, options = {}) {
667
+ const env = options.env ?? process.env;
668
+ const cwd = options.cwd ?? process.cwd();
669
+ const bin = options.bin ?? env.ARGUS_BIN ?? "argus";
670
+ const exec = options.exec ?? defaultExec;
671
+ const log = options.log ?? ((line) => console.error(line));
672
+ const requested = options.transport ?? env.ARGUS_UPLOAD_TRANSPORT ?? "auto";
673
+ if (requested !== "auto" && requested !== "http" && requested !== "cli") throw new Error(`Argus: unknown upload transport "${requested}" (auto, http or cli)`);
674
+ const transport = requested;
675
+ const results = [];
676
+ if (!options.dryRun && !env.ARGUS_UPLOAD_KEY) throw new Error("Argus: ARGUS_UPLOAD_KEY is not set (an upload key, never the public ingest key)");
677
+ for (const plan of plans) {
678
+ const argOpts = {};
679
+ if (options.dryRun) argOpts.dryRun = true;
680
+ if (options.url) argOpts.url = options.url;
681
+ const args = uploadArgs(plan, argOpts);
682
+ const command = formatCommand(bin, args, cwd);
683
+ for (const w of plan.warnings) log(`[argus] warning: ${w}`);
684
+ log(`[argus] ${plan.label}: ${plan.files.length} file(s) \u2192 set ${plan.artifactSetId}`);
685
+ const viaHttp = transport === "http" || transport === "auto" && !options.exec && !env.ARGUS_BIN && isJsOnlyPlan(plan);
686
+ if (viaHttp) {
687
+ const url = options.url ?? env.ARGUS_URL;
688
+ if (!url) throw new Error("Argus: ARGUS_URL is not set (the server base URL, e.g. https://argus.example.com)");
689
+ const http = { url, uploadKey: env.ARGUS_UPLOAD_KEY ?? "", log };
690
+ if (options.dryRun) http.dryRun = true;
691
+ if (options.fetch) http.fetch = options.fetch;
692
+ let code2 = 0;
693
+ try {
694
+ await uploadPlanOverHttp(plan, http);
695
+ } catch (e) {
696
+ log(`[argus] ${e instanceof Error ? e.message : String(e)}`);
697
+ code2 = 1;
698
+ }
699
+ results.push({ plan, command: `http upload \u2192 ${url}`, code: code2 });
700
+ if (code2 !== 0) break;
701
+ continue;
702
+ }
703
+ const renamedMaps = Object.keys(plan.generatedFor ?? {}).length;
704
+ if (renamedMaps > 0) log(`[argus] warning: ${plan.label}: ${renamedMaps} source map(s) are tied to their chunk only by its sourceMappingURL comment (Turbopack); the argus CLI names them after the map file, so their frames will not resolve \u2014 use the http transport`);
705
+ const code = await exec(bin, args, { env, cwd });
706
+ results.push({ plan, command, code });
707
+ if (code !== 0) break;
708
+ }
709
+ return results;
710
+ }
711
+
712
+ // src/next-upload.ts
713
+ async function uploadNextBuild(options) {
714
+ const env = options.env ?? process.env;
715
+ const log = options.log ?? ((line) => console.log(line));
716
+ const uploaded = [];
717
+ let error;
718
+ try {
719
+ const missing = ["ARGUS_URL", "ARGUS_UPLOAD_KEY"].filter((k) => !env[k]);
720
+ if (missing.length > 0) throw new Error(`${missing.join(" and ")} not set`);
721
+ if (!options.artifactSetId) throw new Error("no artifact set id (set ARGUS_ARTIFACT_SET_ID or the artifactSetId option)");
722
+ const plan = { projectDir: options.projectDir, distDir: options.distDir, artifactSetId: options.artifactSetId };
723
+ if (options.serverArtifactSetId) plan.serverArtifactSetId = options.serverArtifactSetId;
724
+ if (options.urlPrefix !== void 0) plan.urlPrefix = options.urlPrefix;
725
+ if (env.ARGUS_RELEASE) plan.release = env.ARGUS_RELEASE;
726
+ if (env.ARGUS_GIT_SHA) plan.gitSha = env.ARGUS_GIT_SHA;
727
+ const run = {
728
+ env,
729
+ cwd: options.projectDir,
730
+ // One line per chunk would bury the build log, and every Next build has runtime files
731
+ // without a map; the per-set summary line stays.
732
+ log: (line) => {
733
+ if (!line.startsWith("[argus] uploading ") && !line.endsWith("without a source map were skipped")) log(line);
734
+ }
735
+ };
736
+ if (options.fetch) run.fetch = options.fetch;
737
+ const results = await runUploads(planNext(plan), run);
738
+ for (const r of results) if (r.code === 0) uploaded.push(r.plan.artifactSetId);
739
+ const failed = results.find((r) => r.code !== 0);
740
+ if (failed) throw new Error(`${failed.plan.label} was not uploaded (${failed.command})`);
741
+ } catch (e) {
742
+ error = (e instanceof Error ? e.message : String(e)).replace(/^Argus: /, "");
743
+ if (options.onUploadError === "fail") {
744
+ throw new Error(`Argus: source map upload failed: ${error}`);
745
+ }
746
+ log(`[argus] warning: source map upload failed, the build continues (onUploadError: 'fail' stops it): ${error}`);
747
+ }
748
+ let deletedMaps = 0;
749
+ if (options.deleteBrowserMaps) {
750
+ const maps = browserSourceMaps(options.projectDir, options.distDir);
751
+ for (const m of maps) (0, import_node_fs4.rmSync)(m, { force: true });
752
+ deletedMaps = maps.length;
753
+ if (deletedMaps > 0) log(`[argus] removed ${deletedMaps} browser source map(s) from the build output${error ? " (they were not uploaded: errors of this build stay unsymbolicated)" : ""}`);
754
+ }
755
+ const result = { ok: !error, uploaded, deletedMaps };
756
+ if (error) result.error = error;
757
+ return result;
758
+ }
759
+
760
+ // src/next.ts
761
+ function nextArtifactSetIds(options = {}) {
762
+ const env = options.env ?? process.env;
763
+ const web = resolveArtifactSetId(options.artifactSetId, "web", env);
764
+ const nodeExplicit = env.ARGUS_ARTIFACT_SET_ID_NODE || options.serverArtifactSetId;
765
+ const node = nodeExplicit ? assertArtifactSetId(nodeExplicit, "server artifact set id") : web ? assertArtifactSetId(`${web}-node`, "server artifact set id") : void 0;
766
+ return { web, node };
767
+ }
768
+ var PHASE_PRODUCTION_BUILD = "phase-production-build";
769
+ var WARNED_ENV = "__ARGUS_NEXT_UPLOAD_WARNED";
770
+ function installedNextVersion() {
771
+ try {
772
+ const { version } = (0, import_node_module2.createRequire)((0, import_node_path5.join)(process.cwd(), "package.json"))("next/package.json");
773
+ const m = /^(\d+)\.(\d+)\./.exec(version ?? "");
774
+ return m ? [Number(m[1]), Number(m[2])] : void 0;
775
+ } catch {
776
+ return void 0;
777
+ }
778
+ }
779
+ function nextUrlPrefix(config) {
780
+ let base = typeof config.assetPrefix === "string" && config.assetPrefix ? config.assetPrefix : typeof config.basePath === "string" ? config.basePath : "";
781
+ if (/^https?:\/\//i.test(base)) {
782
+ try {
783
+ base = new URL(base).pathname;
784
+ } catch {
785
+ base = "";
786
+ }
787
+ }
788
+ const path = base.replace(/^\/+|\/+$/g, "");
789
+ return path ? `/${path}/_next/` : "/_next/";
790
+ }
791
+ function apply(config, options, phase) {
792
+ const env = options.env ?? process.env;
793
+ const mode = options.upload ?? "auto";
794
+ let uploads = mode === true || mode === "auto" && !!env.ARGUS_UPLOAD_KEY && !!env.ARGUS_URL;
795
+ if (uploads) {
796
+ const version = installedNextVersion();
797
+ if (version && (version[0] < 15 || version[0] === 15 && version[1] < 4)) {
798
+ uploads = false;
799
+ if (!env[WARNED_ENV]) {
800
+ env[WARNED_ENV] = "1";
801
+ (options.log ?? console.warn)(
802
+ `[argus] source maps are not uploaded by this build: Next ${version.join(".")} has no compiler.runAfterProductionCompile (15.4+). Run \`argus-build upload next --delete-maps\` after \`next build\`.`
803
+ );
804
+ }
805
+ }
806
+ }
807
+ let ids = nextArtifactSetIds(options);
808
+ const building = phase !== void 0 ? phase === PHASE_PRODUCTION_BUILD : env.NODE_ENV === "production";
809
+ if (uploads && building && !ids.web) {
810
+ env.ARGUS_ARTIFACT_SET_ID_WEB = newArtifactSetId("next");
811
+ ids = nextArtifactSetIds(options);
812
+ }
813
+ const out = { ...config, env: { ...config.env } };
814
+ if (ids.web) out.env[NEXT_PUBLIC_ENV] = ids.web;
815
+ if (ids.node) out.env[NEXT_SERVER_ENV] = ids.node;
816
+ const wantMaps = options.browserSourceMaps ?? uploads;
817
+ const enabledMaps = wantMaps && config.productionBrowserSourceMaps === void 0;
818
+ if (enabledMaps) out.productionBrowserSourceMaps = true;
819
+ if (options.serverSourceMaps ?? uploads) {
820
+ const previous = config.webpack;
821
+ out.webpack = (webpackConfig, context) => {
822
+ const next = typeof previous === "function" ? previous(webpackConfig, context) : webpackConfig;
823
+ if (context?.isServer && !context.dev) next.devtool = "source-map";
824
+ return next;
825
+ };
826
+ }
827
+ if (uploads) {
828
+ const compiler = config.compiler ?? {};
829
+ const previous = compiler.runAfterProductionCompile;
830
+ const hook = async (metadata) => {
831
+ if (typeof previous === "function") await previous(metadata);
832
+ const upload = {
833
+ projectDir: metadata.projectDir,
834
+ distDir: metadata.distDir,
835
+ artifactSetId: ids.web,
836
+ serverArtifactSetId: ids.node,
837
+ urlPrefix: options.urlPrefix ?? nextUrlPrefix(config),
838
+ onUploadError: options.onUploadError ?? "warn",
839
+ deleteBrowserMaps: options.deleteBrowserMapsAfterUpload ?? enabledMaps,
840
+ env
841
+ };
842
+ if (options.log) upload.log = options.log;
843
+ await uploadNextBuild(upload);
844
+ };
845
+ out.compiler = { ...compiler, runAfterProductionCompile: hook };
846
+ }
847
+ return out;
848
+ }
849
+ function withArgus2(nextConfig, options = {}) {
850
+ if (typeof nextConfig === "function") {
851
+ return (phase, context) => {
852
+ const result = nextConfig(phase, context);
853
+ return result instanceof Promise ? result.then((c) => apply(c, options, phase)) : apply(result, options, phase);
854
+ };
855
+ }
856
+ return apply(nextConfig, options, void 0);
857
+ }
858
+
859
+ // src/cli.ts
860
+ var import_node_fs6 = require("fs");
861
+ var import_node_path7 = require("path");
862
+ var import_node_readline = require("readline");
863
+
864
+ // src/init.ts
865
+ var import_node_child_process2 = require("child_process");
866
+ var import_node_fs5 = require("fs");
867
+ var import_node_path6 = require("path");
868
+ var isDir = (p) => (0, import_node_fs5.existsSync)(p) && (0, import_node_fs5.statSync)(p).isDirectory();
869
+ var LOCKFILES = [
870
+ ["pnpm-lock.yaml", "pnpm"],
871
+ ["yarn.lock", "yarn"],
872
+ ["bun.lockb", "bun"],
873
+ ["bun.lock", "bun"],
874
+ ["package-lock.json", "npm"]
875
+ ];
876
+ function detectPackageManager(dir, pkg = {}) {
877
+ const declared = typeof pkg.packageManager === "string" ? /^(pnpm|yarn|npm|bun)@/.exec(pkg.packageManager)?.[1] : void 0;
878
+ if (declared) return declared;
879
+ for (let d = (0, import_node_path6.resolve)(dir); ; d = (0, import_node_path6.dirname)(d)) {
880
+ for (const [file, pm] of LOCKFILES) if ((0, import_node_fs5.existsSync)((0, import_node_path6.join)(d, file))) return pm;
881
+ if ((0, import_node_path6.dirname)(d) === d) return "npm";
882
+ }
883
+ }
884
+ function versionOf(dir, declared) {
885
+ let version;
886
+ try {
887
+ version = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path6.join)(dir, "node_modules/next/package.json"), "utf8")).version;
888
+ } catch {
889
+ version = declared;
890
+ }
891
+ const m = /(\d+)(?:\.(\d+))?/.exec(version ?? "");
892
+ return m ? [Number(m[1]), Number(m[2] ?? 0)] : void 0;
893
+ }
894
+ function detectProject(dir) {
895
+ const root = (0, import_node_path6.resolve)(dir);
896
+ const pkgPath = (0, import_node_path6.join)(root, "package.json");
897
+ if (!(0, import_node_fs5.existsSync)(pkgPath)) throw new Error(`no package.json in ${root} \u2014 run this in the directory of the app (in a monorepo: the app's own package)`);
898
+ let pkg;
899
+ try {
900
+ pkg = JSON.parse((0, import_node_fs5.readFileSync)(pkgPath, "utf8"));
901
+ } catch (e) {
902
+ throw new Error(`cannot read ${pkgPath}: ${e instanceof Error ? e.message : String(e)}`);
903
+ }
904
+ const deps = /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
905
+ const info = {
906
+ kind: deps.has("next") ? "next" : deps.has("expo") ? "expo" : deps.has("react-native") ? "react-native" : "node",
907
+ dir: root,
908
+ name: typeof pkg.name === "string" ? pkg.name : void 0,
909
+ typescript: (0, import_node_fs5.existsSync)((0, import_node_path6.join)(root, "tsconfig.json")) || deps.has("typescript"),
910
+ esm: pkg.type === "module",
911
+ packageManager: detectPackageManager(root, pkg),
912
+ deps,
913
+ sourceDir: ""
914
+ };
915
+ if (info.kind === "next") {
916
+ const rootRouter = isDir((0, import_node_path6.join)(root, "app")) || isDir((0, import_node_path6.join)(root, "pages"));
917
+ const src = !rootRouter && (isDir((0, import_node_path6.join)(root, "src/app")) || isDir((0, import_node_path6.join)(root, "src/pages")));
918
+ info.sourceDir = src ? "src" : "";
919
+ info.router = isDir((0, import_node_path6.join)(root, info.sourceDir, "app")) ? "app" : isDir((0, import_node_path6.join)(root, info.sourceDir, "pages")) ? "pages" : "app";
920
+ info.nextConfig = ["next.config.ts", "next.config.mjs", "next.config.js", "next.config.cjs"].find((f) => (0, import_node_fs5.existsSync)((0, import_node_path6.join)(root, f)));
921
+ info.nextVersion = versionOf(root, pkg.dependencies?.next ?? pkg.devDependencies?.next);
922
+ } else if (info.kind === "node") {
923
+ info.sourceDir = isDir((0, import_node_path6.join)(root, "src")) ? "src" : "";
924
+ info.main = typeof pkg.main === "string" ? pkg.main : void 0;
925
+ }
926
+ return info;
927
+ }
928
+ function nextInstrumentation(typescript) {
929
+ const args = typescript ? "...args: unknown[]" : "...args";
930
+ const call2 = typescript ? "(onRequestError as (...a: unknown[]) => Promise<void>)(...args)" : "onRequestError(...args)";
931
+ return `// Argus server-side capture. Next calls register() once per server process.
932
+ export async function register() {
933
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
934
+ const { register } = await import('@getargus/node/next');
935
+ // DSN, environment and release are read from ARGUS_DSN / ARGUS_ENVIRONMENT / ARGUS_RELEASE.
936
+ register({ artifactSetId: process.env.ARGUS_SERVER_ARTIFACT_SET_ID });
937
+ }
938
+ }
939
+
940
+ // Errors of server components, route handlers and server actions.
941
+ export const onRequestError = async (${args}) => {
942
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
943
+ const { onRequestError } = await import('@getargus/node/next');
944
+ return ${call2};
945
+ }
946
+ };
947
+ `;
948
+ }
949
+ function nextInstrumentationClient() {
950
+ return `// Argus browser capture; Next loads this file before the app hydrates.
951
+ import { initClient } from '@getargus/browser/next';
952
+
953
+ // The DSN is read from NEXT_PUBLIC_ARGUS_DSN (inlined at build time).
954
+ initClient({ artifactSetId: process.env.NEXT_PUBLIC_ARGUS_ARTIFACT_SET_ID });
955
+
956
+ export { onRouterTransitionStart } from '@getargus/browser/next';
957
+ `;
958
+ }
959
+ function nodeSetup(style) {
960
+ const load = style === "esm" ? "import { init } from '@getargus/node';" : "const { init } = require('@getargus/node');";
961
+ return `// Argus: load this file first in the entry file, so capture is installed before anything else runs.
962
+ ${load}
963
+
964
+ init(); // reads ARGUS_DSN, ARGUS_ENVIRONMENT (else NODE_ENV) and ARGUS_RELEASE
965
+ `;
966
+ }
967
+ var WITH_ARGUS_IMPORT = "import { withArgus } from '@getargus/build-tools/next';";
968
+ var WITH_ARGUS_REQUIRE = "const { withArgus } = require('@getargus/build-tools/next');";
969
+ function wrapNextConfig(source) {
970
+ const esm = [...source.matchAll(/^export default ([A-Za-z_$][\w$]*);?[ \t]*$/gm)];
971
+ const cjs = [...source.matchAll(/^module\.exports = ([A-Za-z_$][\w$]*);?[ \t]*$/gm)];
972
+ if (esm.length + cjs.length !== 1) return void 0;
973
+ const isEsm = esm.length === 1;
974
+ if (isEsm ? /\bmodule\.exports\b/.test(source) : /^export /m.test(source)) return void 0;
975
+ const m = (isEsm ? esm : cjs)[0];
976
+ const wrapped = isEsm ? `export default withArgus(${m[1]});` : `module.exports = withArgus(${m[1]});`;
977
+ const header = isEsm ? WITH_ARGUS_IMPORT : WITH_ARGUS_REQUIRE;
978
+ const body = source.slice(0, m.index) + wrapped + source.slice(m.index + m[0].length);
979
+ return { content: `${header}
980
+ ${body}`, removed: m[0], added: [header, wrapped] };
981
+ }
982
+ var DSN_FORM = "https://<ingest key>@<host>";
983
+ var RE_DSN = /^https?:\/\/[^@\s/]+@[^@\s/]+/;
984
+ function read(dir, file) {
985
+ try {
986
+ return (0, import_node_fs5.readFileSync)((0, import_node_path6.join)(dir, file), "utf8");
987
+ } catch {
988
+ return void 0;
989
+ }
990
+ }
991
+ function installCommand(pm, packages) {
992
+ return `${pm === "npm" ? "npm install" : `${pm} add`} ${packages.join(" ")}`;
993
+ }
994
+ function envBlock(existing, entries) {
995
+ const has = (key) => new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=`, "m").test(existing ?? "");
996
+ const lines = [];
997
+ const keys = [];
998
+ for (const [key, value, comment] of entries) {
999
+ if (has(key)) continue;
1000
+ if (comment) lines.push(`# ${comment}`);
1001
+ lines.push(`${key}=${value}`);
1002
+ keys.push(key);
1003
+ }
1004
+ if (keys.length === 0) return { text: "", keys };
1005
+ const lead = existing === void 0 || existing === "" ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
1006
+ return { text: `${lead}${lines.join("\n")}
1007
+ `, keys };
1008
+ }
1009
+ function envAction(dir, file, entries, why) {
1010
+ const existing = read(dir, file);
1011
+ const block = envBlock(existing, entries);
1012
+ if (block.keys.length === 0) return { type: "ok", file, summary: "already lists the Argus variables" };
1013
+ return {
1014
+ type: existing === void 0 ? "create" : "append",
1015
+ file,
1016
+ summary: `${block.keys.join(", ")} \u2014 ${why}`,
1017
+ content: block.text,
1018
+ preview: block.text.trim()
1019
+ };
1020
+ }
1021
+ function isGitIgnored(dir, file) {
1022
+ const git = (0, import_node_child_process2.spawnSync)("git", ["check-ignore", "-q", file], { cwd: dir, stdio: "ignore" });
1023
+ if (git.status === 0) return true;
1024
+ if (git.status === 1) return false;
1025
+ let ignored = false;
1026
+ for (const raw of (read(dir, ".gitignore") ?? "").split("\n")) {
1027
+ const line = raw.trim();
1028
+ if (!line || line.startsWith("#")) continue;
1029
+ const negated = line.startsWith("!");
1030
+ const pattern = line.replace(/^!/, "").replace(/^\//, "");
1031
+ if (pattern.includes("/")) continue;
1032
+ const escaped = pattern.replace(/[.+^$(){}|[\]\\]/g, "\\$&");
1033
+ const re = new RegExp(`^${escaped.replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
1034
+ if (re.test(file)) ignored = !negated;
1035
+ }
1036
+ return ignored;
1037
+ }
1038
+ function dsnAction(dir, file, entries) {
1039
+ const existing = read(dir, file);
1040
+ const block = envBlock(existing, entries);
1041
+ if (block.keys.length === 0) return { type: "ok", file, summary: `already sets ${entries.map(([k]) => k).join(", ")}` };
1042
+ if (!isGitIgnored(dir, file)) {
1043
+ return {
1044
+ type: "manual",
1045
+ file,
1046
+ summary: `not git-ignored, so the DSN was not written \u2014 add "${file}" to .gitignore, then put this in it`,
1047
+ preview: block.text.trim()
1048
+ };
1049
+ }
1050
+ return { type: existing === void 0 ? "create" : "append", file, summary: `${block.keys.join(", ")} (git-ignored)`, content: block.text, preview: block.text.trim() };
1051
+ }
1052
+ var USES_ARGUS = /@getargus\//;
1053
+ function sourceFile(dir, candidates, create, content, summary) {
1054
+ const existing = candidates.find((f) => (0, import_node_fs5.existsSync)((0, import_node_path6.join)(dir, f)));
1055
+ if (!existing) return { type: "create", file: create, summary, content, preview: content.trimEnd() };
1056
+ if (USES_ARGUS.test(read(dir, existing) ?? "")) return { type: "ok", file: existing, summary: "already uses Argus" };
1057
+ return { type: "manual", file: existing, summary: "exists and is not overwritten \u2014 merge this into it", preview: content.trimEnd() };
1058
+ }
1059
+ function planNextProject(p, dsn) {
1060
+ const ext = p.typescript ? "ts" : "js";
1061
+ const at = (name) => p.sourceDir ? `${p.sourceDir}/${name}` : name;
1062
+ const variants = (base) => ["ts", "tsx", "js", "jsx", "mjs"].flatMap((e) => [`${base}.${e}`, `src/${base}.${e}`]);
1063
+ const actions = [
1064
+ sourceFile(p.dir, variants("instrumentation"), at(`instrumentation.${ext}`), nextInstrumentation(p.typescript), "server capture: register() + onRequestError"),
1065
+ sourceFile(p.dir, variants("instrumentation-client"), at(`instrumentation-client.${ext}`), nextInstrumentationClient(), "browser capture: initClient()")
1066
+ ];
1067
+ if (!p.nextConfig) {
1068
+ const content = `${WITH_ARGUS_IMPORT}
1069
+
1070
+ export default withArgus({});
1071
+ `;
1072
+ actions.push({ type: "create", file: "next.config.mjs", summary: "build id + source map upload: withArgus()", content, preview: content.trimEnd() });
1073
+ } else {
1074
+ const source = read(p.dir, p.nextConfig) ?? "";
1075
+ const wrapped = /\bwithArgus\b/.test(source) ? void 0 : wrapNextConfig(source);
1076
+ if (/\bwithArgus\b/.test(source)) actions.push({ type: "ok", file: p.nextConfig, summary: "already wrapped with withArgus" });
1077
+ else if (wrapped) {
1078
+ actions.push({
1079
+ type: "modify",
1080
+ file: p.nextConfig,
1081
+ summary: "wrap the exported config with withArgus()",
1082
+ content: wrapped.content,
1083
+ preview: [`- ${wrapped.removed}`, ...wrapped.added.map((l) => `+ ${l}`)].join("\n")
1084
+ });
1085
+ } else {
1086
+ const cjs = /\bmodule\.exports\b/.test(source);
1087
+ actions.push({
1088
+ type: "manual",
1089
+ file: p.nextConfig,
1090
+ summary: "its export is not a plain identifier, so it was left alone \u2014 wrap it yourself",
1091
+ preview: cjs ? `+ ${WITH_ARGUS_REQUIRE}
1092
+ - module.exports = <your config>;
1093
+ + module.exports = withArgus(<your config>);` : `+ ${WITH_ARGUS_IMPORT}
1094
+ - export default <your config>;
1095
+ + export default withArgus(<your config>);`
1096
+ });
1097
+ }
1098
+ }
1099
+ actions.push(
1100
+ envAction(
1101
+ p.dir,
1102
+ ".env.example",
1103
+ [
1104
+ ["ARGUS_DSN", "", `Argus (${DSN_FORM}). The browser needs the same value under the NEXT_PUBLIC_ name.`],
1105
+ ["NEXT_PUBLIC_ARGUS_DSN", ""],
1106
+ ["ARGUS_URL", "", "Build / CI only: with both set, `next build` uploads source maps. An upload key, never the ingest key."],
1107
+ ["ARGUS_UPLOAD_KEY", ""]
1108
+ ],
1109
+ "names only, no values"
1110
+ )
1111
+ );
1112
+ if (dsn) actions.push(dsnAction(p.dir, ".env.local", [["ARGUS_DSN", dsn], ["NEXT_PUBLIC_ARGUS_DSN", dsn]]));
1113
+ const missing = ["@getargus/node", "@getargus/browser", "@getargus/build-tools"].filter((d) => !p.deps.has(d));
1114
+ const nextSteps = [];
1115
+ if (!dsn) nextSteps.push(`Set ARGUS_DSN and NEXT_PUBLIC_ARGUS_DSN (${DSN_FORM}) in .env.local and in your deployment.`);
1116
+ else nextSteps.push("Set ARGUS_DSN and NEXT_PUBLIC_ARGUS_DSN in your deployment too (.env.local is for this machine).");
1117
+ nextSteps.push("Set ARGUS_URL and ARGUS_UPLOAD_KEY where `next build` runs (CI): source maps are then uploaded at the end of the build.");
1118
+ const v = p.nextVersion;
1119
+ if (v && (v[0] < 15 || v[0] === 15 && v[1] < 3)) nextSteps.push(`Next ${v.join(".")} does not load instrumentation-client (15.3+): import that file from your earliest client entry instead.`);
1120
+ if (v && (v[0] < 15 || v[0] === 15 && v[1] < 4)) nextSteps.push(`Next ${v.join(".")} has no post-compile hook (15.4+): run \`argus-build upload next --delete-maps\` after \`next build\`.`);
1121
+ if (p.router === "app") nextSteps.push("Optional: report render errors from app/error.tsx and app/global-error.tsx with captureNextError (see '@getargus/browser/next').");
1122
+ return { project: p, actions, install: missing.length > 0 ? installCommand(p.packageManager, missing) : void 0, nextSteps };
1123
+ }
1124
+ function planNodeProject(p, dsn) {
1125
+ const mainExt = (0, import_node_path6.extname)(p.main ?? "");
1126
+ const ext = p.typescript ? ".ts" : mainExt === ".mjs" || mainExt === ".cjs" ? mainExt : ".js";
1127
+ const style = ext === ".mjs" ? "esm" : ext === ".cjs" ? "cjs" : p.typescript || p.esm ? "esm" : "cjs";
1128
+ const at = (name) => p.sourceDir ? `${p.sourceDir}/${name}` : name;
1129
+ const variants = [".ts", ".js", ".mjs", ".cjs"].flatMap((e) => [`argus${e}`, `src/argus${e}`]);
1130
+ const setup = sourceFile(p.dir, variants, at(`argus${ext}`), nodeSetup(style), "init() from the environment");
1131
+ const actions = [
1132
+ setup,
1133
+ envAction(p.dir, ".env.example", [["ARGUS_DSN", "", `Argus (${DSN_FORM})`], ["ARGUS_RELEASE", "", "Optional: what the dashboard shows as the version, e.g. api@1.2.0"]], "names only, no values")
1134
+ ];
1135
+ if (dsn) actions.push(dsnAction(p.dir, ".env", [["ARGUS_DSN", dsn]]));
1136
+ const nodeNext = /"module(?:Resolution)?"\s*:\s*"node(?:16|next)"/i.test(read(p.dir, "tsconfig.json") ?? "");
1137
+ const base = setup.file.replace(/^src\//, "");
1138
+ const specifier = p.typescript ? nodeNext ? "./argus.js" : "./argus" : `./${base}`;
1139
+ const line = style === "esm" ? `import '${specifier}';` : `require('${specifier}');`;
1140
+ const entry = p.main ?? (p.sourceDir ? `${p.sourceDir}/index${ext}` : `index${ext}`);
1141
+ const nextSteps = [
1142
+ `Make this the first line of your entry file (${entry}; adjust the path if the entry is in another directory):
1143
+
1144
+ ${line}
1145
+ `,
1146
+ dsn ? "Node does not read .env by itself: start with `node --env-file=.env \u2026` or export ARGUS_DSN in the process environment." : `Set ARGUS_DSN (${DSN_FORM}) in the process environment. Without it the SDK is disabled and every call is a no-op.`
1147
+ ];
1148
+ const missing = ["@getargus/node"].filter((d) => !p.deps.has(d));
1149
+ return { project: p, actions, install: missing.length > 0 ? installCommand(p.packageManager, missing) : void 0, nextSteps };
1150
+ }
1151
+ function planMobileProject(p) {
1152
+ const expo = p.kind === "expo";
1153
+ const wanted = expo ? ["@getargus/react-native", "@getargus/expo", "@getargus/build-tools", "expo-sqlite"] : ["@getargus/react-native", "@getargus/build-tools"];
1154
+ const missing = wanted.filter((d) => !p.deps.has(d));
1155
+ const metro = expo ? "const { getDefaultConfig } = require('expo/metro-config');\n const { withArgus } = require('@getargus/build-tools/metro');\n module.exports = withArgus(getDefaultConfig(__dirname));" : "const { withArgus } = require('@getargus/build-tools/metro');\n module.exports = withArgus(<your metro config>);";
1156
+ const nextSteps = [
1157
+ ...expo ? [`app.json \u2014 add the config plugin (native crash capture; needs a development or release build, not Expo Go):
1158
+
1159
+ "plugins": [["@getargus/expo", { "release": "${p.name ?? "app"}@1.0.0" }]]
1160
+ `] : ["Native crash capture needs @getargus/expo, an Expo module: add it once the project has expo-modules-core; JS errors work without it."],
1161
+ `metro.config.js \u2014 compile the build's artifact set id into the bundle:
1162
+
1163
+ ${metro}
1164
+ `,
1165
+ `Entry file (before anything else of the app is imported):
1166
+
1167
+ import { init } from '@getargus/react-native';
1168
+ init({ dsn: '${DSN_FORM}', release: '${p.name ?? "app"}@1.0.0' });
1169
+ `,
1170
+ "Add `.argus/` to .gitignore (generated by the Metro helper).",
1171
+ 'After `expo export` / an EAS build, upload the source maps (ARGUS_URL and ARGUS_UPLOAD_KEY in the environment):\n\n npx argus-build upload expo-export --ios-set-id "$ARGUS_ARTIFACT_SET_ID_IOS" --android-set-id "$ARGUS_ARTIFACT_SET_ID_ANDROID"\n'
1172
+ ];
1173
+ return { project: p, actions: [], install: missing.length > 0 ? installCommand(p.packageManager, missing) : void 0, nextSteps };
1174
+ }
1175
+ function planInit(options = {}) {
1176
+ if (options.dsn !== void 0 && !RE_DSN.test(options.dsn)) throw new Error(`--dsn must look like ${DSN_FORM}`);
1177
+ const project = detectProject(options.dir ?? process.cwd());
1178
+ if (project.kind === "next") return planNextProject(project, options.dsn);
1179
+ if (project.kind === "node") return planNodeProject(project, options.dsn);
1180
+ return planMobileProject(project);
1181
+ }
1182
+ function applyInit(plan) {
1183
+ const written = [];
1184
+ for (const a of plan.actions) {
1185
+ if (a.content === void 0 || a.type === "ok" || a.type === "manual") continue;
1186
+ const path = (0, import_node_path6.join)(plan.project.dir, a.file);
1187
+ if (a.type === "create") {
1188
+ (0, import_node_fs5.mkdirSync)((0, import_node_path6.dirname)(path), { recursive: true });
1189
+ (0, import_node_fs5.writeFileSync)(path, a.content, { flag: "wx" });
1190
+ } else if (a.type === "append") (0, import_node_fs5.writeFileSync)(path, a.content, { flag: "a" });
1191
+ else (0, import_node_fs5.writeFileSync)(path, a.content);
1192
+ written.push(a.file);
1193
+ }
1194
+ return written;
1195
+ }
1196
+ var KIND_LABEL = { next: "Next.js", expo: "Expo", "react-native": "React Native", node: "Node.js" };
1197
+ function describeProject(p) {
1198
+ const parts = [];
1199
+ if (p.kind === "next") parts.push(`${p.router} router`, p.sourceDir ? "src/" : "no src/");
1200
+ if (p.kind === "node") parts.push(p.typescript || p.esm ? "ESM" : "CommonJS");
1201
+ if (p.kind === "next" || p.kind === "node") parts.push(p.typescript ? "TypeScript" : "JavaScript");
1202
+ parts.push(p.packageManager);
1203
+ return `${KIND_LABEL[p.kind]} (${parts.join(", ")})`;
1204
+ }
1205
+ var indent = (text) => text.split("\n").map((l) => l ? ` ${l}` : "").join("\n");
1206
+ var isWrite = (a) => a.type === "create" || a.type === "append" || a.type === "modify";
1207
+ function renderActions(plan, mode) {
1208
+ const out = [`Argus init \u2014 ${describeProject(plan.project)} in ${plan.project.dir}`, ""];
1209
+ const done = mode === "applied";
1210
+ const verb = {
1211
+ create: done ? "created " : "create ",
1212
+ append: done ? "appended" : "append ",
1213
+ modify: done ? "modified" : "modify ",
1214
+ ok: "ok ",
1215
+ manual: "manual "
1216
+ };
1217
+ for (const a of plan.actions) {
1218
+ out.push(` ${verb[a.type]} ${a.file} \u2014 ${a.summary}`);
1219
+ if (a.preview) out.push("", indent(a.preview), "");
1220
+ }
1221
+ return out.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
1222
+ }
1223
+ function renderOutcome(plan, mode) {
1224
+ const out = [];
1225
+ const writes = plan.actions.filter(isWrite).length;
1226
+ if (plan.actions.length > 0) {
1227
+ if (writes === 0) out.push("Nothing to write.");
1228
+ else if (mode === "applied") out.push(`${writes} file(s) written.`);
1229
+ else if (mode === "dry-run") out.push(`Dry run: ${writes} file(s) would be written, nothing was changed.`);
1230
+ else out.push(`Nothing was changed: ${writes} file(s) would be written. Run again with --yes to write them.`);
1231
+ out.push("");
1232
+ }
1233
+ if (plan.install) out.push("Install the packages:", "", ` ${plan.install}`, "");
1234
+ if (plan.nextSteps.length > 0) {
1235
+ out.push(plan.actions.length > 0 ? "Then:" : "Steps (nothing is edited automatically for a native app):", "");
1236
+ plan.nextSteps.forEach((s2, n) => out.push(` ${n + 1}. ${s2}`));
1237
+ }
1238
+ return out.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
1239
+ }
1240
+ function renderInit(plan, mode) {
1241
+ return `${renderActions(plan, mode)}
1242
+
1243
+ ${renderOutcome(plan, mode)}`;
1244
+ }
1245
+ async function runInit(options = {}) {
1246
+ const out = options.out ?? ((t) => void process.stdout.write(`${t}
1247
+ `));
1248
+ const plan = planInit(options);
1249
+ let apply2 = !!options.yes && !options.dryRun;
1250
+ if (!apply2 && !options.dryRun && options.confirm && plan.actions.some(isWrite)) {
1251
+ out(renderActions(plan, "preview"));
1252
+ apply2 = await options.confirm("\nWrite these files? [y/N] ");
1253
+ const written2 = apply2 ? applyInit(plan) : [];
1254
+ out(`
1255
+ ${renderOutcome(plan, apply2 ? "applied" : "preview")}`);
1256
+ return { plan, written: written2 };
1257
+ }
1258
+ const written = apply2 ? applyInit(plan) : [];
1259
+ out(renderInit(plan, apply2 ? "applied" : options.dryRun ? "dry-run" : "preview"));
1260
+ return { plan, written };
1261
+ }
1262
+
1263
+ // src/cli.ts
1264
+ var USAGE = `argus-build <command>
1265
+
1266
+ init [--dsn <dsn>] [--yes] [--dry-run] [--dir <project>]
1267
+ set a Next.js / Node / Expo project up for Argus: prints every file it
1268
+ would write; writes with --yes (or after asking, on a terminal); never
1269
+ overwrites a file, never installs anything
1270
+ new-id [--prefix <p>] print a new artifact set id (same format as \`argus artifacts new-id\`)
1271
+ prelude --set-id <id> --out <file> write the generated module that defines globalThis.__ARGUS_ARTIFACT_SET_ID__
1272
+ collect <target> [options] print what would be uploaded, per artifact set (no network)
1273
+ upload <target> [options] upload each artifact set (JS sets over HTTP, native symbols via the argus CLI)
1274
+
1275
+ Targets
1276
+ next --set-id <web id> [--server-set-id <id>] [--dir <project>] [--dist-dir .next] [--url-prefix /_next/]
1277
+ browser chunks and server chunks go to two different sets (default server id: <web id>-node)
1278
+ expo-export --ios-set-id <id> --android-set-id <id> [--dir dist] [--platform ios|android]
1279
+ [--update-id <id> | --ios-update-id <id> --android-update-id <id>]
1280
+ [--parent-set-id <id> | --ios-parent-set-id <id> --android-parent-set-id <id>] [--runtime-version <v>]
1281
+ ios --set-id <id> --dir <xcarchive | dSYMs | build dir>... [--bundle <main.jsbundle>] [--sourcemap <map>]
1282
+ android --set-id <id> --dir <android/app/build>... [--bundle <f>] [--sourcemap <map>] [--native-libs]
1283
+ files --set-id <id> --platform <ios|android|web|node> --dir <dir or file>... [--strip-prefix <p>] [--url-prefix <p>]
1284
+
1285
+ Common options
1286
+ --release <r> --dist <d> --native-release <v> --native-build <b> --runtime-version <v> --git-sha <sha>
1287
+ --no-bundles upload source maps only (next, files)
1288
+ --json collect: machine readable output
1289
+ --dry-run upload: print the manifests and upload nothing
1290
+ --print upload: only print the commands
1291
+ --transport <t> upload: http (from Node, JS artifacts only), cli (the argus binary) or auto (default:
1292
+ http for JS-only sets, cli for native symbols)
1293
+ --delete-maps upload next: delete browser .map files from the build output after a successful upload
1294
+ --argus-bin <path> the argus binary (default $ARGUS_BIN, else "argus")
1295
+ --url <base url> server (default: $ARGUS_URL as read by the argus CLI)
1296
+
1297
+ Environment: ARGUS_UPLOAD_KEY (upload key; never passed on the command line), ARGUS_URL, ARGUS_BIN, ARGUS_GIT_SHA.
1298
+ `;
1299
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["yes", "json", "dry-run", "print", "no-bundles", "delete-maps", "native-libs", "help"]);
1300
+ var MULTI_FLAGS = /* @__PURE__ */ new Set(["dir"]);
1301
+ function parseArgs(argv) {
1302
+ const out = { positionals: [], flags: {}, multi: {} };
1303
+ for (let i = 0; i < argv.length; i++) {
1304
+ const a = argv[i];
1305
+ if (!a.startsWith("--")) {
1306
+ out.positionals.push(a);
1307
+ continue;
1308
+ }
1309
+ const eq = a.indexOf("=");
1310
+ const name = a.slice(2, eq === -1 ? void 0 : eq);
1311
+ if (BOOLEAN_FLAGS.has(name)) {
1312
+ out.flags[name] = true;
1313
+ continue;
1314
+ }
1315
+ const value = eq === -1 ? argv[++i] : a.slice(eq + 1);
1316
+ if (value === void 0) throw new Error(`missing value for --${name}`);
1317
+ if (MULTI_FLAGS.has(name)) (out.multi[name] ??= []).push(value);
1318
+ else out.flags[name] = value;
1319
+ }
1320
+ return out;
1321
+ }
1322
+ var s = (v) => typeof v === "string" ? v : void 0;
1323
+ function need(args, name) {
1324
+ const v = s(args.flags[name]);
1325
+ if (!v) throw new Error(`--${name} is required`);
1326
+ return v;
1327
+ }
1328
+ function metaOf(args, env) {
1329
+ return {
1330
+ release: s(args.flags.release),
1331
+ dist: s(args.flags.dist),
1332
+ nativeRelease: s(args.flags["native-release"]),
1333
+ nativeBuild: s(args.flags["native-build"]),
1334
+ runtimeVersion: s(args.flags["runtime-version"]),
1335
+ gitSha: s(args.flags["git-sha"]) ?? env.ARGUS_GIT_SHA
1336
+ };
1337
+ }
1338
+ function plansFor(target, args, env = process.env, cwd = process.cwd()) {
1339
+ const meta2 = metaOf(args, env);
1340
+ const dirs = (args.multi.dir ?? []).map((d) => (0, import_node_path7.resolve)(cwd, d));
1341
+ const setId = () => s(args.flags["set-id"]) ?? env.ARGUS_ARTIFACT_SET_ID ?? need(args, "set-id");
1342
+ switch (target) {
1343
+ case "next": {
1344
+ const opts = { ...meta2, projectDir: dirs[0] ?? cwd, artifactSetId: setId(), includeBundles: !args.flags["no-bundles"] };
1345
+ const serverId = s(args.flags["server-set-id"]) ?? env.ARGUS_ARTIFACT_SET_ID_NODE;
1346
+ if (serverId) opts.serverArtifactSetId = serverId;
1347
+ const distDir = s(args.flags["dist-dir"]);
1348
+ if (distDir) opts.distDir = distDir;
1349
+ const urlPrefix = s(args.flags["url-prefix"]);
1350
+ if (urlPrefix !== void 0) opts.urlPrefix = urlPrefix;
1351
+ return planNext(opts);
1352
+ }
1353
+ case "expo-export": {
1354
+ const per = (flag, envName) => {
1355
+ const out = {};
1356
+ for (const p of ["ios", "android"]) {
1357
+ const v = s(args.flags[`${p}-${flag}`]) ?? (envName ? env[`${envName}_${p.toUpperCase()}`] : void 0);
1358
+ if (v) out[p] = v;
1359
+ }
1360
+ return out;
1361
+ };
1362
+ const opts = { ...meta2, exportDir: dirs[0] ?? (0, import_node_path7.resolve)(cwd, "dist"), artifactSetIds: per("set-id", "ARGUS_ARTIFACT_SET_ID") };
1363
+ const platform = s(args.flags.platform);
1364
+ if (platform) {
1365
+ if (platform !== "ios" && platform !== "android") throw new Error("--platform must be ios or android for expo-export");
1366
+ opts.platforms = [platform];
1367
+ const generic = s(args.flags["set-id"]) ?? env.ARGUS_ARTIFACT_SET_ID;
1368
+ if (!opts.artifactSetIds[platform] && generic) opts.artifactSetIds[platform] = generic;
1369
+ }
1370
+ const updateIds = per("update-id");
1371
+ const oneUpdate = s(args.flags["update-id"]);
1372
+ if (Object.keys(updateIds).length > 0) opts.updateId = updateIds;
1373
+ else if (oneUpdate) opts.updateId = oneUpdate;
1374
+ const parents = per("parent-set-id");
1375
+ const oneParent = s(args.flags["parent-set-id"]);
1376
+ if (oneParent && platform) parents[platform] ??= oneParent;
1377
+ else if (oneParent) throw new Error("--parent-set-id needs --platform; a native build set belongs to one platform (use --ios-parent-set-id / --android-parent-set-id)");
1378
+ if (Object.keys(parents).length > 0) opts.parentSetIds = parents;
1379
+ return planExpoExport(opts);
1380
+ }
1381
+ case "ios":
1382
+ case "android": {
1383
+ if (dirs.length === 0) throw new Error("--dir is required (where the build left its dSYMs / mapping.txt / bundle map)");
1384
+ const opts = { ...meta2, platform: target, artifactSetId: setId(), searchPaths: dirs };
1385
+ const bundle = s(args.flags.bundle);
1386
+ const sourcemap = s(args.flags.sourcemap);
1387
+ if (bundle) opts.bundle = (0, import_node_path7.resolve)(cwd, bundle);
1388
+ if (sourcemap) opts.sourcemap = (0, import_node_path7.resolve)(cwd, sourcemap);
1389
+ if (args.flags["native-libs"]) opts.includeNativeLibs = true;
1390
+ return [planNative(opts)];
1391
+ }
1392
+ case "files": {
1393
+ const platform = need(args, "platform");
1394
+ if (!["ios", "android", "web", "node"].includes(platform)) throw new Error("--platform must be ios, android, web or node");
1395
+ if (dirs.length === 0) throw new Error("--dir is required");
1396
+ const opts = { ...meta2, artifactSetId: setId(), platform, paths: dirs, includeBundles: !args.flags["no-bundles"] };
1397
+ const strip = s(args.flags["strip-prefix"]);
1398
+ if (strip) opts.stripPrefix = (0, import_node_path7.resolve)(cwd, strip);
1399
+ const urlPrefix = s(args.flags["url-prefix"]);
1400
+ if (urlPrefix !== void 0) opts.urlPrefix = urlPrefix;
1401
+ return [planFiles(opts)];
1402
+ }
1403
+ default:
1404
+ throw new Error(`unknown target "${target}" (next, expo-export, ios, android, files)`);
1405
+ }
1406
+ }
1407
+ function renderPlans(plans, bin, cwd) {
1408
+ const lines = [];
1409
+ const rel = (p) => p.startsWith(cwd + import_node_path7.sep) ? (0, import_node_path7.relative)(cwd, p) : p;
1410
+ for (const plan of plans) {
1411
+ lines.push(`${plan.label} set=${plan.artifactSetId} platform=${plan.platform}${plan.ota ? " ota" : ""}${plan.updateId ? ` update=${plan.updateId}` : ""}${plan.parentSetId ? ` parent=${plan.parentSetId}` : ""}`);
1412
+ for (const f of plan.files) {
1413
+ const name = f.kind === "sourcemap" || f.kind === "js_bundle" ? ` \u2192 ${manifestName(plan, f.path)}` : "";
1414
+ lines.push(` ${f.kind.padEnd(9)} ${String(f.size).padStart(9)} ${rel(f.path)}${name}`);
1415
+ }
1416
+ for (const w of plan.warnings) lines.push(` warning: ${w}`);
1417
+ lines.push(` $ ${formatCommand(bin, uploadArgs(plan), cwd)}`);
1418
+ lines.push("");
1419
+ }
1420
+ return lines.join("\n");
1421
+ }
1422
+ function ttyConfirm(question) {
1423
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
1424
+ return new Promise((done) => {
1425
+ rl.question(question, (answer) => {
1426
+ rl.close();
1427
+ done(/^y(es)?$/i.test(answer.trim()));
1428
+ });
1429
+ });
1430
+ }
1431
+ async function main(argv, io = {}) {
1432
+ const out = io.out ?? ((t) => void process.stdout.write(`${t}
1433
+ `));
1434
+ const err = io.err ?? ((t) => void process.stderr.write(`${t}
1435
+ `));
1436
+ const env = io.env ?? process.env;
1437
+ const cwd = io.cwd ?? process.cwd();
1438
+ try {
1439
+ const args = parseArgs(argv);
1440
+ const [command, target] = args.positionals;
1441
+ if (!command || args.flags.help || command === "help") {
1442
+ out(USAGE);
1443
+ return command || args.flags.help ? 0 : 2;
1444
+ }
1445
+ const bin = s(args.flags["argus-bin"]) ?? env.ARGUS_BIN ?? "argus";
1446
+ switch (command) {
1447
+ case "init": {
1448
+ const opts = { dir: (0, import_node_path7.resolve)(cwd, (args.multi.dir ?? [])[0] ?? "."), out };
1449
+ const dsn = s(args.flags.dsn);
1450
+ if (dsn) opts.dsn = dsn;
1451
+ if (args.flags.yes) opts.yes = true;
1452
+ if (args.flags["dry-run"]) opts.dryRun = true;
1453
+ const confirm = io.confirm ?? (process.stdin.isTTY && process.stdout.isTTY && !io.out ? ttyConfirm : void 0);
1454
+ if (confirm) opts.confirm = confirm;
1455
+ await runInit(opts);
1456
+ return 0;
1457
+ }
1458
+ case "new-id":
1459
+ out(newArtifactSetId(s(args.flags.prefix) ?? "build"));
1460
+ return 0;
1461
+ case "prelude": {
1462
+ const res = writeArtifactSetModule({ file: (0, import_node_path7.resolve)(cwd, need(args, "out")), artifactSetId: s(args.flags["set-id"]) ?? env.ARGUS_ARTIFACT_SET_ID ?? need(args, "set-id") });
1463
+ out(res.file);
1464
+ return 0;
1465
+ }
1466
+ case "collect": {
1467
+ if (!target) throw new Error("collect needs a target (next, expo-export, ios, android, files)");
1468
+ const plans = plansFor(target, args, env, cwd);
1469
+ if (args.flags.json) out(JSON.stringify(plans.map((p) => ({ ...p, args: uploadArgs(p) })), null, 2));
1470
+ else out(renderPlans(plans, bin, cwd));
1471
+ return 0;
1472
+ }
1473
+ case "upload": {
1474
+ if (!target) throw new Error("upload needs a target (next, expo-export, ios, android, files)");
1475
+ const plans = plansFor(target, args, env, cwd);
1476
+ if (args.flags.print) {
1477
+ for (const p of plans) out(formatCommand(bin, uploadArgs(p, args.flags["dry-run"] ? { dryRun: true } : {}), cwd));
1478
+ return 0;
1479
+ }
1480
+ const runOpts = { bin, env, cwd, log: err };
1481
+ if (args.flags["dry-run"]) runOpts.dryRun = true;
1482
+ const url = s(args.flags.url);
1483
+ if (url) runOpts.url = url;
1484
+ if (io.exec) runOpts.exec = io.exec;
1485
+ const transport = s(args.flags.transport);
1486
+ if (transport) runOpts.transport = transport;
1487
+ else if (s(args.flags["argus-bin"])) runOpts.transport = "cli";
1488
+ const results = await runUploads(plans, runOpts);
1489
+ const failed = results.find((r) => r.code !== 0);
1490
+ if (failed) {
1491
+ err(`[argus] upload failed for ${failed.plan.label} (exit ${failed.code}): ${failed.command}`);
1492
+ return failed.code || 1;
1493
+ }
1494
+ if (target === "next" && args.flags["delete-maps"] && !args.flags["dry-run"]) {
1495
+ const maps = browserSourceMaps((args.multi.dir ?? [])[0] ? (0, import_node_path7.resolve)(cwd, args.multi.dir[0]) : cwd, s(args.flags["dist-dir"]) ?? ".next");
1496
+ for (const m of maps) (0, import_node_fs6.rmSync)(m, { force: true });
1497
+ err(`[argus] deleted ${maps.length} browser source map(s) from the build output`);
1498
+ }
1499
+ return 0;
1500
+ }
1501
+ default:
1502
+ throw new Error(`unknown command "${command}"`);
1503
+ }
1504
+ } catch (e) {
1505
+ err(`argus-build: ${e instanceof Error ? e.message : String(e)}`);
1506
+ return 2;
1507
+ }
1508
+ }
1509
+ // Annotate the CommonJS export names for ESM import in node:
1510
+ 0 && (module.exports = {
1511
+ ARTIFACT_SET_GLOBAL,
1512
+ NEXT_PUBLIC_ENV,
1513
+ NEXT_SERVER_ENV,
1514
+ applyInit,
1515
+ artifactSetPrelude,
1516
+ assertArtifactSetId,
1517
+ browserSourceMaps,
1518
+ buildManifest,
1519
+ classifyArtifact,
1520
+ detectPackageManager,
1521
+ detectProject,
1522
+ formatCommand,
1523
+ injectArtifactSetId,
1524
+ isJsOnlyPlan,
1525
+ isValidArtifactSetId,
1526
+ manifestName,
1527
+ newArtifactSetId,
1528
+ nextArtifactSetIds,
1529
+ nextUrlPrefix,
1530
+ planExpoExport,
1531
+ planFiles,
1532
+ planInit,
1533
+ planNative,
1534
+ planNext,
1535
+ renderInit,
1536
+ resolveArtifactSetId,
1537
+ runCli,
1538
+ runInit,
1539
+ runUploads,
1540
+ uploadArgs,
1541
+ uploadNextBuild,
1542
+ uploadPlanOverHttp,
1543
+ walkFiles,
1544
+ withArgusMetro,
1545
+ withArgusNext,
1546
+ writeArtifactSetModule
1547
+ });
1548
+ //# sourceMappingURL=index.cjs.map