diff --git a/src/commands/proguard/uuid.ts b/src/commands/proguard/uuid.ts index dce3b6a0c..b45dc3226 100644 --- a/src/commands/proguard/uuid.ts +++ b/src/commands/proguard/uuid.ts @@ -32,6 +32,8 @@ export const uuidCommand = buildCommand({ " sentry proguard uuid ./app/build/outputs/mapping/release/mapping.txt\n" + " sentry proguard uuid mapping.txt --json", }, + // Purely local file operation — no Sentry API calls, no auth needed. + auth: false, output: { // Print only the bare UUID for human/plain output (scriptable, matches // legacy `sentry-cli proguard uuid`). JSON output includes the path. diff --git a/src/commands/sourcemap/resolve.ts b/src/commands/sourcemap/resolve.ts index 8cc6d6b21..8afccd845 100644 --- a/src/commands/sourcemap/resolve.ts +++ b/src/commands/sourcemap/resolve.ts @@ -21,37 +21,43 @@ import { assertDirectoryReadable, buildIgnoreMatcher, resolveDirectorySourcemaps, - type SourcemapResolution, } from "../../lib/sourcemap/inject.js"; +/** Per-file resolution entry for the command output. Uses relative paths only. */ +type ResolveFileEntry = { + /** JS path relative to the scanned directory. */ + path: string; + /** Companion sourcemap path relative to the scanned directory, if any. */ + mapPath?: string; + /** Raw `sourceMappingURL` directive value, if any. */ + sourceMappingUrl?: string; + /** True when the sourceMappingURL is an inline data: URL. */ + inline: boolean; + /** True when the sourceMappingURL is a remote http(s) reference. */ + remote: boolean; + /** The embedded `//# debugId=` value, if any. */ + debugId?: string; +}; + /** Result type for the resolve command output. */ type ResolveCommandResult = { /** Total JS files scanned. */ total: number; - /** Files with a resolvable companion sourcemap. */ + /** Files with a resolvable sourcemap (companion file, inline, or remote). */ resolved: number; /** Files with an injected debug ID. */ withDebugId: number; - /** Per-file resolution details (paths relative to the scanned dir). */ - files: Array< - SourcemapResolution & { - /** JS path relative to the scanned directory (for display). */ - relPath: string; - /** Map path relative to the scanned directory, if any. */ - relMapPath?: string; - } - >; + /** Per-file resolution details (relative paths only). */ + files: ResolveFileEntry[]; }; /** * Describe how the sourcemap resolved, for the human table's "Source map" * column. */ -function describeMapStatus( - file: ResolveCommandResult["files"][number] -): string { - if (file.relMapPath) { - return escapeMarkdownCell(file.relMapPath); +function describeMapStatus(file: ResolveFileEntry): string { + if (file.mapPath) { + return escapeMarkdownCell(file.mapPath); } if (file.inline) { return colorTag("muted", "inline (data: URL)"); @@ -62,6 +68,11 @@ function describeMapStatus( return colorTag("red", "not found"); } +/** True when a file has any kind of sourcemap (companion, inline, or remote). */ +function hasSourcemap(file: ResolveFileEntry): boolean { + return !!file.mapPath || file.inline || file.remote; +} + /** Format human-readable output for resolve results. */ function formatResolveResult(data: ResolveCommandResult): string { const lines: string[] = []; @@ -82,7 +93,7 @@ function formatResolveResult(data: ResolveCommandResult): string { ? escapeMarkdownCell(file.debugId) : colorTag("muted", "—"); lines.push( - `| ${escapeMarkdownCell(file.relPath)} | ${describeMapStatus(file)} | ${debugId} |` + `| ${escapeMarkdownCell(file.path)} | ${describeMapStatus(file)} | ${debugId} |` ); } } @@ -105,6 +116,8 @@ export const resolveCommand = buildCommand({ " sentry sourcemap resolve ./build --ext .js,.mjs\n" + " sentry sourcemap resolve ./out --json", }, + // Purely local file operation — no Sentry API calls, no auth needed. + auth: false, output: { human: formatResolveResult, }, @@ -172,13 +185,16 @@ export const resolveCommand = buildCommand({ ); const absDir = resolvePath(dir); - const files = resolutions.map((r) => ({ - ...r, - relPath: relative(absDir, r.jsPath) || r.jsPath, - relMapPath: r.mapPath ? relative(absDir, r.mapPath) : undefined, + const files: ResolveFileEntry[] = resolutions.map((r) => ({ + path: relative(absDir, r.jsPath) || r.jsPath, + mapPath: r.mapPath ? relative(absDir, r.mapPath) : undefined, + sourceMappingUrl: r.sourceMappingUrl, + inline: r.inline, + remote: r.remote, + debugId: r.debugId, })); - const resolved = files.filter((f) => f.mapPath).length; + const resolved = files.filter(hasSourcemap).length; const withDebugId = files.filter((f) => f.debugId).length; yield new CommandOutput({ diff --git a/src/lib/api/releases.ts b/src/lib/api/releases.ts index 10b5fd887..6c17e3451 100644 --- a/src/lib/api/releases.ts +++ b/src/lib/api/releases.ts @@ -252,7 +252,7 @@ export async function updateRelease( url?: string; dateReleased?: string; /** Release lifecycle status: "open" (active) or "archived". */ - status?: string; + status?: "open" | "archived"; commits?: Array<{ id: string; repository?: string; diff --git a/src/lib/proguard.ts b/src/lib/proguard.ts index d40c72315..a09d39678 100644 --- a/src/lib/proguard.ts +++ b/src/lib/proguard.ts @@ -61,6 +61,7 @@ function uuidV5(name: Buffer, namespace: string): string { // Version nibble (5) at hex position 12; variant nibble at position 16, // chosen deterministically from the original digit so the result is stable. + // ?? "0" satisfies noUncheckedIndexedAccess — hex[16] is always present (32 chars). const variant = VARIANT_NIBBLE[Number.parseInt(hex[16] ?? "0", 16) % 4]; return ( `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-` + diff --git a/test/commands/release/archive.test.ts b/test/commands/release/archive.test.ts index cf63c9608..9cb513d8f 100644 --- a/test/commands/release/archive.test.ts +++ b/test/commands/release/archive.test.ts @@ -168,4 +168,24 @@ describe("release restore", () => { expect(updateReleaseSpy).not.toHaveBeenCalled(); }); + + test("throws when no version provided", async () => { + const { context } = createMockContext(); + const func = await restoreCommand.loader(); + + await expect( + func.call(context, { "dry-run": false, json: false }) + ).rejects.toThrow("Release version"); + }); + + test("throws when org cannot be resolved", async () => { + resolveOrgSpy.mockResolvedValue(null); + + const { context } = createMockContext(); + const func = await restoreCommand.loader(); + + await expect( + func.call(context, { "dry-run": false, json: false }, "1.0.0") + ).rejects.toThrow("organization"); + }); });