@getexception/cli 0.1.24 → 0.1.26

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.js CHANGED
@@ -8,27 +8,9 @@ var __export = (target, all) => {
8
8
  // packages/cli/src/index.ts
9
9
  import { parseArgs } from "util";
10
10
 
11
- // packages/cli/src/prepare.ts
12
- import { createHash, randomUUID } from "crypto";
13
- import {
14
- lstat,
15
- mkdir,
16
- readdir,
17
- readFile,
18
- realpath,
19
- rename,
20
- unlink,
21
- writeFile
22
- } from "fs/promises";
23
- import {
24
- basename,
25
- dirname,
26
- isAbsolute,
27
- join,
28
- relative,
29
- resolve,
30
- sep
31
- } from "path";
11
+ // packages/cli/src/source-map-delivery.ts
12
+ import { mkdir as mkdir3, realpath as realpath2, readdir as readdir3, rm as rm2 } from "fs/promises";
13
+ import { dirname as dirname3, resolve as resolve2, relative as relative4, isAbsolute as isAbsolute2, sep as sep3, join as join4 } from "path";
32
14
 
33
15
  // node_modules/zod/v4/classic/external.js
34
16
  var external_exports = {};
@@ -1841,18 +1823,18 @@ var validateAsync = async (schema, value, _ctx) => {
1841
1823
  return result.issues.length === 0;
1842
1824
  };
1843
1825
  var _encode = (_Err) => {
1844
- const parse3 = _parse(_Err);
1826
+ const parse5 = _parse(_Err);
1845
1827
  const fn = (schema, value, _ctx, _params) => {
1846
1828
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
1847
- return parse3(schema, value, ctx, finalizeParams(fn, _params));
1829
+ return parse5(schema, value, ctx, finalizeParams(fn, _params));
1848
1830
  };
1849
1831
  return fn;
1850
1832
  };
1851
1833
  var encode = /* @__PURE__ */ _encode($ZodRealError);
1852
1834
  var _decode = (_Err) => {
1853
- const parse3 = _parse(_Err);
1835
+ const parse5 = _parse(_Err);
1854
1836
  const fn = (schema, value, _ctx, _params) => {
1855
- return parse3(schema, value, _ctx, finalizeParams(fn, _params));
1837
+ return parse5(schema, value, _ctx, finalizeParams(fn, _params));
1856
1838
  };
1857
1839
  return fn;
1858
1840
  };
@@ -18906,7 +18888,12 @@ var SOURCE_MAP_LIMITS = {
18906
18888
  diskReserveBytes: 5 * 1024 * 1024 * 1024,
18907
18889
  manifestBytes: 64 * 1024
18908
18890
  };
18909
- var releaseNameSchema = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,79}@[a-f0-9]{40}$/);
18891
+ var releaseNameSchema = external_exports.string().max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*(?:@[A-Za-z0-9][A-Za-z0-9._+-]*)?$/).refine((value) => !/^[^@]+@[a-zA-Z][^@]*\.[a-zA-Z]{2,}$/.test(value));
18892
+ var environmentNameSchema = external_exports.string().max(64).regex(/^[a-z][a-z0-9_-]{0,63}$/).refine(
18893
+ (value) => !["all", "constructor", "prototype", "__proto__"].includes(value)
18894
+ );
18895
+ var buildIdSchema = external_exports.string().max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/);
18896
+ var environmentListSchema = external_exports.array(environmentNameSchema).max(20).refine((values) => new Set(values).size === values.length);
18910
18897
  var artifactPathSchema = external_exports.string().max(512).regex(/^[a-zA-Z0-9_@~.-]+(?:\/[a-zA-Z0-9_@~.-]+)*\.(?:js|mjs)$/).refine(
18911
18898
  (value) => !value.split("/").some((part) => part === "." || part === "..")
18912
18899
  );
@@ -18918,9 +18905,13 @@ var sourceArtifactSchema = external_exports.object({
18918
18905
  }).strict();
18919
18906
  var sourceUploadSchema = external_exports.object({
18920
18907
  release: releaseNameSchema,
18908
+ environment: environmentNameSchema.optional(),
18909
+ buildId: buildIdSchema.optional(),
18910
+ batchIndex: external_exports.number().int().min(0).max(4095).optional(),
18911
+ batchCount: external_exports.number().int().min(1).max(4096).optional(),
18921
18912
  artifacts: external_exports.array(sourceArtifactSchema).min(1).max(SOURCE_MAP_LIMITS.files)
18922
18913
  }).strict().superRefine((value, ctx) => {
18923
- if (value.artifacts.reduce((sum, file2) => sum + file2.size, 0) > SOURCE_MAP_LIMITS.releaseBytes || new Set(value.artifacts.map((file2) => file2.path)).size !== value.artifacts.length || new Set(value.artifacts.map((file2) => file2.debugId)).size !== value.artifacts.length) {
18914
+ if ((value.batchIndex !== void 0 || value.batchCount !== void 0) && (value.batchIndex === void 0 || value.batchCount === void 0 || value.batchIndex >= value.batchCount) || value.artifacts.reduce((sum, file2) => sum + file2.size, 0) > SOURCE_MAP_LIMITS.releaseBytes || new Set(value.artifacts.map((file2) => file2.path)).size !== value.artifacts.length || new Set(value.artifacts.map((file2) => file2.debugId)).size !== value.artifacts.length) {
18924
18915
  ctx.addIssue({ code: "custom", message: "Invalid artifact manifest" });
18925
18916
  }
18926
18917
  });
@@ -18938,20 +18929,18 @@ var originalFrameSchema = external_exports.object({
18938
18929
  var appVersionSchema = external_exports.string().max(64).refine((value) => value.trim() === value).regex(
18939
18930
  /^(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})(?:-(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/
18940
18931
  );
18941
- var RELEASE_ENVIRONMENTS = [
18942
- "production",
18943
- "staging",
18944
- "development"
18945
- ];
18946
18932
  var deploymentSchema = external_exports.object({
18947
- environment: external_exports.enum(RELEASE_ENVIRONMENTS),
18933
+ environment: environmentNameSchema,
18948
18934
  review: external_exports.object({
18949
- provider: external_exports.literal("gitlab"),
18950
- repositoryId: external_exports.number().int().min(1).max(2147483647),
18935
+ provider: external_exports.enum(["gitlab", "github", "other"]),
18936
+ repositoryId: external_exports.union([
18937
+ external_exports.number().int().min(1).max(2147483647),
18938
+ external_exports.string().max(200).regex(/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+$/)
18939
+ ]),
18951
18940
  number: external_exports.number().int().min(1).max(2147483647)
18952
18941
  }).strict().optional()
18953
- }).strict().refine((value) => !value.review || value.environment === "staging", {
18954
- message: "Merge requests belong to staging"
18942
+ }).strict().refine((value) => !value.review || value.environment !== "production", {
18943
+ message: "Reviews belong to non-production environments"
18955
18944
  });
18956
18945
  var releaseRegistrationSchema = external_exports.object({
18957
18946
  release: releaseNameSchema,
@@ -19075,7 +19064,7 @@ var safeEventSchema = external_exports.object({
19075
19064
  message: external_exports.string().max(1024),
19076
19065
  exceptionType: external_exports.string().max(120),
19077
19066
  handled: external_exports.boolean(),
19078
- environment: external_exports.enum(["production", "staging", "development"]),
19067
+ environment: environmentNameSchema,
19079
19068
  release: external_exports.string().max(160).optional(),
19080
19069
  appVersion: appVersionSchema.optional(),
19081
19070
  dist: external_exports.string().max(64).optional(),
@@ -19087,7 +19076,43 @@ var safeEventSchema = external_exports.object({
19087
19076
  breadcrumbs: external_exports.array(safeBreadcrumbSchema).max(50)
19088
19077
  }).strict();
19089
19078
 
19079
+ // packages/cli/src/prepare-build.ts
19080
+ import { randomUUID as randomUUID2 } from "crypto";
19081
+ import {
19082
+ copyFile,
19083
+ lstat as lstat3,
19084
+ mkdir as mkdir2,
19085
+ readFile as readFile3,
19086
+ rename as rename2,
19087
+ rm,
19088
+ stat,
19089
+ unlink as unlink2,
19090
+ writeFile as writeFile2
19091
+ } from "fs/promises";
19092
+ import { dirname as dirname2, join as join3, relative as relative3 } from "path";
19093
+
19090
19094
  // packages/cli/src/prepare.ts
19095
+ import { createHash, randomUUID } from "crypto";
19096
+ import { parse as parse3 } from "acorn";
19097
+ import {
19098
+ lstat,
19099
+ mkdir,
19100
+ readdir,
19101
+ readFile,
19102
+ realpath,
19103
+ rename,
19104
+ unlink,
19105
+ writeFile
19106
+ } from "fs/promises";
19107
+ import {
19108
+ basename,
19109
+ dirname,
19110
+ isAbsolute,
19111
+ join,
19112
+ relative,
19113
+ resolve,
19114
+ sep
19115
+ } from "path";
19091
19116
  function checksum(bytes) {
19092
19117
  return createHash("sha256").update(bytes).digest("hex");
19093
19118
  }
@@ -19114,7 +19139,7 @@ async function files(root, directory = root, result = []) {
19114
19139
  }
19115
19140
  return result;
19116
19141
  }
19117
- async function prepareMaps(directory, output2, release, prefix = "") {
19142
+ async function prepareMaps(directory, output2, release, prefix = "", options = {}) {
19118
19143
  const root = await realpath(directory);
19119
19144
  const destination = join(
19120
19145
  await realpath(dirname(resolve(output2))),
@@ -19122,6 +19147,9 @@ async function prepareMaps(directory, output2, release, prefix = "") {
19122
19147
  );
19123
19148
  const child = relative(root, destination);
19124
19149
  releaseNameSchema.parse(release);
19150
+ if (options.buildId) {
19151
+ buildIdSchema.parse(options.buildId);
19152
+ }
19125
19153
  if (!child || !isAbsolute(child) && child !== ".." && !child.startsWith(`..${sep}`)) {
19126
19154
  throw new Error(
19127
19155
  "Private artifacts must be outside the public build directory"
@@ -19147,7 +19175,7 @@ async function prepareMaps(directory, output2, release, prefix = "") {
19147
19175
  (await regular(join(root, mapPath), SOURCE_MAP_LIMITS.fileBytes)).toString("utf8")
19148
19176
  );
19149
19177
  const source = (await regular(join(root, jsPath), SOURCE_MAP_LIMITS.fileBytes)).toString("utf8");
19150
- if (map2.version !== 3 || typeof map2.mappings !== "string" || map2.sections || map2.debug_id || source.startsWith(
19178
+ if (map2.version !== 3 || typeof map2.mappings !== "string" || map2.sections || map2.debug_id || source.startsWith("#!") || source.startsWith(
19151
19179
  ";(globalThis.__GETEXCEPTION_DEBUG_IDS__??=Object.create(null))"
19152
19180
  )) {
19153
19181
  throw new Error("Expected a fresh, non-indexed v3 source map");
@@ -19172,12 +19200,29 @@ async function prepareMaps(directory, output2, release, prefix = "") {
19172
19200
  /(?:^|\n)[ \t]*(?:\/\/[#@][ \t]*sourceMappingURL=[^\r\n]*|\/\*[#@][ \t]*sourceMappingURL=[^\r\n]*?\*\/)[ \t]*(?:\r?\n)?$/,
19173
19201
  (comment) => comment.replace(/[^\r\n]/g, " ")
19174
19202
  );
19175
- const js = `;(globalThis.__GETEXCEPTION_DEBUG_IDS__??=Object.create(null))[import.meta.url]=${JSON.stringify(debugId)};
19203
+ const directives = [];
19204
+ if (options.format === "classic") {
19205
+ const program = parse3(source, {
19206
+ ecmaVersion: "latest",
19207
+ sourceType: "script"
19208
+ });
19209
+ for (const statement of program.body) {
19210
+ if (!("directive" in statement) || typeof statement.directive !== "string") {
19211
+ break;
19212
+ }
19213
+ directives.push(statement.directive);
19214
+ }
19215
+ }
19216
+ const injection = options.format === "classic" ? `${directives.map((value) => JSON.stringify(value) + ";").join("")};(()=>{const s=typeof document!=="undefined"?document.currentScript?.src:undefined;if(s)(globalThis.__GETEXCEPTION_DEBUG_IDS__??=Object.create(null))[s]=${JSON.stringify(debugId)};})();` : `;(globalThis.__GETEXCEPTION_DEBUG_IDS__??=Object.create(null))[import.meta.url]=${JSON.stringify(debugId)};`;
19217
+ const js = `${injection}
19176
19218
  ${body}`;
19177
19219
  prepared.push({ mapPath, jsPath, bytes, js, entry });
19178
19220
  }
19179
19221
  const manifest = sourceUploadSchema.parse({
19180
19222
  release,
19223
+ ...options.buildId ? { buildId: options.buildId } : {},
19224
+ ...options.environment ? { environment: options.environment } : {},
19225
+ ...options.batchIndex !== void 0 ? { batchIndex: options.batchIndex, batchCount: options.batchCount } : {},
19181
19226
  artifacts: prepared.map((item) => item.entry)
19182
19227
  });
19183
19228
  await mkdir(destination, { mode: 448 });
@@ -19222,8 +19267,193 @@ async function readPreparedMap(directory, entry) {
19222
19267
  return bytes;
19223
19268
  }
19224
19269
 
19225
- // packages/cli/src/upload.ts
19270
+ // packages/cli/src/public-output.ts
19271
+ import { lstat as lstat2, readFile as readFile2, readdir as readdir2 } from "fs/promises";
19272
+ import { basename as basename2, extname, join as join2, relative as relative2, sep as sep2 } from "path";
19273
+ import { brotliDecompress, gunzip } from "zlib";
19226
19274
  import { promisify } from "util";
19275
+ import { parse as parse4 } from "acorn";
19276
+ import postcss from "postcss";
19277
+ var gunzipAsync = promisify(gunzip);
19278
+ var brotliAsync = promisify(brotliDecompress);
19279
+ var maxPublicFileBytes = 128 * 1024 * 1024;
19280
+ var inlineMap = /[#@]\s*sourceMappingURL\s*=\s*data:/i;
19281
+ var debugHeader = ";(globalThis.__GETEXCEPTION_DEBUG_IDS__??=Object.create(null))";
19282
+ async function listPublicFiles(root, directory = root) {
19283
+ if (!(await lstat2(directory)).isDirectory()) {
19284
+ throw new Error("Public output must be a directory");
19285
+ }
19286
+ const paths = [];
19287
+ for (const entry of await readdir2(directory, { withFileTypes: true })) {
19288
+ const file2 = join2(directory, entry.name);
19289
+ if (entry.isSymbolicLink() || !entry.isFile() && !entry.isDirectory()) {
19290
+ throw new Error("Public output must contain only regular files");
19291
+ }
19292
+ if (/^\.getexception-(?:maps|work)$/.test(entry.name)) {
19293
+ throw new Error("Private source map directory in public output");
19294
+ }
19295
+ if (entry.isDirectory()) {
19296
+ paths.push(...await listPublicFiles(root, file2));
19297
+ } else {
19298
+ paths.push(file2);
19299
+ }
19300
+ }
19301
+ return paths;
19302
+ }
19303
+ function hasInlineMap(source, extension) {
19304
+ if (!inlineMap.test(source)) {
19305
+ return false;
19306
+ }
19307
+ if (extension === ".css") {
19308
+ let found2 = false;
19309
+ postcss.parse(source, { map: false }).walkComments((comment) => {
19310
+ found2 ||= inlineMap.test(comment.text);
19311
+ });
19312
+ return found2;
19313
+ }
19314
+ let found = false;
19315
+ parse4(source, {
19316
+ ecmaVersion: "latest",
19317
+ sourceType: extension === ".cjs" ? "script" : "module",
19318
+ onComment: (_block, comment) => {
19319
+ found ||= inlineMap.test(comment);
19320
+ }
19321
+ });
19322
+ return found;
19323
+ }
19324
+ async function assertPublicOutput(root, options = {}) {
19325
+ for (const file2 of await listPublicFiles(root)) {
19326
+ const name = file2.replace(/\.(?:gz|br)$/i, "");
19327
+ const extension = extname(name).toLowerCase();
19328
+ const relativePath = relative2(root, file2).split(sep2).join("/");
19329
+ if (/\.map$/i.test(name)) {
19330
+ throw new Error("Source map in public output");
19331
+ }
19332
+ if (options.assetPrefix && [".js", ".mjs", ".cjs"].includes(extension) && !relativePath.startsWith(options.assetPrefix)) {
19333
+ throw new Error("Public JavaScript outside verified asset prefix");
19334
+ }
19335
+ const info = await lstat2(file2);
19336
+ if (!info.isFile() || info.size > maxPublicFileBytes) {
19337
+ throw new Error("Unverifiable public output file");
19338
+ }
19339
+ let bytes = await readFile2(file2);
19340
+ if (/\.gz$/i.test(file2)) {
19341
+ bytes = await gunzipAsync(bytes, { maxOutputLength: maxPublicFileBytes });
19342
+ } else if (/\.br$/i.test(file2)) {
19343
+ bytes = await brotliAsync(bytes, { maxOutputLength: maxPublicFileBytes });
19344
+ }
19345
+ const source = bytes.toString("utf8");
19346
+ if (options.credentials?.some(
19347
+ (credential) => credential && source.includes(credential)
19348
+ ) || /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/.test(source)) {
19349
+ throw new Error("Credential in public output");
19350
+ }
19351
+ if (basename2(name) === "release.json") {
19352
+ throw new Error("Private release metadata in public output");
19353
+ }
19354
+ if (extension === ".json") {
19355
+ const value = JSON.parse(source);
19356
+ if (value.release && Array.isArray(value.artifacts)) {
19357
+ throw new Error("Source map manifest in public output");
19358
+ }
19359
+ }
19360
+ if ([".js", ".mjs", ".cjs", ".css"].includes(extension) ? hasInlineMap(source, extension) : inlineMap.test(source)) {
19361
+ throw new Error("Inline source map in public output");
19362
+ }
19363
+ if (!options.mapsEnabled && [".js", ".mjs"].includes(extension) && source.startsWith(debugHeader)) {
19364
+ throw new Error("Prepared JavaScript in build without source maps");
19365
+ }
19366
+ }
19367
+ }
19368
+
19369
+ // packages/cli/src/prepare-build.ts
19370
+ var maxBuildMaps = 4096;
19371
+ async function prepareBuild(output2, privateRoot, release, signal = AbortSignal.timeout(5 * 6e4), options = {}) {
19372
+ signal.throwIfAborted();
19373
+ const paths = await listPublicFiles(output2);
19374
+ const maps = paths.filter((file2) => /\.(?:m?js)\.map$/i.test(file2)).sort();
19375
+ if (!maps.length || maps.length > maxBuildMaps || paths.some(
19376
+ (file2) => /\.map(?:\.(?:gz|br))?$/i.test(file2) && !maps.includes(file2)
19377
+ ) || paths.some((file2) => /\.(?:gz|br)$/i.test(file2))) {
19378
+ throw new Error("Expected fresh uncompressed JavaScript source maps");
19379
+ }
19380
+ const batches = [];
19381
+ let batchBytes = 0;
19382
+ let totalBytes = 0;
19383
+ for (const map2 of maps) {
19384
+ signal.throwIfAborted();
19385
+ const js = map2.slice(0, -4);
19386
+ const [mapInfo, jsInfo] = await Promise.all([stat(map2), stat(js)]);
19387
+ const bytes = mapInfo.size + 4096;
19388
+ if (!mapInfo.isFile() || !jsInfo.isFile() || bytes > SOURCE_MAP_LIMITS.fileBytes || jsInfo.size > SOURCE_MAP_LIMITS.fileBytes) {
19389
+ throw new Error("Oversized source map or JavaScript");
19390
+ }
19391
+ if (!batches.length || batches[batches.length - 1].length >= SOURCE_MAP_LIMITS.files || batchBytes + bytes > SOURCE_MAP_LIMITS.releaseBytes) {
19392
+ batches.push([]);
19393
+ batchBytes = 0;
19394
+ }
19395
+ batches[batches.length - 1].push(map2);
19396
+ batchBytes += bytes;
19397
+ totalBytes += bytes;
19398
+ }
19399
+ if (totalBytes > SOURCE_MAP_LIMITS.projectBytes) {
19400
+ throw new Error("Build exceeds project source map quota");
19401
+ }
19402
+ const work = join3(privateRoot, "work");
19403
+ const privateMaps = join3(privateRoot, "maps");
19404
+ await mkdir2(work, { mode: 448 });
19405
+ await mkdir2(privateMaps, { mode: 448 });
19406
+ const prepared = [];
19407
+ for (const [index, batch] of batches.entries()) {
19408
+ signal.throwIfAborted();
19409
+ const name = `batch-${String(index + 1).padStart(3, "0")}`;
19410
+ const staging = join3(work, name);
19411
+ const destination = join3(privateMaps, name);
19412
+ await mkdir2(staging, { mode: 448 });
19413
+ for (const map2 of batch) {
19414
+ for (const file2 of [map2, map2.slice(0, -4)]) {
19415
+ signal.throwIfAborted();
19416
+ const copy = join3(staging, relative3(output2, file2));
19417
+ await mkdir2(dirname2(copy), { recursive: true, mode: 448 });
19418
+ await copyFile(file2, copy);
19419
+ }
19420
+ }
19421
+ await prepareMaps(staging, destination, release, options.urlPrefix, {
19422
+ ...options,
19423
+ ...options.buildId ? { batchIndex: index, batchCount: batches.length } : {}
19424
+ });
19425
+ signal.throwIfAborted();
19426
+ for (const map2 of batch) {
19427
+ const js = map2.slice(0, -4);
19428
+ prepared.push({ from: join3(staging, relative3(output2, js)), to: js });
19429
+ }
19430
+ }
19431
+ for (const { from, to } of prepared) {
19432
+ signal.throwIfAborted();
19433
+ const temporary = `${to}.${randomUUID2()}.tmp`;
19434
+ try {
19435
+ await writeFile2(temporary, await readFile3(from, { signal }), {
19436
+ flag: "wx",
19437
+ signal
19438
+ });
19439
+ signal.throwIfAborted();
19440
+ await rename2(temporary, to);
19441
+ } finally {
19442
+ await rm(temporary, { force: true });
19443
+ }
19444
+ }
19445
+ for (const map2 of maps) {
19446
+ signal.throwIfAborted();
19447
+ if (!(await lstat3(map2)).isFile()) {
19448
+ throw new Error("Source map changed during preparation");
19449
+ }
19450
+ await unlink2(map2);
19451
+ }
19452
+ return privateMaps;
19453
+ }
19454
+
19455
+ // packages/cli/src/upload.ts
19456
+ import { promisify as promisify2 } from "util";
19227
19457
  import { setTimeout as delay2 } from "timers/promises";
19228
19458
  import { constants, gzip } from "zlib";
19229
19459
 
@@ -19242,7 +19472,13 @@ var codeSchema = external_exports.enum([
19242
19472
  "HTTP_ERROR",
19243
19473
  "CONTRACT_JSON",
19244
19474
  "CONTRACT_RESPONSE",
19245
- "COMMAND_FAILED"
19475
+ "COMMAND_FAILED",
19476
+ "ENVIRONMENT_FORBIDDEN",
19477
+ "BUILD_CONFLICT",
19478
+ "INVALID_UPLOAD",
19479
+ "STORAGE_FULL",
19480
+ "RATE_LIMITED",
19481
+ "UPLOAD_UNAVAILABLE"
19246
19482
  ]);
19247
19483
  var diagnosticSchema = external_exports.object({
19248
19484
  version: external_exports.literal(1),
@@ -19330,6 +19566,17 @@ function authorizationHeader(credential) {
19330
19566
  }
19331
19567
 
19332
19568
  // packages/cli/src/api-response.ts
19569
+ var uploadFailureSchema = external_exports.object({
19570
+ code: external_exports.enum([
19571
+ "ENVIRONMENT_FORBIDDEN",
19572
+ "BUILD_CONFLICT",
19573
+ "INVALID_UPLOAD",
19574
+ "STORAGE_FULL",
19575
+ "RATE_LIMITED",
19576
+ "UPLOAD_UNAVAILABLE"
19577
+ ]),
19578
+ requestId: external_exports.string().uuid()
19579
+ });
19333
19580
  async function responseJson(response) {
19334
19581
  const reader = response.body?.getReader();
19335
19582
  const chunks = [];
@@ -19379,6 +19626,14 @@ async function httpError(response) {
19379
19626
  result.data.requestId
19380
19627
  );
19381
19628
  }
19629
+ const upload = uploadFailureSchema.safeParse(candidate);
19630
+ if (upload.success) {
19631
+ return new CliError(
19632
+ upload.data.code,
19633
+ response.status,
19634
+ upload.data.requestId
19635
+ );
19636
+ }
19382
19637
  }
19383
19638
  } catch {
19384
19639
  }
@@ -19413,6 +19668,9 @@ function projectApi(address, project, token, transport = fetch, options = {}) {
19413
19668
  const requestTimeout = options.requestTimeoutMs ?? 3e4;
19414
19669
  return async function request(path, method = "GET", body, contentEncoding) {
19415
19670
  for (let attempt = 0; attempt < 3; attempt++) {
19671
+ if (options.signal?.aborted) {
19672
+ throw new CliError("NETWORK_TIMEOUT");
19673
+ }
19416
19674
  if (Date.now() >= deadline) {
19417
19675
  throw new CliError("NETWORK_TIMEOUT");
19418
19676
  }
@@ -19428,14 +19686,21 @@ function projectApi(address, project, token, transport = fetch, options = {}) {
19428
19686
  body,
19429
19687
  redirect: "error",
19430
19688
  credentials: "omit",
19431
- signal: AbortSignal.timeout(
19432
- Math.min(requestTimeout, deadline - Date.now())
19433
- )
19689
+ signal: AbortSignal.any([
19690
+ AbortSignal.timeout(
19691
+ Math.min(requestTimeout, deadline - Date.now())
19692
+ ),
19693
+ ...options.signal ? [options.signal] : []
19694
+ ])
19434
19695
  });
19435
19696
  } catch (error61) {
19436
19697
  const failure = networkError(error61);
19437
19698
  if (retryableNetworkError(failure) && attempt < 2) {
19438
- await delay(1e3 * 2 ** attempt);
19699
+ await delay(
19700
+ Math.min(1e3 * 2 ** attempt, Math.max(1, deadline - Date.now())),
19701
+ void 0,
19702
+ { signal: options.signal }
19703
+ );
19439
19704
  continue;
19440
19705
  }
19441
19706
  throw failure;
@@ -19445,7 +19710,11 @@ function projectApi(address, project, token, transport = fetch, options = {}) {
19445
19710
  }
19446
19711
  if ((response.status === 429 || response.status >= 500) && attempt < 2) {
19447
19712
  await response.body?.cancel().catch(() => void 0);
19448
- await delay(1e3 * 2 ** attempt);
19713
+ await delay(
19714
+ Math.min(1e3 * 2 ** attempt, Math.max(1, deadline - Date.now())),
19715
+ void 0,
19716
+ { signal: options.signal }
19717
+ );
19449
19718
  continue;
19450
19719
  }
19451
19720
  throw await httpError(response);
@@ -19468,12 +19737,17 @@ var receiptSchema = external_exports.object({
19468
19737
  });
19469
19738
  var uploadConcurrency = 8;
19470
19739
  var uploadDeadlineMs = 20 * 6e4;
19471
- var gzipAsync = promisify(gzip);
19472
- async function uploadMaps(directory, address, project, token, transport = fetch) {
19473
- const manifest = await readPrepared(directory);
19740
+ var gzipAsync = promisify2(gzip);
19741
+ async function uploadMaps(directory, address, project, token, transport = fetch, options = {}) {
19742
+ const prepared = await readPrepared(directory);
19743
+ const manifest = sourceUploadSchema.parse({
19744
+ ...prepared,
19745
+ ...options.environment ? { environment: options.environment } : {}
19746
+ });
19474
19747
  const api = projectApi(address, project, token, transport, {
19475
- deadlineMs: uploadDeadlineMs,
19476
- requestTimeoutMs: 6e4
19748
+ deadlineMs: options.deadlineMs ?? uploadDeadlineMs,
19749
+ requestTimeoutMs: 6e4,
19750
+ signal: options.signal
19477
19751
  });
19478
19752
  const request = (path, method = "GET", body, contentEncoding) => api("/source-maps" + path, method, body, contentEncoding);
19479
19753
  const receipt = receiptSchema.parse(
@@ -19521,7 +19795,96 @@ async function uploadMaps(directory, address, project, token, transport = fetch)
19521
19795
  if (result.status === "failed") {
19522
19796
  throw new Error("Source map validation failed; rebuild the artifacts");
19523
19797
  }
19524
- await delay2(1e3);
19798
+ await delay2(1e3, void 0, { signal: options.signal });
19799
+ }
19800
+ }
19801
+
19802
+ // packages/cli/src/source-map-delivery.ts
19803
+ async function prepareSourceMaps(options) {
19804
+ releaseNameSchema.parse(options.release);
19805
+ buildIdSchema.parse(options.buildId);
19806
+ const directory = await realpath2(options.directory);
19807
+ const target = resolve2(options.output);
19808
+ const output2 = join4(
19809
+ await realpath2(dirname3(target)),
19810
+ target.slice(dirname3(target).length + 1)
19811
+ );
19812
+ const child = relative4(directory, output2);
19813
+ if (!child || !isAbsolute2(child) && child !== ".." && !child.startsWith(`..${sep3}`)) {
19814
+ throw new CliError("CLI_CONFIGURATION");
19815
+ }
19816
+ await mkdir3(output2, { mode: 448 });
19817
+ try {
19818
+ const maps = await prepareBuild(
19819
+ directory,
19820
+ output2,
19821
+ options.release,
19822
+ options.signal,
19823
+ options
19824
+ );
19825
+ await assertPublicOutput(directory, {
19826
+ mapsEnabled: true,
19827
+ credentials: options.credentials
19828
+ });
19829
+ await rm2(join4(output2, "work"), { recursive: true });
19830
+ return {
19831
+ directory: maps,
19832
+ release: options.release,
19833
+ buildId: options.buildId
19834
+ };
19835
+ } catch (error61) {
19836
+ await rm2(output2, { recursive: true, force: true });
19837
+ throw error61;
19838
+ }
19839
+ }
19840
+ async function uploadSourceMaps(options, transport = fetch) {
19841
+ try {
19842
+ options.signal?.throwIfAborted();
19843
+ if (typeof options.token !== "string") {
19844
+ throw new CliError("CLI_CONFIGURATION");
19845
+ }
19846
+ authorizationHeader(options.token);
19847
+ const environment = environmentNameSchema.parse(options.environment);
19848
+ const budget = options.timeoutMs ?? 30 * 6e4;
19849
+ if (!Number.isInteger(budget) || budget < 1e3 || budget > 60 * 6e4) {
19850
+ throw new CliError("CLI_CONFIGURATION");
19851
+ }
19852
+ const deadline = Date.now() + budget;
19853
+ const entries = await readdir3(options.directory, { withFileTypes: true });
19854
+ const batches = entries.filter((entry) => /^batch-[0-9]{3,4}$/.test(entry.name)).sort((a, b) => Number(a.name.slice(6)) - Number(b.name.slice(6)));
19855
+ if (!batches.length || batches.length > 4096 || batches.some((entry) => !entry.isDirectory() || entry.isSymbolicLink()) || batches.length !== entries.length) {
19856
+ throw new CliError("CLI_CONFIGURATION");
19857
+ }
19858
+ const manifests = await Promise.all(
19859
+ batches.map((entry) => readPrepared(join4(options.directory, entry.name)))
19860
+ );
19861
+ const first = manifests[0];
19862
+ if (!first.buildId || manifests.some(
19863
+ (manifest, index) => manifest.buildId !== first.buildId || manifest.release !== first.release || manifest.batchCount !== batches.length || manifest.batchIndex !== index
19864
+ )) {
19865
+ throw new CliError("CLI_CONFIGURATION");
19866
+ }
19867
+ for (const batch of batches) {
19868
+ options.signal?.throwIfAborted();
19869
+ const remaining = deadline - Date.now();
19870
+ if (remaining < 1) {
19871
+ throw new CliError("NETWORK_TIMEOUT");
19872
+ }
19873
+ await uploadMaps(
19874
+ join4(options.directory, batch.name),
19875
+ options.url,
19876
+ options.projectId,
19877
+ options.token,
19878
+ transport,
19879
+ { environment, deadlineMs: remaining, signal: options.signal }
19880
+ );
19881
+ }
19882
+ return { status: "ready", batches: batches.length };
19883
+ } catch (error61) {
19884
+ return {
19885
+ status: "unavailable",
19886
+ diagnostic: formatDiagnostic(error61).trim()
19887
+ };
19525
19888
  }
19526
19889
  }
19527
19890
 
@@ -19556,10 +19919,16 @@ async function main() {
19556
19919
  dir: { type: "string" },
19557
19920
  output: { type: "string" },
19558
19921
  release: { type: "string" },
19922
+ "build-id": { type: "string" },
19923
+ format: { type: "string" },
19924
+ "timeout-seconds": { type: "string" },
19925
+ "app-version": { type: "string" },
19926
+ "review-provider": { type: "string" },
19559
19927
  "url-prefix": { type: "string" },
19560
19928
  url: { type: "string" },
19561
19929
  project: { type: "string" },
19562
19930
  environment: { type: "string" },
19931
+ "token-env": { type: "string" },
19563
19932
  "repository-id": { type: "string" },
19564
19933
  "merge-request": { type: "string" },
19565
19934
  help: { type: "boolean" }
@@ -19567,10 +19936,14 @@ async function main() {
19567
19936
  });
19568
19937
  if (values.help) {
19569
19938
  process.stdout.write(
19570
- "getexception sourcemaps prepare --dir dist --output ../private-maps --release app@<40-character-SHA> [--url-prefix assets]\ngetexception sourcemaps upload --dir ../private-maps --url https://dashboard.example --project <UUID>\ngetexception releases register --url https://dashboard.example --project <UUID> --release app@<40-character-SHA> --environment <production|staging|development> [--repository-id <GitLab project ID> --merge-request <IID>]\ngetexception ci context --url https://dashboard.example --project <UUID>\nAuthentication: GETEXCEPTION_UPLOAD_TOKEN or GETEXCEPTION_GITLAB_ID_TOKEN, never both. Prepare modifies ESM JavaScript and removes public .js.map files.\n"
19939
+ "getexception sourcemaps prepare --dir dist --output ../private-maps --release web-app@1.2.3 --build-id build-001 [--format esm|classic] [--url-prefix assets]\ngetexception sourcemaps check --dir dist\ngetexception sourcemaps deliver --dir ../private-maps/maps --url https://monitor.example.com --project <UUID> --environment production --token-env MY_UPLOAD_KEY [--timeout-seconds 1800]\ngetexception releases register --url https://monitor.example.com --project <UUID> --release web-app@1.2.3 --environment production [--app-version 1.2.3]\nDeliver requires --token-env NAME to read the key from your chosen private environment variable. Never pass the key value as an argument. Prepare/check accept --token-env NAME to detect a leaked key in public output. Legacy upload/register/ci commands retain their existing credentials when no explicit variable is selected. Prepare modifies browser JavaScript and removes public .js.map files. Deliver reports monitoring failures without failing deployment; prepare/check errors must be handled before publishing. Legacy commands: sourcemaps upload, ci context.\n"
19571
19940
  );
19572
19941
  return;
19573
19942
  }
19943
+ if (values["token-env"] && !/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(values["token-env"])) {
19944
+ throw new CliError("CLI_CONFIGURATION");
19945
+ }
19946
+ const uploadToken = values["token-env"] ? process.env[values["token-env"]] : void 0;
19574
19947
  if (positionals.length === 2 && positionals[0] === "ci" && positionals[1] === "context" && values.url && values.project) {
19575
19948
  process.stdout.write(
19576
19949
  JSON.stringify(
@@ -19581,26 +19954,65 @@ async function main() {
19581
19954
  }
19582
19955
  if (positionals.length === 2 && positionals[0] === "releases" && positionals[1] === "register" && values.url && values.project) {
19583
19956
  const hasReview = values["repository-id"] !== void 0 || values["merge-request"] !== void 0;
19584
- await registerRelease(values.url, values.project, ciCredential(), {
19585
- release: values.release,
19586
- deployment: {
19587
- environment: values.environment,
19588
- ...hasReview ? {
19589
- review: {
19590
- provider: "gitlab",
19591
- repositoryId: Number(values["repository-id"]),
19592
- number: Number(values["merge-request"])
19593
- }
19594
- } : {}
19957
+ await registerRelease(
19958
+ values.url,
19959
+ values.project,
19960
+ values["token-env"] ? uploadToken ?? "" : ciCredential(),
19961
+ {
19962
+ release: values.release,
19963
+ appVersion: values["app-version"],
19964
+ deployment: {
19965
+ environment: values.environment,
19966
+ ...hasReview ? {
19967
+ review: {
19968
+ provider: values["review-provider"] ?? "gitlab",
19969
+ repositoryId: /^\d+$/.test(values["repository-id"] ?? "") ? Number(values["repository-id"]) : values["repository-id"],
19970
+ number: Number(values["merge-request"])
19971
+ }
19972
+ } : {}
19973
+ }
19595
19974
  }
19596
- });
19975
+ );
19597
19976
  process.stdout.write("Release environment registered.\n");
19598
19977
  return;
19599
19978
  }
19600
19979
  if (positionals.length !== 2 || positionals[0] !== "sourcemaps" || !values.dir) {
19601
19980
  throw new Error("Invalid command");
19602
19981
  }
19603
- if (positionals[1] === "prepare" && values.output && values.release) {
19982
+ if (positionals[1] === "check") {
19983
+ await assertPublicOutput(values.dir, {
19984
+ mapsEnabled: true,
19985
+ credentials: [uploadToken ?? ""]
19986
+ });
19987
+ process.stdout.write("Public output verified.\n");
19988
+ } else if (positionals[1] === "deliver" && values.url && values.project && values.environment) {
19989
+ const result = await uploadSourceMaps({
19990
+ directory: values.dir,
19991
+ url: values.url,
19992
+ projectId: values.project,
19993
+ token: uploadToken,
19994
+ environment: values.environment,
19995
+ timeoutMs: values["timeout-seconds"] ? Number(values["timeout-seconds"]) * 1e3 : void 0
19996
+ });
19997
+ process.stdout.write(
19998
+ result.status === "ready" ? "[getexception] Source maps ready.\n" : `[getexception] ${values.environment === "production" ? "WARNING: " : ""}Source maps unavailable; deployment may continue. ${result.diagnostic}
19999
+ `
20000
+ );
20001
+ } else if (positionals[1] === "prepare" && values.output && values.release && values["build-id"]) {
20002
+ if (values.format && !["esm", "classic"].includes(values.format)) {
20003
+ throw new Error("Invalid format");
20004
+ }
20005
+ await prepareSourceMaps({
20006
+ directory: values.dir,
20007
+ output: values.output,
20008
+ release: values.release,
20009
+ buildId: values["build-id"],
20010
+ urlPrefix: values["url-prefix"],
20011
+ format: values.format,
20012
+ credentials: [uploadToken ?? ""]
20013
+ });
20014
+ process.stdout.write("Prepared private source maps.\n");
20015
+ } else if (positionals[1] === "prepare" && values.output && values.release) {
19604
20016
  const result = await prepareMaps(
19605
20017
  values.dir,
19606
20018
  values.output,
@@ -19612,7 +20024,14 @@ async function main() {
19612
20024
  `
19613
20025
  );
19614
20026
  } else if (positionals[1] === "upload" && values.url && values.project) {
19615
- await uploadMaps(values.dir, values.url, values.project, ciCredential());
20027
+ await uploadMaps(
20028
+ values.dir,
20029
+ values.url,
20030
+ values.project,
20031
+ values["token-env"] ? uploadToken ?? "" : ciCredential(),
20032
+ void 0,
20033
+ { environment: values.environment }
20034
+ );
19616
20035
  process.stdout.write("Source maps validated and ready.\n");
19617
20036
  } else {
19618
20037
  throw new Error("Invalid command");