Skip to content

Commit 707c8c9

Browse files
Luca ForstnerLms24
andauthored
feat: Auto detect build artifacts (#257)
Co-authored-by: Lukas Stracke <[email protected]>
1 parent decb591 commit 707c8c9

File tree

17 files changed

+191
-195
lines changed

17 files changed

+191
-195
lines changed

packages/bundler-plugin-core/src/plugins/debug-id-upload.ts renamed to packages/bundler-plugin-core/src/debug-id-upload.ts

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { glob } from "glob";
33
import os from "os";
44
import path from "path";
55
import * as util from "util";
6-
import { UnpluginOptions } from "unplugin";
7-
import { Logger } from "../sentry/logger";
6+
import { Logger } from "./sentry/logger";
87
import { promisify } from "util";
98
import { Hub, NodeClient } from "@sentry/node";
109
import SentryCli from "@sentry/cli";
@@ -15,7 +14,7 @@ interface RewriteSourcesHook {
1514

1615
interface DebugIdUploadPluginOptions {
1716
logger: Logger;
18-
assets: string | string[];
17+
assets?: string | string[];
1918
ignore?: string | string[];
2019
releaseName?: string;
2120
dist?: string;
@@ -35,7 +34,7 @@ interface DebugIdUploadPluginOptions {
3534
};
3635
}
3736

38-
export function debugIdUploadPlugin({
37+
export function createDebugIdUploadFunction({
3938
assets,
4039
ignore,
4140
logger,
@@ -47,34 +46,51 @@ export function debugIdUploadPlugin({
4746
sentryCliOptions,
4847
rewriteSourcesHook,
4948
deleteFilesAfterUpload,
50-
}: DebugIdUploadPluginOptions): UnpluginOptions {
51-
return {
52-
name: "sentry-debug-id-upload-plugin",
53-
async writeBundle() {
54-
let folderToCleanUp: string | undefined;
49+
}: DebugIdUploadPluginOptions) {
50+
return async (buildArtifactPaths: string[]) => {
51+
let folderToCleanUp: string | undefined;
5552

56-
const cliInstance = new SentryCli(null, sentryCliOptions);
53+
const cliInstance = new SentryCli(null, sentryCliOptions);
5754

58-
try {
59-
const tmpUploadFolder = await fs.promises.mkdtemp(
60-
path.join(os.tmpdir(), "sentry-bundler-plugin-upload-")
61-
);
55+
try {
56+
const tmpUploadFolder = await fs.promises.mkdtemp(
57+
path.join(os.tmpdir(), "sentry-bundler-plugin-upload-")
58+
);
6259

63-
folderToCleanUp = tmpUploadFolder;
60+
folderToCleanUp = tmpUploadFolder;
6461

65-
const debugIdChunkFilePaths = (
66-
await glob(assets, {
67-
absolute: true,
68-
nodir: true,
69-
ignore: ignore,
70-
})
71-
).filter(
72-
(debugIdChunkFilePath) =>
73-
debugIdChunkFilePath.endsWith(".js") ||
74-
debugIdChunkFilePath.endsWith(".mjs") ||
75-
debugIdChunkFilePath.endsWith(".cjs")
62+
let globAssets;
63+
if (assets) {
64+
globAssets = assets;
65+
} else {
66+
logger.debug(
67+
"No `sourcemaps.assets` option provided, falling back to uploading detected build artifacts."
7668
);
69+
globAssets = buildArtifactPaths;
70+
}
7771

72+
const debugIdChunkFilePaths = (
73+
await glob(globAssets, {
74+
absolute: true,
75+
nodir: true,
76+
ignore: ignore,
77+
})
78+
).filter(
79+
(debugIdChunkFilePath) =>
80+
debugIdChunkFilePath.endsWith(".js") ||
81+
debugIdChunkFilePath.endsWith(".mjs") ||
82+
debugIdChunkFilePath.endsWith(".cjs")
83+
);
84+
85+
if (Array.isArray(assets) && assets.length === 0) {
86+
logger.debug(
87+
"Empty `sourcemaps.assets` option provided. Will not upload sourcemaps with debug ID."
88+
);
89+
} else if (debugIdChunkFilePaths.length === 0) {
90+
logger.warn(
91+
"Didn't find any matching sources for debug ID upload. Please check the `sourcemaps.assets` option."
92+
);
93+
} else {
7894
await Promise.all(
7995
debugIdChunkFilePaths.map(async (chunkFilePath, chunkIndex): Promise<void> => {
8096
await prepareBundleForDebugIdUpload(
@@ -100,33 +116,33 @@ export function debugIdUploadPlugin({
100116
useArtifactBundle: true,
101117
}
102118
);
119+
}
103120

104-
if (deleteFilesAfterUpload) {
105-
const filePathsToDelete = await glob(deleteFilesAfterUpload, {
106-
absolute: true,
107-
nodir: true,
108-
});
121+
if (deleteFilesAfterUpload) {
122+
const filePathsToDelete = await glob(deleteFilesAfterUpload, {
123+
absolute: true,
124+
nodir: true,
125+
});
109126

110-
filePathsToDelete.forEach((filePathToDelete) => {
111-
logger.debug(`Deleting asset after upload: ${filePathToDelete}`);
112-
});
127+
filePathsToDelete.forEach((filePathToDelete) => {
128+
logger.debug(`Deleting asset after upload: ${filePathToDelete}`);
129+
});
113130

114-
await Promise.all(
115-
filePathsToDelete.map((filePathToDelete) =>
116-
fs.promises.rm(filePathToDelete, { force: true })
117-
)
118-
);
119-
}
120-
} catch (e) {
121-
sentryHub.captureException('Error in "debugIdUploadPlugin" writeBundle hook');
122-
await sentryClient.flush();
123-
handleRecoverableError(e);
124-
} finally {
125-
if (folderToCleanUp) {
126-
void fs.promises.rm(folderToCleanUp, { recursive: true, force: true });
127-
}
131+
await Promise.all(
132+
filePathsToDelete.map((filePathToDelete) =>
133+
fs.promises.rm(filePathToDelete, { force: true })
134+
)
135+
);
136+
}
137+
} catch (e) {
138+
sentryHub.captureException('Error in "debugIdUploadPlugin" writeBundle hook');
139+
await sentryClient.flush();
140+
handleRecoverableError(e);
141+
} finally {
142+
if (folderToCleanUp) {
143+
void fs.promises.rm(folderToCleanUp, { recursive: true, force: true });
128144
}
129-
},
145+
}
130146
};
131147
}
132148

packages/bundler-plugin-core/src/index.ts

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import SentryCli from "@sentry/cli";
2-
import fs from "fs";
2+
import * as fs from "fs";
3+
import * as path from "path";
34
import MagicString from "magic-string";
45
import { createUnplugin, UnpluginOptions } from "unplugin";
56
import { normalizeUserOptions, validateOptions } from "./options-mapping";
6-
import { debugIdUploadPlugin } from "./plugins/debug-id-upload";
7+
import { createDebugIdUploadFunction } from "./debug-id-upload";
78
import { releaseManagementPlugin } from "./plugins/release-management";
89
import { telemetryPlugin } from "./plugins/telemetry";
910
import { createLogger } from "./sentry/logger";
@@ -21,6 +22,7 @@ import {
2122
interface SentryUnpluginFactoryOptions {
2223
releaseInjectionPlugin: (injectionCode: string) => UnpluginOptions;
2324
debugIdInjectionPlugin: () => UnpluginOptions;
25+
debugIdUploadPlugin: (upload: (buildArtifacts: string[]) => Promise<void>) => UnpluginOptions;
2426
}
2527

2628
/**
@@ -53,6 +55,7 @@ interface SentryUnpluginFactoryOptions {
5355
export function sentryUnpluginFactory({
5456
releaseInjectionPlugin,
5557
debugIdInjectionPlugin,
58+
debugIdUploadPlugin,
5659
}: SentryUnpluginFactoryOptions) {
5760
return createUnplugin<Options, true>((userOptions, unpluginMetaContext) => {
5861
const options = normalizeUserOptions(userOptions);
@@ -180,35 +183,31 @@ export function sentryUnpluginFactory({
180183
);
181184
}
182185

183-
if (options.sourcemaps) {
184-
if (!options.authToken) {
185-
logger.warn(
186-
"No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/"
187-
);
188-
} else if (!options.org) {
189-
logger.warn(
190-
"No org provided. Will not upload source maps. Please set the `org` option to your Sentry organization slug."
191-
);
192-
} else if (!options.project) {
193-
logger.warn(
194-
"No project provided. Will not upload source maps. Please set the `project` option to your Sentry project slug."
195-
);
196-
} else if (!options.sourcemaps.assets) {
197-
logger.warn(
198-
"No assets defined. Will not upload source maps. Please provide set the `assets` option to your build-output folder."
199-
);
200-
} else {
201-
plugins.push(debugIdInjectionPlugin());
202-
plugins.push(
203-
debugIdUploadPlugin({
204-
assets: options.sourcemaps.assets,
205-
ignore: options.sourcemaps.ignore,
206-
deleteFilesAfterUpload: options.sourcemaps.deleteFilesAfterUpload,
186+
if (!options.authToken) {
187+
logger.warn(
188+
"No auth token provided. Will not upload source maps. Please set the `authToken` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/"
189+
);
190+
} else if (!options.org) {
191+
logger.warn(
192+
"No org provided. Will not upload source maps. Please set the `org` option to your Sentry organization slug."
193+
);
194+
} else if (!options.project) {
195+
logger.warn(
196+
"No project provided. Will not upload source maps. Please set the `project` option to your Sentry project slug."
197+
);
198+
} else {
199+
plugins.push(debugIdInjectionPlugin());
200+
plugins.push(
201+
debugIdUploadPlugin(
202+
createDebugIdUploadFunction({
203+
assets: options.sourcemaps?.assets,
204+
ignore: options.sourcemaps?.ignore,
205+
deleteFilesAfterUpload: options.sourcemaps?.deleteFilesAfterUpload,
207206
dist: options.release.dist,
208207
releaseName: options.release.name,
209208
logger: logger,
210209
handleRecoverableError: handleRecoverableError,
211-
rewriteSourcesHook: options.sourcemaps.rewriteSources,
210+
rewriteSourcesHook: options.sourcemaps?.rewriteSources,
212211
sentryHub,
213212
sentryClient,
214213
sentryCliOptions: {
@@ -221,8 +220,8 @@ export function sentryUnpluginFactory({
221220
headers: options.headers,
222221
},
223222
})
224-
);
225-
}
223+
)
224+
);
226225
}
227226

228227
return plugins;
@@ -341,6 +340,28 @@ export function createRollupDebugIdInjectionHooks() {
341340
};
342341
}
343342

343+
export function createRollupDebugIdUploadHooks(
344+
upload: (buildArtifacts: string[]) => Promise<void>
345+
) {
346+
return {
347+
async writeBundle(
348+
outputOptions: { dir?: string; file?: string },
349+
bundle: { [fileName: string]: unknown }
350+
) {
351+
if (outputOptions.dir) {
352+
const outputDir = outputOptions.dir;
353+
const buildArtifacts = Object.keys(bundle).map((asset) => path.join(outputDir, asset));
354+
await upload(buildArtifacts);
355+
} else if (outputOptions.file) {
356+
await upload([outputOptions.file]);
357+
} else {
358+
const buildArtifacts = Object.keys(bundle).map((asset) => path.join(path.resolve(), asset));
359+
await upload(buildArtifacts);
360+
}
361+
},
362+
};
363+
}
364+
344365
export function getDebugIdSnippet(debugId: string): string {
345366
return `;!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(e){}}();`;
346367
}

packages/bundler-plugin-core/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,13 @@ export interface Options {
9393
/**
9494
* A glob or an array of globs that specifies the build artifacts that should be uploaded to Sentry.
9595
*
96+
* If this option is not specified, the plugin will try to upload all JavaScript files and source map files that are created during build.
97+
*
9698
* The globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob)
9799
*
98100
* Use the `debug` option to print information about which files end up being uploaded.
99101
*/
100-
assets: string | string[];
102+
assets?: string | string[];
101103

102104
/**
103105
* A glob or an array of globs that specifies which build artifacts should not be uploaded to Sentry.

packages/dev-utils/src/generate-documentation-table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ errorHandler: (err) => {
7979
name: "assets",
8080
type: "string | string[]",
8181
fullDescription:
82-
"A glob or an array of globs that specifies the build artifacts that should be uploaded to Sentry.\n\nThe globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob)\n\nUse the `debug` option to print information about which files end up being uploaded.",
82+
"A glob or an array of globs that specifies the build artifacts that should be uploaded to Sentry.\n\nIf this option is not specified, the plugin will try to upload all JavaScript files and source map files that are created during build.\n\nThe globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob)\n\nUse the `debug` option to print information about which files end up being uploaded.",
8383
},
8484
{
8585
name: "ignore",

packages/esbuild-plugin/README_TEMPLATE.md

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,6 @@ require("esbuild").build({
4949
// Auth tokens can be obtained from https://sentry.io/settings/account/api/auth-tokens/
5050
// and need `project:releases` and `org:read` scopes
5151
authToken: process.env.SENTRY_AUTH_TOKEN,
52-
53-
sourcemaps: {
54-
// Specify the directory containing build artifacts
55-
assets: "./**",
56-
// Don't upload the source maps of dependencies
57-
ignore: ["./node_modules/**"],
58-
},
59-
60-
// Helps troubleshooting - set to false to make plugin less noisy
61-
debug: true,
62-
63-
// Use the following option if you're on an SDK version lower than 7.47.0:
64-
// release: {
65-
// uploadLegacySourcemaps: {
66-
// include: ".",
67-
// ignore: ["node_modules"],
68-
// },
69-
// },
70-
71-
// Optionally uncomment the line below to override automatic release name detection
72-
// release: process.env.RELEASE,
7352
}),
7453
],
7554
});

packages/esbuild-plugin/src/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,27 @@ function esbuildDebugIdInjectionPlugin(): UnpluginOptions {
6969
};
7070
}
7171

72+
function esbuildDebugIdUploadPlugin(
73+
upload: (buildArtifacts: string[]) => Promise<void>
74+
): UnpluginOptions {
75+
return {
76+
name: "sentry-esbuild-debug-id-upload-plugin",
77+
esbuild: {
78+
setup({ initialOptions, onEnd }) {
79+
initialOptions.metafile = true;
80+
onEnd(async (result) => {
81+
const buildArtifacts = result.metafile ? Object.keys(result.metafile.outputs) : [];
82+
await upload(buildArtifacts);
83+
});
84+
},
85+
},
86+
};
87+
}
88+
7289
const sentryUnplugin = sentryUnpluginFactory({
7390
releaseInjectionPlugin: esbuildReleaseInjectionPlugin,
7491
debugIdInjectionPlugin: esbuildDebugIdInjectionPlugin,
92+
debugIdUploadPlugin: esbuildDebugIdUploadPlugin,
7593
});
7694

7795
// eslint-disable-next-line @typescript-eslint/no-explicit-any

packages/playground/build-esbuild.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ build({
66
outdir: "./out/esbuild",
77
plugins: [
88
sentryEsbuildPlugin({
9-
sourcemaps: {
10-
assets: "./out/esbuild/**",
11-
deleteFilesAfterUpload: "./out/esbuild/**/*.map",
12-
},
9+
debug: true,
1310
}),
1411
],
1512
minify: true,

packages/playground/build-webpack4.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ webpack4(
1616
},
1717
plugins: [
1818
sentryWebpackPlugin({
19-
sourcemaps: {
20-
assets: "./out/webpack4/**",
21-
deleteFilesAfterUpload: "./out/webpack4/**/*.map",
22-
},
19+
debug: true,
2320
}),
2421
],
2522
devtool: "source-map",

0 commit comments

Comments
 (0)