@fixback/cli 0.1.0 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fixback
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -5,6 +5,8 @@ production build's sourcemaps** so the Issues Fixback captures on your site
5
5
  arrive with a symbolicated **code-area pointer** — the original `file:line`
6
6
  (with source context) where the error likely lives, not a minified frame.
7
7
 
8
+ > 📖 **Full documentation: [docs.fixback.dev/sdk/sourcemaps](https://docs.fixback.dev/sdk/sourcemaps)** — the source maps guide and the CLI reference.
9
+
8
10
  ```bash
9
11
  # after your production build, locally or in CI
10
12
  FIXBACK_SECRET_KEY=sk_… npx @fixback/cli sourcemaps upload ./dist
@@ -13,6 +15,11 @@ FIXBACK_SECRET_KEY=sk_… npx @fixback/cli sourcemaps upload ./dist
13
15
  That's the whole integration. If a release has no uploaded sourcemaps, nothing
14
16
  changes — the feature is simply off for it.
15
17
 
18
+ > **Using Vite?** [`@fixback/vite-plugin`](../vite-plugin) does this automatically
19
+ > as part of `vite build`, so there's no separate CI step to add or forget. Reach
20
+ > for this CLI when your build isn't Vite, or when you want the upload decoupled
21
+ > from the build.
22
+
16
23
  ## How it fits together
17
24
 
18
25
  1. **Build with sourcemaps.** e.g. Vite: `build: { sourcemap: true }` (or
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@fixback/cli` doubles as the **build-time sourcemap library** shared with the
3
+ * bundler plugins (e.g. `@fixback/vite-plugin`): the one implementation of
4
+ * sourcemap discovery, the (release, `~/`-abstract path) upload wire (ADR-0024),
5
+ * the upload loop, and release resolution. The `fixback` bin (`cli.ts`) is a thin
6
+ * command wrapper over these same functions — importing this module runs none of it.
7
+ */
8
+ export { discoverSourcemaps, postSourcemap, toArtifactPath, uploadAll, uploadSourcemaps, } from "./upload.js";
9
+ export type { DiscoveredMap, PostSourcemapOptions, PostSourcemapResult, UploadAllDeps, UploadAllResult, UploadDeps, UploadOptions, } from "./upload.js";
10
+ export { normaliseRelease, RELEASE_MAX_LENGTH, resolveRelease, } from "./release.js";
11
+ export type { ResolveReleaseInput, ResolveReleaseResult } from "./release.js";
12
+ export { gitHeadSha } from "./main.js";
13
+ export { DEFAULT_API_URL } from "@fixback/sdk-core";
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@fixback/cli` doubles as the **build-time sourcemap library** shared with the
3
+ * bundler plugins (e.g. `@fixback/vite-plugin`): the one implementation of
4
+ * sourcemap discovery, the (release, `~/`-abstract path) upload wire (ADR-0024),
5
+ * the upload loop, and release resolution. The `fixback` bin (`cli.ts`) is a thin
6
+ * command wrapper over these same functions — importing this module runs none of it.
7
+ */
8
+ export { discoverSourcemaps, postSourcemap, toArtifactPath, uploadAll, uploadSourcemaps, } from "./upload.js";
9
+ export { normaliseRelease, RELEASE_MAX_LENGTH, resolveRelease, } from "./release.js";
10
+ export { gitHeadSha } from "./main.js";
11
+ // The hosted API origin is defined once in the shared core (ADR-0028); re-exported
12
+ // here so the bundler plugins take their default from the same constant the SDKs do.
13
+ export { DEFAULT_API_URL } from "@fixback/sdk-core";
package/dist/main.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { type UploadDeps } from "./upload.js";
2
+ /** Everything the CLI touches outside its own logic — injectable for tests. */
3
+ export interface CliDeps extends UploadDeps {
4
+ readonly env: NodeJS.ProcessEnv;
5
+ /** Resolve the git HEAD SHA of the working directory, or null when not a repo. */
6
+ readonly resolveGitSha: () => Promise<string | null>;
7
+ }
8
+ /** The production `resolveGitSha`: `git rev-parse HEAD`, null on any failure. */
9
+ export declare function gitHeadSha(): Promise<string | null>;
10
+ /** Run the CLI; returns the process exit code. */
11
+ export declare function runCli(argv: readonly string[], deps: CliDeps): Promise<number>;
package/dist/main.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
- import { normaliseRelease } from "./release.js";
3
+ import { DEFAULT_API_URL } from "@fixback/sdk-core";
4
+ import { resolveRelease } from "./release.js";
4
5
  import { uploadSourcemaps } from "./upload.js";
5
6
  /**
6
7
  * The `fixback` CLI (#117, ADR-0024) — Fixback's build-time companion, Sentry-
@@ -13,8 +14,6 @@ import { uploadSourcemaps } from "./upload.js";
13
14
  * Everything is injectable (`CliDeps`) so the command is tested without a
14
15
  * network, a repo, or real env.
15
16
  */
16
- /** The hosted API origin, mirrored by value from the SDK's `DEFAULT_API_URL`. */
17
- const DEFAULT_API_URL = "https://api.fixback.dev";
18
17
  const USAGE = `fixback — Fixback's build-time CLI
19
18
 
20
19
  Usage:
@@ -82,22 +81,22 @@ export async function runCli(argv, deps) {
82
81
  deps.error(`Missing <dir> — the build output directory to scan.\n\n${USAGE}`);
83
82
  return 1;
84
83
  }
85
- // Release: flag > env > git HEAD SHA (Sentry's propose-version equivalent).
84
+ // Release: flag > env > git HEAD SHA the precedence the bundler plugins share
85
+ // (`resolveRelease`), so a plugin upload and a CLI upload key maps identically.
86
86
  const flagRelease = flags.get("release");
87
- const rawRelease = (typeof flagRelease === "string" ? flagRelease : undefined) ??
88
- deps.env.FIXBACK_RELEASE ??
89
- (await deps.resolveGitSha()) ??
90
- undefined;
91
- if (!rawRelease) {
92
- deps.error("No release given and no git repository found. Pass --release <r> or set FIXBACK_RELEASE.");
93
- return 1;
94
- }
95
- const release = normaliseRelease(rawRelease);
96
- if (!release) {
97
- deps.error(`Invalid release ${JSON.stringify(rawRelease)} — use 1-100 visible characters ` +
98
- "with no whitespace or slashes (e.g. a git SHA or semver).");
87
+ const resolved = await resolveRelease({
88
+ release: typeof flagRelease === "string" ? flagRelease : undefined,
89
+ env: deps.env,
90
+ resolveGitSha: deps.resolveGitSha,
91
+ });
92
+ if (!resolved.ok) {
93
+ deps.error(resolved.reason === "missing"
94
+ ? "No release given and no git repository found. Pass --release <r> or set FIXBACK_RELEASE."
95
+ : `Invalid release ${JSON.stringify(resolved.raw)} — use 1-100 visible characters ` +
96
+ "with no whitespace or slashes (e.g. a git SHA or semver).");
99
97
  return 1;
100
98
  }
99
+ const release = resolved.release;
101
100
  const dryRun = flags.get("dry-run") === true;
102
101
  const flagKey = flags.get("key");
103
102
  const secretKey = (typeof flagKey === "string" ? flagKey : undefined) ??
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Resolving a build's **Release** — the one precedence rule shared by the
3
+ * `fixback` CLI and the bundler plugins (`@fixback/vite-plugin`).
4
+ *
5
+ * A release is only useful if the value the build uploads maps under is the value
6
+ * the SDK stamps on its captures (#117, ADR-0024). Two things have to agree for
7
+ * that: the *validation* (which lives once in `@fixback/sdk-core`, shared with the
8
+ * SDKs themselves) and the *precedence* — an explicit value, else `FIXBACK_RELEASE`,
9
+ * else the git HEAD SHA (Sentry's propose-version equivalent). The precedence used
10
+ * to be written out twice, once per caller; it is {@link resolveRelease} now.
11
+ */
12
+ import { normaliseRelease, RELEASE_MAX_LENGTH } from "@fixback/sdk-core";
13
+ export { normaliseRelease, RELEASE_MAX_LENGTH };
14
+ /** What {@link resolveRelease} reads, in precedence order. */
15
+ export interface ResolveReleaseInput {
16
+ /** An explicitly configured release (a `--release` flag, a plugin option). */
17
+ readonly release?: string;
18
+ /** The process environment — `FIXBACK_RELEASE` is the second source. */
19
+ readonly env: {
20
+ readonly FIXBACK_RELEASE?: string | undefined;
21
+ };
22
+ /** The git HEAD SHA, or `null` when the build is not in a repo — the fallback. */
23
+ readonly resolveGitSha: () => Promise<string | null>;
24
+ }
25
+ /**
26
+ * A resolved release, or *why* there is none — `missing` (nothing configured and
27
+ * no repo to fall back on) versus `invalid` (a value that would be discarded
28
+ * server-side). The two need different messages, so they stay distinct rather than
29
+ * collapsing to `undefined`.
30
+ */
31
+ export type ResolveReleaseResult = {
32
+ readonly ok: true;
33
+ readonly release: string;
34
+ } | {
35
+ readonly ok: false;
36
+ readonly reason: "missing";
37
+ } | {
38
+ readonly ok: false;
39
+ readonly reason: "invalid";
40
+ readonly raw: string;
41
+ };
42
+ /**
43
+ * Resolve the release to upload under: an explicit value, else `FIXBACK_RELEASE`,
44
+ * else the git HEAD SHA — then validate it with the shared rules.
45
+ */
46
+ export declare function resolveRelease(input: ResolveReleaseInput): Promise<ResolveReleaseResult>;
package/dist/release.js CHANGED
@@ -1,22 +1,27 @@
1
1
  /**
2
- * Release validation, mirrored **by value** from the server's shared rules
3
- * (`@fixback/shared`'s `isValidRelease`) the CLI publishes standalone and the
4
- * shared package is workspace-private, so the slice it needs is vendored, the
5
- * same rule the SDK follows. Keep in lock-step with the server: 1–100 visible
6
- * characters, no whitespace / control characters / path separators, not a
7
- * reserved name.
2
+ * Resolving a build's **Release** the one precedence rule shared by the
3
+ * `fixback` CLI and the bundler plugins (`@fixback/vite-plugin`).
4
+ *
5
+ * A release is only useful if the value the build uploads maps under is the value
6
+ * the SDK stamps on its captures (#117, ADR-0024). Two things have to agree for
7
+ * that: the *validation* (which lives once in `@fixback/sdk-core`, shared with the
8
+ * SDKs themselves) and the *precedence* — an explicit value, else `FIXBACK_RELEASE`,
9
+ * else the git HEAD SHA (Sentry's propose-version equivalent). The precedence used
10
+ * to be written out twice, once per caller; it is {@link resolveRelease} now.
8
11
  */
9
- const RELEASE_MAX_LENGTH = 100;
10
- const RESERVED_RELEASES = new Set([".", "..", "latest"]);
11
- /** Trim + validate a release; `undefined` when unusable. */
12
- export function normaliseRelease(value) {
13
- const trimmed = value.trim();
14
- if (trimmed.length === 0 || trimmed.length > RELEASE_MAX_LENGTH)
15
- return undefined;
16
- if (RESERVED_RELEASES.has(trimmed))
17
- return undefined;
18
- // eslint-disable-next-line no-control-regex
19
- if (/[\s/\\\u0000-\u001f\u007f]/.test(trimmed))
20
- return undefined;
21
- return trimmed;
12
+ import { normaliseRelease, RELEASE_MAX_LENGTH } from "@fixback/sdk-core";
13
+ // Release validation is the shared core's (ADR-0028) the SDKs validate what they
14
+ // stamp with the very same function. Re-exported so `@fixback/cli` consumers (and
15
+ // the bundler plugins) keep one import path for it.
16
+ export { normaliseRelease, RELEASE_MAX_LENGTH };
17
+ /**
18
+ * Resolve the release to upload under: an explicit value, else `FIXBACK_RELEASE`,
19
+ * else the git HEAD SHA — then validate it with the shared rules.
20
+ */
21
+ export async function resolveRelease(input) {
22
+ const raw = input.release ?? input.env.FIXBACK_RELEASE ?? (await input.resolveGitSha()) ?? undefined;
23
+ if (!raw)
24
+ return { ok: false, reason: "missing" };
25
+ const release = normaliseRelease(raw);
26
+ return release ? { ok: true, release } : { ok: false, reason: "invalid", raw };
22
27
  }
@@ -0,0 +1,94 @@
1
+ /** One discovered sourcemap: where it is on disk, and the bundle path it maps. */
2
+ export interface DiscoveredMap {
3
+ /** Absolute path of the `.map` file on disk. */
4
+ readonly filePath: string;
5
+ /** The generated bundle's path relative to the build dir, POSIX-separated (no `.map`). */
6
+ readonly bundlePath: string;
7
+ }
8
+ /**
9
+ * Walk a build directory for JS sourcemaps. The bundle path is the map's
10
+ * relative path minus `.map` (a `.js.map` maps the `.js` beside it), normalised
11
+ * to POSIX separators — ready to be joined onto the `~/` prefix.
12
+ */
13
+ export declare function discoverSourcemaps(dir: string): Promise<DiscoveredMap[]>;
14
+ /** Join the `~/` URL prefix and a bundle-relative path into the abstract path. */
15
+ export declare function toArtifactPath(urlPrefix: string, bundlePath: string): string;
16
+ export interface UploadOptions {
17
+ readonly dir: string;
18
+ readonly release: string;
19
+ /** The abstract prefix bundles are served under. Defaults to `~/`. */
20
+ readonly urlPrefix: string;
21
+ readonly apiUrl: string;
22
+ readonly secretKey: string;
23
+ /** List what would upload without any network. */
24
+ readonly dryRun: boolean;
25
+ }
26
+ export interface UploadDeps {
27
+ readonly fetch: typeof globalThis.fetch;
28
+ readonly log: (line: string) => void;
29
+ readonly error: (line: string) => void;
30
+ }
31
+ /** Just the fields `postSourcemap` needs — a structural subset of {@link UploadOptions}. */
32
+ export interface PostSourcemapOptions {
33
+ readonly release: string;
34
+ readonly urlPrefix: string;
35
+ readonly apiUrl: string;
36
+ readonly secretKey: string;
37
+ }
38
+ /** The outcome of one artifact POST. */
39
+ export interface PostSourcemapResult {
40
+ readonly ok: boolean;
41
+ /** The HTTP status, or `0` if the request itself never completed. */
42
+ readonly status: number;
43
+ /** The `~/`-abstract path the artifact was keyed under. */
44
+ readonly artifactPath: string;
45
+ /** A human-readable failure reason; present only when `ok` is false. */
46
+ readonly message?: string;
47
+ }
48
+ /**
49
+ * POST one discovered sourcemap to `POST /api/sourcemaps`, keyed by
50
+ * (release, `~/`-abstract path) and authenticated with the Project's secret key.
51
+ * The single implementation of the ADR-0024 upload wire — shared by the CLI's
52
+ * {@link uploadSourcemaps} loop and the bundler plugins (`@fixback/vite-plugin`),
53
+ * so both key artifacts identically. It never throws on an API error (that is
54
+ * reported through the result); a transport failure still rejects, for the caller
55
+ * to handle.
56
+ */
57
+ export declare function postSourcemap(map: DiscoveredMap, options: PostSourcemapOptions, deps: Pick<UploadDeps, "fetch">): Promise<PostSourcemapResult>;
58
+ /** The outcome of an {@link uploadAll} run. */
59
+ export interface UploadAllResult {
60
+ /** The `.map` files that landed, by absolute path — what a caller may prune. */
61
+ readonly uploaded: string[];
62
+ /** How many maps failed (an API refusal or a transport error). */
63
+ readonly failed: number;
64
+ /** Whether the loop stopped early on a `401` (every remaining map would fail too). */
65
+ readonly stoppedOnAuth: boolean;
66
+ }
67
+ /** Injectable collaborators for {@link uploadAll}. */
68
+ export interface UploadAllDeps {
69
+ readonly fetch: typeof globalThis.fetch;
70
+ /**
71
+ * Called once per map with its outcome, so each caller reports in its own voice
72
+ * (the CLI logs lines and returns an exit code; the plugin warns through Vite's
73
+ * logger). A transport failure arrives here as a `status: 0` result rather than
74
+ * a throw, so one unreachable request never abandons the remaining maps.
75
+ */
76
+ readonly onResult?: (map: DiscoveredMap, result: PostSourcemapResult) => void;
77
+ }
78
+ /**
79
+ * POST every discovered map for one release — the single upload loop behind both
80
+ * `fixback sourcemaps upload` and the bundler plugins, so a plugin upload and a
81
+ * CLI upload keep byte-for-byte identical wire behaviour (ADR-0024).
82
+ *
83
+ * It reports rather than decides: no logging, no exit code, no pruning. A `401`
84
+ * **breaks the loop** — an invalid secret key would fail identically for every
85
+ * remaining map, and hammering the API with the rest tells no one anything new.
86
+ */
87
+ export declare function uploadAll(maps: readonly DiscoveredMap[], options: PostSourcemapOptions, deps: UploadAllDeps): Promise<UploadAllResult>;
88
+ /**
89
+ * Upload every discovered map for one release. Returns a process exit code:
90
+ * `0` when everything (or a clean dry run) succeeded, `1` when nothing was
91
+ * found or any upload failed — CI should fail loudly rather than silently
92
+ * shipping a release without symbolication.
93
+ */
94
+ export declare function uploadSourcemaps(options: UploadOptions, deps: UploadDeps): Promise<number>;
package/dist/upload.js CHANGED
@@ -51,6 +51,74 @@ async function apiErrorMessage(response) {
51
51
  }
52
52
  return response.statusText || `HTTP ${response.status}`;
53
53
  }
54
+ /**
55
+ * POST one discovered sourcemap to `POST /api/sourcemaps`, keyed by
56
+ * (release, `~/`-abstract path) and authenticated with the Project's secret key.
57
+ * The single implementation of the ADR-0024 upload wire — shared by the CLI's
58
+ * {@link uploadSourcemaps} loop and the bundler plugins (`@fixback/vite-plugin`),
59
+ * so both key artifacts identically. It never throws on an API error (that is
60
+ * reported through the result); a transport failure still rejects, for the caller
61
+ * to handle.
62
+ */
63
+ export async function postSourcemap(map, options, deps) {
64
+ const path = toArtifactPath(options.urlPrefix, map.bundlePath);
65
+ const body = new FormData();
66
+ body.set("payload", JSON.stringify({ release: options.release, path }));
67
+ body.set("map", new Blob([await readFile(map.filePath)], { type: "application/json" }), `${map.bundlePath.split("/").pop() ?? "bundle.js"}.map`);
68
+ const response = await deps.fetch(`${options.apiUrl}/api/sourcemaps`, {
69
+ method: "POST",
70
+ headers: { Authorization: `Bearer ${options.secretKey}` },
71
+ body,
72
+ });
73
+ if (response.ok)
74
+ return { ok: true, status: response.status, artifactPath: path };
75
+ return {
76
+ ok: false,
77
+ status: response.status,
78
+ artifactPath: path,
79
+ message: await apiErrorMessage(response),
80
+ };
81
+ }
82
+ /**
83
+ * POST every discovered map for one release — the single upload loop behind both
84
+ * `fixback sourcemaps upload` and the bundler plugins, so a plugin upload and a
85
+ * CLI upload keep byte-for-byte identical wire behaviour (ADR-0024).
86
+ *
87
+ * It reports rather than decides: no logging, no exit code, no pruning. A `401`
88
+ * **breaks the loop** — an invalid secret key would fail identically for every
89
+ * remaining map, and hammering the API with the rest tells no one anything new.
90
+ */
91
+ export async function uploadAll(maps, options, deps) {
92
+ const uploaded = [];
93
+ let failed = 0;
94
+ let stoppedOnAuth = false;
95
+ for (const map of maps) {
96
+ let result;
97
+ try {
98
+ result = await postSourcemap(map, options, { fetch: deps.fetch });
99
+ }
100
+ catch (error) {
101
+ result = {
102
+ ok: false,
103
+ status: 0,
104
+ artifactPath: toArtifactPath(options.urlPrefix, map.bundlePath),
105
+ message: error instanceof Error ? error.message : String(error),
106
+ };
107
+ }
108
+ deps.onResult?.(map, result);
109
+ if (result.ok) {
110
+ uploaded.push(map.filePath);
111
+ continue;
112
+ }
113
+ failed += 1;
114
+ // 401 means every remaining upload will fail identically — stop early.
115
+ if (result.status === 401) {
116
+ stoppedOnAuth = true;
117
+ break;
118
+ }
119
+ }
120
+ return { uploaded, failed, stoppedOnAuth };
121
+ }
54
122
  /**
55
123
  * Upload every discovered map for one release. Returns a process exit code:
56
124
  * `0` when everything (or a clean dry run) succeeded, `1` when nothing was
@@ -64,36 +132,24 @@ export async function uploadSourcemaps(options, deps) {
64
132
  "Enable them in your build (e.g. Vite: build.sourcemap = true) and re-run.");
65
133
  return 1;
66
134
  }
67
- let failed = 0;
68
- for (const map of maps) {
69
- const path = toArtifactPath(options.urlPrefix, map.bundlePath);
70
- if (options.dryRun) {
71
- deps.log(`would upload ${map.bundlePath}.map as ${path}`);
72
- continue;
73
- }
74
- const body = new FormData();
75
- body.set("payload", JSON.stringify({ release: options.release, path }));
76
- body.set("map", new Blob([await readFile(map.filePath)], { type: "application/json" }), `${map.bundlePath.split("/").pop() ?? "bundle.js"}.map`);
77
- const response = await deps.fetch(`${options.apiUrl}/api/sourcemaps`, {
78
- method: "POST",
79
- headers: { Authorization: `Bearer ${options.secretKey}` },
80
- body,
81
- });
82
- if (response.ok) {
83
- deps.log(`uploaded ${map.bundlePath}.map as ${path}`);
84
- }
85
- else {
86
- failed += 1;
87
- deps.error(`failed ${map.bundlePath}.map (${response.status}): ${await apiErrorMessage(response)}`);
88
- // 401 means every remaining upload will fail identically — stop early.
89
- if (response.status === 401)
90
- break;
91
- }
92
- }
93
135
  if (options.dryRun) {
136
+ for (const map of maps) {
137
+ deps.log(`would upload ${map.bundlePath}.map as ${toArtifactPath(options.urlPrefix, map.bundlePath)}`);
138
+ }
94
139
  deps.log(`Dry run: ${maps.length} sourcemap(s) would upload for release ${options.release}.`);
95
140
  return 0;
96
141
  }
142
+ const { failed } = await uploadAll(maps, options, {
143
+ fetch: deps.fetch,
144
+ onResult: (map, result) => {
145
+ if (result.ok) {
146
+ deps.log(`uploaded ${map.bundlePath}.map as ${result.artifactPath}`);
147
+ }
148
+ else {
149
+ deps.error(`failed ${map.bundlePath}.map (${result.status}): ${result.message ?? "upload failed"}`);
150
+ }
151
+ },
152
+ });
97
153
  if (failed > 0) {
98
154
  deps.error(`${failed} upload(s) failed for release ${options.release}.`);
99
155
  return 1;
package/package.json CHANGED
@@ -1,24 +1,54 @@
1
1
  {
2
2
  "name": "@fixback/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Fixback's build-time CLI — upload sourcemaps per release so Issues gain symbolicated code-area pointers.",
5
5
  "license": "MIT",
6
+ "homepage": "https://docs.fixback.dev/sdk/sourcemaps",
6
7
  "type": "module",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
7
16
  "bin": {
8
17
  "fixback": "./dist/cli.js"
9
18
  },
10
19
  "files": [
11
20
  "dist",
12
- "README.md"
21
+ "README.md",
22
+ "LICENSE"
13
23
  ],
14
24
  "engines": {
15
25
  "node": ">=20.12"
16
26
  },
17
27
  "devDependencies": {
18
- "@types/node": "^22.18.13",
28
+ "@types/node": "^22.20.1",
19
29
  "typescript": "5.9.3",
20
30
  "vitest": "^4.1.10"
21
31
  },
32
+ "sideEffects": false,
33
+ "keywords": [
34
+ "fixback",
35
+ "sourcemaps",
36
+ "source-maps",
37
+ "cli",
38
+ "symbolication"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/wemuda/fixback.git",
43
+ "directory": "packages/cli"
44
+ },
45
+ "bugs": "https://docs.fixback.dev",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "dependencies": {
50
+ "@fixback/sdk-core": "0.3.0"
51
+ },
22
52
  "scripts": {
23
53
  "build": "tsc -p tsconfig.build.json",
24
54
  "lint": "eslint .",