Skip to content

Commit 2b2e140

Browse files
committed
add injectDebugIds
1 parent c15f9df commit 2b2e140

File tree

2 files changed

+139
-24
lines changed

2 files changed

+139
-24
lines changed

packages/bundler-plugin-core/src/build-plugin-manager.ts

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ export type SentryBuildPluginManager = {
6767
*/
6868
createRelease(): Promise<void>;
6969

70+
/**
71+
* Injects debug IDs into the build artifacts.
72+
*
73+
* This is a separate function from `uploadSourcemaps` because that needs to run before the sourcemaps are uploaded.
74+
* Usually the respective bundler-plugin will take care of this before the sourcemaps are uploaded.
75+
* Only use this if you need to manually inject debug IDs into the build artifacts.
76+
*/
77+
injectDebugIds(buildArtifactPaths: string[]): Promise<void>;
78+
7079
/**
7180
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
7281
*/
@@ -80,6 +89,24 @@ export type SentryBuildPluginManager = {
8089
createDependencyOnBuildArtifacts: () => () => void;
8190
};
8291

92+
function createCliInstance(
93+
options: NormalizedOptions,
94+
additionalHeaders: Record<string, string> = {}
95+
): SentryCli {
96+
return new SentryCli(null, {
97+
authToken: options.authToken,
98+
org: options.org,
99+
project: options.project,
100+
silent: options.silent,
101+
url: options.url,
102+
vcsRemote: options.release.vcsRemote,
103+
headers: {
104+
...options.headers,
105+
...additionalHeaders,
106+
},
107+
});
108+
}
109+
83110
/**
84111
* Creates a build plugin manager that exposes primitives for everything that a Sentry JavaScript SDK or build tooling may do during a build.
85112
*
@@ -153,6 +180,9 @@ export function createSentryBuildPluginManager(
153180
createDependencyOnBuildArtifacts: () => () => {
154181
/* noop */
155182
},
183+
injectDebugIds: async () => {
184+
/* noop */
185+
},
156186
};
157187
}
158188

@@ -424,15 +454,7 @@ export function createSentryBuildPluginManager(
424454
createDependencyOnBuildArtifacts();
425455

426456
try {
427-
const cliInstance = new SentryCli(null, {
428-
authToken: options.authToken,
429-
org: options.org,
430-
project: options.project,
431-
silent: options.silent,
432-
url: options.url,
433-
vcsRemote: options.release.vcsRemote,
434-
headers: options.headers,
435-
});
457+
const cliInstance = createCliInstance(options);
436458

437459
if (options.release.create) {
438460
await cliInstance.releases.new(options.release.name);
@@ -502,6 +524,33 @@ export function createSentryBuildPluginManager(
502524
}
503525
},
504526

527+
/*
528+
Injects debug IDs into the build artifacts.
529+
530+
This is a separate function from `uploadSourcemaps` because that needs to run before the sourcemaps are uploaded.
531+
Usually the respective bundler-plugin will take care of this before the sourcemaps are uploaded.
532+
Only use this if you need to manually inject debug IDs into the build artifacts.
533+
*/
534+
async injectDebugIds(buildArtifactPaths: string[]) {
535+
await startSpan(
536+
{ name: "inject-debug-ids", scope: sentryScope, forceTransaction: true },
537+
async () => {
538+
try {
539+
const cliInstance = createCliInstance(options);
540+
await cliInstance.execute(
541+
["sourcemaps", "inject", ...buildArtifactPaths],
542+
options.debug ?? false
543+
);
544+
} catch (e) {
545+
sentryScope.captureException('Error in "debugIdInjectionPlugin" writeBundle hook');
546+
handleRecoverableError(e, false);
547+
} finally {
548+
await safeFlushTelemetry(sentryClient);
549+
}
550+
}
551+
);
552+
},
553+
505554
/**
506555
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
507556
*/
@@ -618,21 +667,12 @@ export function createSentryBuildPluginManager(
618667
setMeasurement("upload_size", uploadSize, "byte", prepBundlesSpan);
619668

620669
await startSpan({ name: "upload", scope: sentryScope }, async (uploadSpan) => {
621-
const cliInstance = new SentryCli(null, {
622-
authToken: options.authToken,
623-
org: options.org,
624-
project: options.project,
625-
silent: options.silent,
626-
url: options.url,
627-
vcsRemote: options.release.vcsRemote,
628-
headers: {
629-
"sentry-trace": spanToTraceHeader(uploadSpan),
630-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
631-
baggage: dynamicSamplingContextToSentryBaggageHeader(
632-
getDynamicSamplingContextFromSpan(uploadSpan)
633-
)!,
634-
...options.headers,
635-
},
670+
const cliInstance = createCliInstance(options, {
671+
"sentry-trace": spanToTraceHeader(uploadSpan),
672+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
673+
baggage: dynamicSamplingContextToSentryBaggageHeader(
674+
getDynamicSamplingContextFromSpan(uploadSpan)
675+
)!,
636676
});
637677

638678
await cliInstance.releases.uploadSourceMaps(

packages/bundler-plugin-core/test/build-plugin-manager.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
import { createSentryBuildPluginManager } from "../src/build-plugin-manager";
22

3+
const mockCliExecute = jest.fn();
4+
jest.mock("@sentry/cli", () => {
5+
return jest.fn().mockImplementation(() => ({
6+
execute: mockCliExecute,
7+
}));
8+
});
9+
10+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
11+
jest.mock("../src/sentry/telemetry", () => ({
12+
...jest.requireActual("../src/sentry/telemetry"),
13+
safeFlushTelemetry: jest.fn(),
14+
}));
15+
16+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
17+
jest.mock("@sentry/core", () => ({
18+
...jest.requireActual("@sentry/core"),
19+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return
20+
startSpan: jest.fn((options, callback) => callback()),
21+
}));
22+
323
describe("createSentryBuildPluginManager", () => {
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
428
describe("when disabled", () => {
529
it("initializes a no-op build plugin manager", () => {
630
const buildPluginManager = createSentryBuildPluginManager(
@@ -48,4 +72,55 @@ describe("createSentryBuildPluginManager", () => {
4872
expect(errorSpy).not.toHaveBeenCalled();
4973
});
5074
});
75+
76+
describe("injectDebugIds", () => {
77+
it("should call CLI with correct sourcemaps inject command", async () => {
78+
mockCliExecute.mockResolvedValue(undefined);
79+
80+
const buildPluginManager = createSentryBuildPluginManager(
81+
{
82+
authToken: "test-token",
83+
org: "test-org",
84+
project: "test-project",
85+
},
86+
{
87+
buildTool: "webpack",
88+
loggerPrefix: "[sentry-webpack-plugin]",
89+
}
90+
);
91+
92+
const buildArtifactPaths = ["/path/to/1", "/path/to/2"];
93+
await buildPluginManager.injectDebugIds(buildArtifactPaths);
94+
95+
expect(mockCliExecute).toHaveBeenCalledWith(
96+
["sourcemaps", "inject", "/path/to/1", "/path/to/2"],
97+
false
98+
);
99+
});
100+
101+
it("should pass debug flag when options.debug is true", async () => {
102+
mockCliExecute.mockResolvedValue(undefined);
103+
104+
const buildPluginManager = createSentryBuildPluginManager(
105+
{
106+
authToken: "test-token",
107+
org: "test-org",
108+
project: "test-project",
109+
debug: true,
110+
},
111+
{
112+
buildTool: "webpack",
113+
loggerPrefix: "[sentry-webpack-plugin]",
114+
}
115+
);
116+
117+
const buildArtifactPaths = ["/path/to/bundle"];
118+
await buildPluginManager.injectDebugIds(buildArtifactPaths);
119+
120+
expect(mockCliExecute).toHaveBeenCalledWith(
121+
["sourcemaps", "inject", "/path/to/bundle"],
122+
true
123+
);
124+
});
125+
});
51126
});

0 commit comments

Comments
 (0)