Skip to content

Commit cdece79

Browse files
committed
add globs to legacy sourcemaps
1 parent fc1f540 commit cdece79

File tree

2 files changed

+287
-1
lines changed

2 files changed

+287
-1
lines changed

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,28 @@ export function createSentryBuildPluginManager(
452452
ignore: includeEntry.ignore ? arrayify(includeEntry.ignore) : undefined,
453453
}));
454454

455+
const expandedInclude = await Promise.all(
456+
normalizedInclude.map(async (includeEntry) => {
457+
const expandedPaths = await Promise.all(
458+
includeEntry.paths.map(async (pathPattern) => {
459+
const globResult = await glob(pathPattern, {
460+
absolute: true,
461+
nodir: true,
462+
ignore: includeEntry.ignore,
463+
});
464+
return globResult;
465+
})
466+
);
467+
468+
return {
469+
...includeEntry,
470+
paths: expandedPaths.flat(),
471+
};
472+
})
473+
);
474+
455475
await cliInstance.releases.uploadSourceMaps(options.release.name, {
456-
include: normalizedInclude,
476+
include: expandedInclude,
457477
dist: options.release.dist,
458478
});
459479
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import { createSentryBuildPluginManager } from "../src/build-plugin-manager";
2+
import SentryCli from "@sentry/cli";
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
import * as os from "os";
6+
7+
jest.mock("@sentry/cli");
8+
const MockedSentryCli = SentryCli as jest.MockedClass<typeof SentryCli>;
9+
10+
interface MockSentryCliInstance {
11+
releases: {
12+
new: jest.MockedFunction<(release: string) => Promise<string>>;
13+
uploadSourceMaps: jest.MockedFunction<
14+
(
15+
release: string,
16+
options: {
17+
include: Array<{
18+
paths: string[];
19+
ext: string[];
20+
validate: boolean;
21+
ignore?: string[];
22+
}>;
23+
dist?: string;
24+
}
25+
) => Promise<string>
26+
>;
27+
setCommits: jest.MockedFunction<(release: string, options: unknown) => Promise<string>>;
28+
finalize: jest.MockedFunction<(release: string, options?: unknown) => Promise<string>>;
29+
newDeploy: jest.MockedFunction<(release: string, options: unknown) => Promise<string>>;
30+
proposeVersion: jest.MockedFunction<() => Promise<string>>;
31+
listDeploys: jest.MockedFunction<(release: string, options?: unknown) => Promise<string>>;
32+
execute: jest.MockedFunction<(args: string[], live?: boolean) => Promise<string>>;
33+
};
34+
}
35+
36+
describe("uploadLegacySourcemaps glob expansion", () => {
37+
let tempDir: string;
38+
let mockCliInstance: MockSentryCliInstance;
39+
40+
beforeEach(async () => {
41+
jest.clearAllMocks();
42+
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "sentry-test-"));
43+
44+
mockCliInstance = {
45+
releases: {
46+
new: jest.fn().mockResolvedValue(""),
47+
uploadSourceMaps: jest.fn().mockResolvedValue(""),
48+
setCommits: jest.fn().mockResolvedValue(""),
49+
finalize: jest.fn().mockResolvedValue(""),
50+
newDeploy: jest.fn().mockResolvedValue(""),
51+
proposeVersion: jest.fn().mockResolvedValue(""),
52+
listDeploys: jest.fn().mockResolvedValue(""),
53+
execute: jest.fn().mockResolvedValue(""),
54+
},
55+
};
56+
57+
MockedSentryCli.mockImplementation(() => mockCliInstance as unknown as SentryCli);
58+
59+
const testFiles = ["dist/beep.js", "dist/boop.js"];
60+
61+
for (const file of testFiles) {
62+
const fullPath = path.join(tempDir, file);
63+
await fs.promises.mkdir(path.dirname(fullPath), { recursive: true });
64+
await fs.promises.writeFile(fullPath, "test content");
65+
}
66+
});
67+
68+
afterEach(async () => {
69+
await fs.promises.rm(tempDir, { recursive: true, force: true });
70+
});
71+
72+
it("should expand a basic glob pattern", async () => {
73+
const buildPluginManager = createSentryBuildPluginManager(
74+
{
75+
org: "test-org",
76+
project: "test-project",
77+
authToken: "test-token",
78+
release: {
79+
name: "test-release",
80+
uploadLegacySourcemaps: path.join(tempDir, "dist/*.js"),
81+
},
82+
},
83+
{
84+
buildTool: "webpack",
85+
loggerPrefix: "[sentry-webpack-plugin]",
86+
}
87+
);
88+
89+
await buildPluginManager.createRelease();
90+
91+
expect(mockCliInstance.releases.uploadSourceMaps).toHaveBeenCalledTimes(1);
92+
93+
const uploadCalls = mockCliInstance.releases.uploadSourceMaps.mock.calls;
94+
expect(uploadCalls).toHaveLength(1);
95+
96+
const uploadCall = uploadCalls[0];
97+
expect(uploadCall).toHaveLength(2);
98+
99+
// TS yelling at me
100+
if (!uploadCall) {
101+
throw new Error("uploadCall should be defined");
102+
}
103+
104+
const uploadOptions = uploadCall[1];
105+
const firstInclude = uploadOptions.include[0];
106+
107+
if (!firstInclude) {
108+
throw new Error("firstInclude should be defined");
109+
}
110+
111+
const includePaths = firstInclude.paths;
112+
expect(includePaths).toHaveLength(2);
113+
expect(includePaths.some((p: string) => p.includes("beep.js"))).toBe(true);
114+
expect(includePaths.some((p: string) => p.includes("boop.js"))).toBe(true);
115+
});
116+
117+
it("should expand multiple glob patterns and mixed path types", async () => {
118+
const additionalFiles = [
119+
"src/app.js",
120+
"src/app.js.map",
121+
"dist/vendor.bundle",
122+
"dist/vendor.bundle.map",
123+
"assets/styles.css",
124+
"static/image.png",
125+
];
126+
127+
for (const file of additionalFiles) {
128+
const fullPath = path.join(tempDir, file);
129+
await fs.promises.mkdir(path.dirname(fullPath), { recursive: true });
130+
await fs.promises.writeFile(fullPath, "additional content");
131+
}
132+
133+
const buildPluginManager = createSentryBuildPluginManager(
134+
{
135+
org: "test-org",
136+
project: "test-project",
137+
authToken: "test-token",
138+
release: {
139+
name: "test-release",
140+
uploadLegacySourcemaps: [
141+
path.join(tempDir, "dist/*"),
142+
path.join(tempDir, "src", "app.js"),
143+
path.join(tempDir, "assets/*"),
144+
],
145+
},
146+
},
147+
{
148+
buildTool: "webpack",
149+
loggerPrefix: "[sentry-webpack-plugin]",
150+
}
151+
);
152+
153+
await buildPluginManager.createRelease();
154+
155+
expect(mockCliInstance.releases.uploadSourceMaps).toHaveBeenCalledTimes(1);
156+
157+
const uploadCalls = mockCliInstance.releases.uploadSourceMaps.mock.calls;
158+
expect(uploadCalls).toHaveLength(1);
159+
160+
const uploadCall = uploadCalls[0];
161+
expect(uploadCall).toHaveLength(2);
162+
163+
if (!uploadCall) {
164+
throw new Error("uploadCall should be defined");
165+
}
166+
167+
const uploadOptions = uploadCall[1];
168+
expect(uploadOptions.include).toHaveLength(3);
169+
170+
// Should match all files in dist/
171+
const firstInclude = uploadOptions.include[0];
172+
if (!firstInclude) {
173+
throw new Error("firstInclude should be defined");
174+
}
175+
176+
const firstIncludePaths = firstInclude.paths;
177+
expect(firstIncludePaths).toHaveLength(4); // beep.js, boop.js, vendor.bundle, vendor.bundle.map
178+
expect(firstIncludePaths.some((p: string) => p.includes("beep.js"))).toBe(true);
179+
expect(firstIncludePaths.some((p: string) => p.includes("boop.js"))).toBe(true);
180+
expect(firstIncludePaths.some((p: string) => p.includes("vendor.bundle"))).toBe(true);
181+
expect(firstIncludePaths.some((p: string) => p.includes("vendor.bundle.map"))).toBe(true);
182+
183+
// Should match the specific file
184+
const secondInclude = uploadOptions.include[1];
185+
if (!secondInclude) {
186+
throw new Error("secondInclude should be defined");
187+
}
188+
189+
const secondIncludePaths = secondInclude.paths;
190+
expect(secondIncludePaths).toHaveLength(1);
191+
expect(secondIncludePaths[0]).toContain("app.js");
192+
193+
// Should match files in assets/
194+
const thirdInclude = uploadOptions.include[2];
195+
if (!thirdInclude) {
196+
throw new Error("thirdInclude should be defined");
197+
}
198+
199+
const thirdIncludePaths = thirdInclude.paths;
200+
expect(thirdIncludePaths).toHaveLength(1);
201+
expect(thirdIncludePaths[0]).toContain("styles.css");
202+
203+
// Should have the correct default extension filter
204+
expect(firstInclude.ext).toEqual([".js", ".map", ".jsbundle", ".bundle"]);
205+
expect(secondInclude.ext).toEqual([".js", ".map", ".jsbundle", ".bundle"]);
206+
expect(thirdInclude.ext).toEqual([".js", ".map", ".jsbundle", ".bundle"]);
207+
});
208+
209+
it("should apply custom file extensions when specified", async () => {
210+
const testFiles = ["dist/main.js", "dist/main.js.map", "dist/styles.css", "dist/data.json"];
211+
212+
for (const file of testFiles) {
213+
const fullPath = path.join(tempDir, file);
214+
await fs.promises.mkdir(path.dirname(fullPath), { recursive: true });
215+
await fs.promises.writeFile(fullPath, "test content");
216+
}
217+
218+
const buildPluginManager = createSentryBuildPluginManager(
219+
{
220+
org: "test-org",
221+
project: "test-project",
222+
authToken: "test-token",
223+
release: {
224+
name: "test-release",
225+
uploadLegacySourcemaps: {
226+
paths: [path.join(tempDir, "dist/*")],
227+
ext: ["js", "css"],
228+
},
229+
},
230+
},
231+
{
232+
buildTool: "webpack",
233+
loggerPrefix: "[sentry-webpack-plugin]",
234+
}
235+
);
236+
237+
await buildPluginManager.createRelease();
238+
239+
expect(mockCliInstance.releases.uploadSourceMaps).toHaveBeenCalledTimes(1);
240+
241+
const uploadCalls = mockCliInstance.releases.uploadSourceMaps.mock.calls;
242+
const uploadCall = uploadCalls[0];
243+
244+
if (!uploadCall) {
245+
throw new Error("uploadCall should be defined");
246+
}
247+
248+
const uploadOptions = uploadCall[1];
249+
const firstInclude = uploadOptions.include[0];
250+
251+
if (!firstInclude) {
252+
throw new Error("firstInclude should be defined");
253+
}
254+
255+
const includePaths = firstInclude.paths;
256+
257+
expect(includePaths).toHaveLength(6);
258+
expect(includePaths.some((p: string) => p.includes("main.js"))).toBe(true);
259+
expect(includePaths.some((p: string) => p.includes("main.js.map"))).toBe(true);
260+
expect(includePaths.some((p: string) => p.includes("styles.css"))).toBe(true);
261+
expect(includePaths.some((p: string) => p.includes("data.json"))).toBe(true);
262+
expect(includePaths.some((p: string) => p.includes("beep.js"))).toBe(true);
263+
expect(includePaths.some((p: string) => p.includes("boop.js"))).toBe(true);
264+
expect(firstInclude.ext).toEqual([".js", ".css"]); // Custom extensions added
265+
});
266+
});

0 commit comments

Comments
 (0)