Skip to content

Commit 485a65e

Browse files
committed
Merge branch 'main' into antonis/enhanced-fragment-detection
2 parents 29e996f + bf394f1 commit 485a65e

File tree

2 files changed

+132
-29
lines changed

2 files changed

+132
-29
lines changed

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

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import SentryCli from "@sentry/cli";
22
import {
33
closeSession,
44
DEFAULT_ENVIRONMENT,
5-
getDynamicSamplingContextFromSpan,
5+
getTraceData,
66
makeSession,
77
setMeasurement,
8-
spanToTraceHeader,
98
startSpan,
109
} from "@sentry/core";
1110
import * as dotenv from "dotenv";
@@ -23,7 +22,6 @@ import { Options, SentrySDKBuildFlags } from "./types";
2322
import { arrayify, getTurborepoEnvPassthroughWarning, stripQueryAndHashFromPath } from "./utils";
2423
import { glob } from "glob";
2524
import { defaultRewriteSourcesHook, prepareBundleForDebugIdUpload } from "./debug-id-upload";
26-
import { dynamicSamplingContextToSentryBaggageHeader } from "@sentry/utils";
2725

2826
export type SentryBuildPluginManager = {
2927
/**
@@ -67,6 +65,15 @@ export type SentryBuildPluginManager = {
6765
*/
6866
createRelease(): Promise<void>;
6967

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

90+
function createCliInstance(options: NormalizedOptions): SentryCli {
91+
return new SentryCli(null, {
92+
authToken: options.authToken,
93+
org: options.org,
94+
project: options.project,
95+
silent: options.silent,
96+
url: options.url,
97+
vcsRemote: options.release.vcsRemote,
98+
headers: {
99+
...getTraceData(),
100+
},
101+
});
102+
}
103+
83104
/**
84105
* Creates a build plugin manager that exposes primitives for everything that a Sentry JavaScript SDK or build tooling may do during a build.
85106
*
@@ -153,6 +174,9 @@ export function createSentryBuildPluginManager(
153174
createDependencyOnBuildArtifacts: () => () => {
154175
/* noop */
155176
},
177+
injectDebugIds: async () => {
178+
/* noop */
179+
},
156180
};
157181
}
158182

@@ -424,15 +448,7 @@ export function createSentryBuildPluginManager(
424448
createDependencyOnBuildArtifacts();
425449

426450
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-
});
451+
const cliInstance = createCliInstance(options);
436452

437453
if (options.release.create) {
438454
await cliInstance.releases.new(options.release.name);
@@ -502,6 +518,33 @@ export function createSentryBuildPluginManager(
502518
}
503519
},
504520

521+
/*
522+
Injects debug IDs into the build artifacts.
523+
524+
This is a separate function from `uploadSourcemaps` because that needs to run before the sourcemaps are uploaded.
525+
Usually the respective bundler-plugin will take care of this before the sourcemaps are uploaded.
526+
Only use this if you need to manually inject debug IDs into the build artifacts.
527+
*/
528+
async injectDebugIds(buildArtifactPaths: string[]) {
529+
await startSpan(
530+
{ name: "inject-debug-ids", scope: sentryScope, forceTransaction: true },
531+
async () => {
532+
try {
533+
const cliInstance = createCliInstance(options);
534+
await cliInstance.execute(
535+
["sourcemaps", "inject", ...buildArtifactPaths],
536+
options.debug ?? false
537+
);
538+
} catch (e) {
539+
sentryScope.captureException('Error in "debugIdInjectionPlugin" writeBundle hook');
540+
handleRecoverableError(e, false);
541+
} finally {
542+
await safeFlushTelemetry(sentryClient);
543+
}
544+
}
545+
);
546+
},
547+
505548
/**
506549
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
507550
*/
@@ -617,23 +660,8 @@ export function createSentryBuildPluginManager(
617660
setMeasurement("files", files.length, "none", prepBundlesSpan);
618661
setMeasurement("upload_size", uploadSize, "byte", prepBundlesSpan);
619662

620-
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-
},
636-
});
663+
await startSpan({ name: "upload", scope: sentryScope }, async () => {
664+
const cliInstance = createCliInstance(options);
637665

638666
await cliInstance.releases.uploadSourceMaps(
639667
options.release.name ?? "undefined", // unfortunately this needs a value for now but it will not matter since debug IDs overpower releases anyhow

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)