@getexception/cli 0.1.7 → 0.1.8

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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +70 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -16,11 +16,13 @@ yarn getexception sourcemaps upload --dir .getexception-maps --url https://sentr
16
16
 
17
17
  The Owner creates **Source map upload tokens** in the project's settings. Store the token in the CI secret `GETEXCEPTION_UPLOAD_TOKEN`. It expires after 90 days and can be revoked. Never pass it as a command-line argument or include it in the browser environment, repository, logs or public artifacts. Only trusted build jobs may read this secret.
18
18
 
19
+ For GitLab MR previews use short-lived `GETEXCEPTION_GITLAB_ID_TOKEN` instead, after the operator has configured the pinned GitLab trust policy. Run `getexception ci context --url <HTTPS dashboard origin> --project <UUID>` before building; use the returned release, deployment and asset prefix for the actual JavaScript output. Upload and release registration use that identity automatically. Never set both credential variables. See the [GitLab integration guide](../../docs/gitlab-ci.md) for server configuration, exact build isolation and production restrictions. Version 0.1.7 does not support this flow.
20
+
19
21
  `prepare` modifies the **final ESM JavaScript**: it adds a Debug ID, shifts mappings by one line, removes sourceMappingURL comments and moves `.js.map` / `.mjs.map` files out of the public build. The private output directory must not exist and must be outside `dist`. Run this once per fresh build, before compression, SRI signing or deployment. Legacy IIFE/CommonJS bundles are unsupported. If assets are served below an additional base path, pass `--url-prefix <base-path>`; paths must match deployed JavaScript URLs.
20
22
 
21
- Deploy the resulting `dist` unchanged. Upload failure does not undo preparation: keep `.getexception-maps` as a **private, access-controlled CI artifact** and retry `upload` with that same directory. Do not run `prepare` twice or rebuild only the maps. Never publish the private artifact directory alongside the application. Other map formats (for example CSS maps) should also be excluded from the public deploy artifact by your build configuration.
23
+ Deploy the resulting `dist` unchanged. Upload failure does not undo preparation: retry within the same job using the same prepared directory. **Do not save source maps as GitLab artifacts or cache when access has not been verified.** The GitLab identity flow keeps them only in the isolated job workspace and uploads directly to GetException. A retry after that job ends requires a new build with a new job prefix; it cannot replace maps for the old build. Do not run `prepare` twice or rebuild only the maps. Never publish the private artifact directory alongside the application. Other map formats (for example CSS maps) should also be excluded from the public deploy artifact by your build configuration.
22
24
 
23
- `upload` sends an authenticated manifest, uploads files with checksums, then waits for background validation. It supports retries and resuming the same manifest, has a two-minute deadline, and exits nonzero on failure. CI may allow this job to fail for an urgent application release while retaining its private artifact for retry. Raw maps have no download endpoint.
25
+ `upload` sends an authenticated manifest, uploads files with checksums, then waits for background validation. It supports retries and resuming the same manifest, has a two-minute deadline, and exits nonzero on failure. CI may allow an urgent production deployment without maps with an explicit warning. MR previews require all batches to be ready before deploying. Do not retain maps in GitLab artifacts as a fallback. Raw maps have no download endpoint.
24
26
 
25
27
  Limits: 128 JS files and 128 MiB per upload, 16 MiB per map, 1 GiB per project, 10 GiB per installation. Non-indexed Source Map v3 JSON only; no archives, compression or remote source downloads. Existing events are processed after a late upload. Errors thrown in the browser console have no source file and cannot gain a source snippet from a map.
26
28
 
package/dist/index.js CHANGED
@@ -18985,6 +18985,13 @@ var releaseRegistrationSchema = external_exports.object({
18985
18985
  deployment: deploymentSchema
18986
18986
  }).strict();
18987
18987
 
18988
+ // packages/protocol/src/ci.ts
18989
+ var buildContextSchema = external_exports.object({
18990
+ release: releaseNameSchema,
18991
+ assetPrefix: external_exports.string().regex(/^assets\/ge-gl-[1-9][0-9]*-[1-9][0-9]*\/$/),
18992
+ deployment: deploymentSchema
18993
+ }).strict();
18994
+
18988
18995
  // packages/protocol/src/index.ts
18989
18996
  var safeFrameSchema = external_exports.object({
18990
18997
  filename: external_exports.string().max(512),
@@ -19163,25 +19170,54 @@ import { setTimeout as delay2 } from "timers/promises";
19163
19170
 
19164
19171
  // packages/cli/src/api.ts
19165
19172
  import { setTimeout as delay } from "timers/promises";
19173
+
19174
+ // packages/cli/src/credentials.ts
19175
+ function ciCredential(env = process.env) {
19176
+ if (env.GETEXCEPTION_GITLAB_ID_TOKEN && env.GETEXCEPTION_UPLOAD_TOKEN) {
19177
+ throw new Error("Choose one CI authentication method");
19178
+ }
19179
+ return env.GETEXCEPTION_GITLAB_ID_TOKEN ? { gitlabIdToken: env.GETEXCEPTION_GITLAB_ID_TOKEN } : env.GETEXCEPTION_UPLOAD_TOKEN ?? "";
19180
+ }
19181
+ function authorizationHeader(credential) {
19182
+ if (typeof credential === "string") {
19183
+ if (!/^[a-f0-9]{64}$/.test(credential)) {
19184
+ throw new Error(
19185
+ "Set GETEXCEPTION_UPLOAD_TOKEN or GETEXCEPTION_GITLAB_ID_TOKEN"
19186
+ );
19187
+ }
19188
+ return `Bearer ${credential}`;
19189
+ }
19190
+ if (credential.gitlabIdToken.length > 16384 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(
19191
+ credential.gitlabIdToken
19192
+ )) {
19193
+ throw new Error("Invalid GitLab build identity");
19194
+ }
19195
+ return `GitLab ${credential.gitlabIdToken}`;
19196
+ }
19197
+
19198
+ // packages/cli/src/api.ts
19166
19199
  function projectApi(address, project, token, transport = fetch) {
19167
19200
  const url2 = new URL(address);
19168
19201
  if (url2.protocol !== "https:" || url2.username || url2.password || url2.search || url2.hash || url2.pathname !== "/") {
19169
19202
  throw new Error("Use the HTTPS dashboard origin");
19170
19203
  }
19171
- if (!external_exports.string().uuid().safeParse(project).success || !/^[a-f0-9]{64}$/.test(token)) {
19172
- throw new Error("Set a valid project ID and GETEXCEPTION_UPLOAD_TOKEN");
19204
+ if (!external_exports.string().uuid().safeParse(project).success) {
19205
+ throw new Error("Set a valid project ID");
19173
19206
  }
19207
+ const authorization = authorizationHeader(token);
19174
19208
  const base = `${url2.origin}/api/v1/projects/${project}`;
19175
19209
  const deadline = Date.now() + 12e4;
19176
19210
  return async function request(path, method = "GET", body) {
19177
19211
  for (let attempt = 0; attempt < 3; attempt++) {
19178
19212
  if (Date.now() >= deadline) {
19179
- throw new Error("Upload timed out; retain private artifacts and retry");
19213
+ throw new Error(
19214
+ "Upload timed out; retry within the job or run a new build"
19215
+ );
19180
19216
  }
19181
19217
  const response = await transport(base + path, {
19182
19218
  method,
19183
19219
  headers: {
19184
- Authorization: `Bearer ${token}`,
19220
+ Authorization: authorization,
19185
19221
  "Content-Type": "application/json"
19186
19222
  },
19187
19223
  body,
@@ -19266,6 +19302,13 @@ async function registerRelease(address, project, token, value, transport = fetch
19266
19302
  )("/releases", "POST", JSON.stringify(input2));
19267
19303
  }
19268
19304
 
19305
+ // packages/cli/src/ci.ts
19306
+ async function buildContext(address, project, credential, transport = fetch) {
19307
+ return buildContextSchema.parse(
19308
+ await projectApi(address, project, credential, transport)("/ci", "POST")
19309
+ );
19310
+ }
19311
+
19269
19312
  // packages/cli/src/index.ts
19270
19313
  async function main() {
19271
19314
  const { values, positionals } = parseArgs({
@@ -19285,30 +19328,33 @@ async function main() {
19285
19328
  });
19286
19329
  if (values.help) {
19287
19330
  process.stdout.write(
19288
- "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>]\nToken: GETEXCEPTION_UPLOAD_TOKEN environment variable. Prepare modifies ESM JavaScript and removes public .js.map files.\n"
19331
+ "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"
19332
+ );
19333
+ return;
19334
+ }
19335
+ if (positionals.length === 2 && positionals[0] === "ci" && positionals[1] === "context" && values.url && values.project) {
19336
+ process.stdout.write(
19337
+ JSON.stringify(
19338
+ await buildContext(values.url, values.project, ciCredential())
19339
+ ) + "\n"
19289
19340
  );
19290
19341
  return;
19291
19342
  }
19292
19343
  if (positionals.length === 2 && positionals[0] === "releases" && positionals[1] === "register" && values.url && values.project) {
19293
19344
  const hasReview = values["repository-id"] !== void 0 || values["merge-request"] !== void 0;
19294
- await registerRelease(
19295
- values.url,
19296
- values.project,
19297
- process.env.GETEXCEPTION_UPLOAD_TOKEN ?? "",
19298
- {
19299
- release: values.release,
19300
- deployment: {
19301
- environment: values.environment,
19302
- ...hasReview ? {
19303
- review: {
19304
- provider: "gitlab",
19305
- repositoryId: Number(values["repository-id"]),
19306
- number: Number(values["merge-request"])
19307
- }
19308
- } : {}
19309
- }
19345
+ await registerRelease(values.url, values.project, ciCredential(), {
19346
+ release: values.release,
19347
+ deployment: {
19348
+ environment: values.environment,
19349
+ ...hasReview ? {
19350
+ review: {
19351
+ provider: "gitlab",
19352
+ repositoryId: Number(values["repository-id"]),
19353
+ number: Number(values["merge-request"])
19354
+ }
19355
+ } : {}
19310
19356
  }
19311
- );
19357
+ });
19312
19358
  process.stdout.write("Release environment registered.\n");
19313
19359
  return;
19314
19360
  }
@@ -19327,12 +19373,7 @@ async function main() {
19327
19373
  `
19328
19374
  );
19329
19375
  } else if (positionals[1] === "upload" && values.url && values.project) {
19330
- await uploadMaps(
19331
- values.dir,
19332
- values.url,
19333
- values.project,
19334
- process.env.GETEXCEPTION_UPLOAD_TOKEN ?? ""
19335
- );
19376
+ await uploadMaps(values.dir, values.url, values.project, ciCredential());
19336
19377
  process.stdout.write("Source maps validated and ready.\n");
19337
19378
  } else {
19338
19379
  throw new Error("Invalid command");
@@ -19340,7 +19381,7 @@ async function main() {
19340
19381
  }
19341
19382
  void main().catch(() => {
19342
19383
  process.stderr.write(
19343
- "Command failed. Check options (--help), CI token, limits and private artifacts; retry upload without rebuilding.\n"
19384
+ "Command failed. Check options (--help), CI authentication, limits and build scope. Never publish maps as CI artifacts.\n"
19344
19385
  );
19345
19386
  process.exitCode = 1;
19346
19387
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getexception/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Private source map uploads for GetException",
5
5
  "type": "module",
6
6
  "license": "MIT",