Skip to content

Commit 4b7d2e8

Browse files
authored
ref(core): Add Sentry CLI (#86)
Bring back Sentry CLI as a dependency and adjust a few necessary things to be compatible with the CLI along the way: * Add CLI dependency * Add `getCLI()` function which creates a CLI or a stub if in `dryRun` mode * Adjust logger (+ introduce a debug level) * `dryRun` mode of CLI needs it * added variable args parameter to fully utilize `console.log` capabilities * Bring back the `configFile` option as it is relevant again because of Sentry CLI
1 parent 9be1d56 commit 4b7d2e8

File tree

8 files changed

+241
-42
lines changed

8 files changed

+241
-42
lines changed

packages/bundler-plugin-core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
"clean:build": "rimraf ./dist *.tgz",
3232
"clean:deps": "rimraf node_modules",
3333
"test": "jest",
34-
"lint": "eslint ./src ./test"
34+
"lint": "eslint ./src ./test",
35+
"fix": "eslint ./src ./test --format stylish --fix"
3536
},
3637
"dependencies": {
38+
"@sentry/cli": "^1.74.6",
3739
"@sentry/node": "^7.11.1",
3840
"@sentry/tracing": "^7.11.1",
3941
"axios": "^0.27.2",

packages/bundler-plugin-core/src/options-mapping.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type RequiredInternalOptions = Required<
2121
>;
2222

2323
type OptionalInternalOptions = Partial<
24-
Pick<UserOptions, "dist" | "errorHandler" | "setCommits" | "deploy">
24+
Pick<UserOptions, "dist" | "errorHandler" | "setCommits" | "deploy" | "configFile">
2525
>;
2626

2727
type NormalizedInternalOptions = {
@@ -97,6 +97,7 @@ export function normalizeUserOptions(userOptions: UserOptions): InternalOptions
9797
deploy: userOptions.deploy,
9898
entries,
9999
include,
100+
configFile: userOptions.configFile,
100101
};
101102
}
102103

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import SentryCli, { SentryCliReleases } from "@sentry/cli";
2+
import { InternalOptions } from "../options-mapping";
3+
import { Logger } from "./logger";
4+
5+
type SentryDryRunCLI = { releases: Omit<SentryCliReleases, "listDeploys" | "execute"> };
6+
type SentryCLILike = SentryCli | SentryDryRunCLI;
7+
8+
/**
9+
* Creates a new Sentry CLI instance.
10+
*
11+
* In case, users selected the `dryRun` options, this returns a stub
12+
* that makes no-ops out of most CLI operations
13+
*/
14+
export function getSentryCli(internalOptions: InternalOptions, logger: Logger): SentryCLILike {
15+
const cli = new SentryCli(internalOptions.configFile, {
16+
silent: internalOptions.silent,
17+
org: internalOptions.org,
18+
project: internalOptions.project,
19+
authToken: internalOptions.authToken,
20+
url: internalOptions.url,
21+
vcsRemote: internalOptions.vcsRemote,
22+
});
23+
24+
if (internalOptions.dryRun) {
25+
return getDryRunCLI(cli, logger);
26+
}
27+
28+
return cli;
29+
}
30+
31+
function getDryRunCLI(cli: SentryCli, logger: Logger): SentryDryRunCLI {
32+
return {
33+
releases: {
34+
proposeVersion: () =>
35+
cli.releases.proposeVersion().then((version) => {
36+
logger.info("Proposed version:\n", version);
37+
return version;
38+
}),
39+
new: (release: string) => {
40+
logger.info("Creating new release:\n", release);
41+
return Promise.resolve(release);
42+
},
43+
uploadSourceMaps: (release: string, config: unknown) => {
44+
logger.info("Calling upload-sourcemaps with:\n", config);
45+
return Promise.resolve(release);
46+
},
47+
finalize: (release: string) => {
48+
logger.info("Finalizing release:\n", release);
49+
return Promise.resolve(release);
50+
},
51+
setCommits: (release: string, config: unknown) => {
52+
logger.info("Calling set-commits with:\n", config);
53+
return Promise.resolve(release);
54+
},
55+
newDeploy: (release: string, config: unknown) => {
56+
logger.info("Calling deploy with:\n", config);
57+
return Promise.resolve(release);
58+
},
59+
},
60+
};
61+
}

packages/bundler-plugin-core/src/sentry/logger.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ interface LoggerOptions {
66
prefix: string;
77
}
88

9-
export function createLogger(options: LoggerOptions) {
9+
export type Logger = {
10+
info(message: string, ...params: unknown[]): void;
11+
warn(message: string, ...params: unknown[]): void;
12+
error(message: string, ...params: unknown[]): void;
13+
debug(message: string, ...params: unknown[]): void;
14+
};
15+
16+
export function createLogger(options: LoggerOptions): Logger {
1017
function addBreadcrumb(level: SeverityLevel, message: string) {
1118
options.hub.addBreadcrumb({
1219
category: "logger",
@@ -16,29 +23,38 @@ export function createLogger(options: LoggerOptions) {
1623
}
1724

1825
return {
19-
info(message: string) {
26+
info(message: string, ...params: unknown[]) {
2027
if (!options?.silent) {
2128
// eslint-disable-next-line no-console
22-
console.log(`${options.prefix} ${message}`);
29+
console.log(`${options.prefix} Info: ${message}`, ...params);
2330
}
2431

2532
addBreadcrumb("info", message);
2633
},
27-
warn(message: string) {
34+
warn(message: string, ...params: unknown[]) {
2835
if (!options?.silent) {
2936
// eslint-disable-next-line no-console
30-
console.log(`${options.prefix} Warning! ${message}`);
37+
console.log(`${options.prefix} Warning: ${message}`, ...params);
3138
}
3239

3340
addBreadcrumb("warning", message);
3441
},
35-
error(message: string) {
42+
error(message: string, ...params: unknown[]) {
3643
if (!options?.silent) {
3744
// eslint-disable-next-line no-console
38-
console.log(`${options.prefix} Error: ${message}`);
45+
console.log(`${options.prefix} Error: ${message}`, ...params);
3946
}
4047

4148
addBreadcrumb("error", message);
4249
},
50+
51+
debug(message: string, ...params: unknown[]) {
52+
if (!options?.silent) {
53+
// eslint-disable-next-line no-console
54+
console.log(`${options.prefix} Debug: ${message}`, ...params);
55+
}
56+
57+
addBreadcrumb("debug", message);
58+
},
4359
};
4460
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ export type Options = Omit<IncludeEntry, "paths"> & {
177177
* Defaults to true
178178
*/
179179
telemetry?: boolean;
180+
181+
/**
182+
* Path to Sentry CLI config properties, as described in
183+
* https://docs.sentry.io/product/cli/configuration/#configuration-file.
184+
*
185+
* By default, the config file is looked for upwards from the current path, and
186+
* defaults from ~/.sentryclirc are always loaded
187+
*/
188+
configFile?: string;
180189
};
181190

182191
export type IncludeEntry = {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import SentryCli from "@sentry/cli";
2+
import { getSentryCli } from "../src/sentry/cli";
3+
4+
describe("getSentryCLI", () => {
5+
it("returns a valid CLI instance if dryRun is not specified", () => {
6+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
7+
const cli = getSentryCli({} as any, {} as any);
8+
expect(cli).toBeInstanceOf(SentryCli);
9+
});
10+
11+
it("returns a valid CLI instance if dryRun is set to true", () => {
12+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
13+
const cli = getSentryCli({ dryRun: true } as any, {} as any);
14+
expect(cli).not.toBeInstanceOf(SentryCli);
15+
});
16+
});

packages/bundler-plugin-core/test/logger.test.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,47 @@ describe("Logger", () => {
1919
mockedAddBreadcrumb.mockReset();
2020
});
2121

22-
it(".info() should log correctly", () => {
22+
it.each([
23+
["info", "Info"],
24+
["warn", "Warning"],
25+
["error", "Error"],
26+
["debug", "Debug"],
27+
] as const)(".%s() should log correctly", (loggerMethod, logLevel) => {
2328
const prefix = "[some-prefix]";
2429
const logger = createLogger({ hub, prefix });
25-
logger.info("Hey!");
2630

27-
expect(consoleLogSpy).toHaveBeenCalledWith("[some-prefix] Hey!");
28-
expect(mockedAddBreadcrumb).toHaveBeenCalledWith({
29-
category: "logger",
30-
level: "info",
31-
message: "Hey!",
32-
});
33-
});
31+
logger[loggerMethod]("Hey!");
3432

35-
it(".warn() should log correctly", () => {
36-
const prefix = "[some-prefix]";
37-
const logger = createLogger({ hub, prefix });
38-
logger.warn("Hey!");
39-
40-
expect(consoleLogSpy).toHaveBeenCalledWith("[some-prefix] Warning! Hey!");
33+
expect(consoleLogSpy).toHaveBeenCalledWith(`[some-prefix] ${logLevel}: Hey!`);
4134
expect(mockedAddBreadcrumb).toHaveBeenCalledWith({
4235
category: "logger",
43-
level: "warning",
36+
level: logLevel.toLowerCase(),
4437
message: "Hey!",
4538
});
4639
});
4740

48-
it(".error() should log correctly", () => {
41+
it.each([
42+
["info", "Info"],
43+
["warn", "Warning"],
44+
["error", "Error"],
45+
["debug", "Debug"],
46+
] as const)(".%s() should log multiple params correctly", (loggerMethod, logLevel) => {
4947
const prefix = "[some-prefix]";
5048
const logger = createLogger({ hub, prefix });
51-
logger.error("Hey!");
5249

53-
expect(consoleLogSpy).toHaveBeenCalledWith("[some-prefix] Error: Hey!");
50+
logger[loggerMethod]("Hey!", "this", "is", "a test with", 5, "params");
51+
52+
expect(consoleLogSpy).toHaveBeenCalledWith(
53+
`[some-prefix] ${logLevel}: Hey!`,
54+
"this",
55+
"is",
56+
"a test with",
57+
5,
58+
"params"
59+
);
5460
expect(mockedAddBreadcrumb).toHaveBeenCalledWith({
5561
category: "logger",
56-
level: "error",
62+
level: logLevel.toLowerCase(),
5763
message: "Hey!",
5864
});
5965
});

0 commit comments

Comments
 (0)