Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/commands/proguard/uuid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
60 changes: 38 additions & 22 deletions src/commands/sourcemap/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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=<uuid>` 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)");
Expand All @@ -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[] = [];
Expand All @@ -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} |`
);
}
}
Expand All @@ -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,
},
Expand Down Expand Up @@ -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<ResolveCommandResult>({
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api/releases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/lib/proguard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}-` +
Expand Down
20 changes: 20 additions & 0 deletions test/commands/release/archive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
Loading