Skip to content

Commit ce610f9

Browse files
authored
ref(core): Make various user and internal options optional (#103)
Adjust some user-facing and internal options from being strictly required to being optional. The reason for this change is that with the introduction of Sentry CLI, we can (and for feature parity should) let users either use env variables or specify some values via a `.sentryclirc` config file. Note that for release injection to work properly, users have to specify `project`, `org` and `release` either via the options or via env variables. The reason is that the config file which could also hold these options is only later used by Sentry CLI. This behaviour is identical to what we do in the webpack plugin.
1 parent d0188bc commit ce610f9

File tree

5 files changed

+104
-65
lines changed

5 files changed

+104
-65
lines changed

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

Lines changed: 81 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
import { IncludeEntry as UserIncludeEntry, Options as UserOptions } from "./types";
2+
import { arrayify } from "./utils";
23

34
type RequiredInternalOptions = Required<
45
Pick<
56
UserOptions,
6-
| "org"
7-
| "project"
8-
| "authToken"
9-
| "url"
107
| "release"
118
| "finalize"
12-
| "vcsRemote"
139
| "dryRun"
1410
| "debug"
1511
| "silent"
@@ -22,7 +18,17 @@ type RequiredInternalOptions = Required<
2218
type OptionalInternalOptions = Partial<
2319
Pick<
2420
UserOptions,
25-
"dist" | "errorHandler" | "setCommits" | "deploy" | "configFile" | "customHeader"
21+
| "org"
22+
| "project"
23+
| "authToken"
24+
| "url"
25+
| "vcsRemote"
26+
| "dist"
27+
| "errorHandler"
28+
| "setCommits"
29+
| "deploy"
30+
| "configFile"
31+
| "customHeader"
2632
>
2733
>;
2834

@@ -52,59 +58,88 @@ export type InternalIncludeEntry = RequiredInternalIncludeEntry &
5258
};
5359

5460
export function normalizeUserOptions(userOptions: UserOptions): InternalOptions {
55-
let entries: (string | RegExp)[] | ((filePath: string) => boolean) | undefined;
56-
if (userOptions.entries === undefined) {
57-
entries = undefined;
58-
} else if (typeof userOptions.entries === "function" || Array.isArray(userOptions.entries)) {
59-
entries = userOptions.entries;
60-
} else {
61-
entries = [userOptions.entries];
62-
}
63-
64-
let userInclude: UserIncludeEntry[];
65-
if (typeof userOptions.include === "string") {
66-
userInclude = [convertIncludePathToIncludeEntry(userOptions.include)];
67-
} else if (Array.isArray(userOptions.include)) {
68-
userInclude = userOptions.include.map((potentialIncludeEntry) => {
69-
if (typeof potentialIncludeEntry === "string") {
70-
return convertIncludePathToIncludeEntry(potentialIncludeEntry);
71-
} else {
72-
return potentialIncludeEntry;
73-
}
74-
});
75-
} else {
76-
userInclude = [userOptions.include];
77-
}
61+
return {
62+
// include is the only strictly required option
63+
// (normalizeInclude needs all userOptions to access top-level include options)
64+
include: normalizeInclude(userOptions),
7865

79-
const include = userInclude.map((userIncludeEntry) =>
80-
normalizeIncludeEntry(userOptions, userIncludeEntry)
81-
);
66+
// These options must be set b/c we need them for release injection.
67+
// They can also be set as environment variables. Technically, they
68+
// could be set in the config file but this would be too late for
69+
// release injection because we only pass the config file path
70+
// to the CLI
71+
org: userOptions.org ?? process.env["SENTRY_ORG"],
72+
project: userOptions.project ?? process.env["SENTRY_PROJECT"],
73+
// Falling back to the empty string here b/c at a later point, we use
74+
// Sentry CLI to determine a release if none was specified via options
75+
// or env vars. In case we don't find one, we'll bail at that point.
76+
release: userOptions.release ?? process.env["SENTRY_RELEASE"] ?? "",
8277

83-
return {
84-
org: userOptions.org,
85-
project: userOptions.project,
86-
authToken: userOptions.authToken,
87-
url: userOptions.url ?? "https://sentry.io/",
88-
release: userOptions.release ?? "",
78+
// Options with default values
8979
finalize: userOptions.finalize ?? true,
90-
vcsRemote: userOptions.vcsRemote ?? "origin",
91-
customHeader: userOptions.customHeader,
80+
cleanArtifacts: userOptions.cleanArtifacts ?? false,
9281
dryRun: userOptions.dryRun ?? false,
9382
debug: userOptions.debug ?? false,
9483
silent: userOptions.silent ?? false,
95-
cleanArtifacts: userOptions.cleanArtifacts ?? false,
9684
telemetry: userOptions.telemetry ?? true,
97-
dist: userOptions.dist,
98-
errorHandler: userOptions.errorHandler,
85+
injectReleasesMap: userOptions.injectReleasesMap ?? false,
86+
87+
// These options and can also be set via env variables or the config file.
88+
// If they're set in the options, we simply pass them to the CLI constructor.
89+
// Sentry CLI will internally query env variables and read its config file if
90+
// the passed options are undefined.
91+
authToken: userOptions.authToken, // env var: `SENTRY_AUTH_TOKEN`
92+
customHeader: userOptions.customHeader, // env var: `CUSTOM_HEADER`
93+
url: userOptions.url, // env var: `SENTRY_URL`
94+
vcsRemote: userOptions.vcsRemote, // env var: `SENTRY_VSC_REMOTE`
95+
96+
// Optional options
9997
setCommits: userOptions.setCommits,
10098
deploy: userOptions.deploy,
101-
entries,
102-
include,
99+
entries: normalizeEntries(userOptions.entries),
100+
dist: userOptions.dist,
101+
errorHandler: userOptions.errorHandler,
103102
configFile: userOptions.configFile,
104-
injectReleasesMap: userOptions.injectReleasesMap ?? false,
105103
};
106104
}
107105

106+
/**
107+
* Converts the user-facing `entries` option to the internal `entries` option
108+
*/
109+
function normalizeEntries(
110+
userEntries: UserOptions["entries"]
111+
): (string | RegExp)[] | ((filePath: string) => boolean) | undefined {
112+
if (userEntries === undefined) {
113+
return undefined;
114+
} else if (typeof userEntries === "function") {
115+
return userEntries;
116+
} else {
117+
return arrayify(userEntries);
118+
}
119+
}
120+
121+
/**
122+
* Converts the user-facing `include` option to the internal `include` option,
123+
* resulting in an array of `InternalIncludeEntry` objects. This later on lets us
124+
* work with only one type of include data structure instead of multiple.
125+
*
126+
* During the process, we hoist top-level include options (e.g. urlPrefix) into each
127+
* object if they were not alrady specified in an `IncludeEntry`, making every object
128+
* fully self-contained. This is also the reason why we pass the entire options
129+
* object and not just `include`.
130+
*
131+
* @param userOptions the entire user-facing `options` object
132+
*
133+
* @return an array of `InternalIncludeEntry` objects.
134+
*/
135+
function normalizeInclude(userOptions: UserOptions): InternalIncludeEntry[] {
136+
return arrayify(userOptions.include)
137+
.map((includeItem) =>
138+
typeof includeItem === "string" ? convertIncludePathToIncludeEntry(includeItem) : includeItem
139+
)
140+
.map((userIncludeEntry) => normalizeIncludeEntry(userOptions, userIncludeEntry));
141+
}
142+
108143
function convertIncludePathToIncludeEntry(includePath: string): UserIncludeEntry {
109144
return {
110145
paths: [includePath],

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ export async function createNewRelease(options: InternalOptions, ctx: BuildConte
2020
} else if (options.org === undefined) {
2121
ctx.logger.warn('Missing "org" option. Will not create release.');
2222
return;
23-
} else if (options.url === undefined) {
24-
ctx.logger.warn('Missing "url" option. Will not create release.');
25-
return;
2623
} else if (options.project === undefined) {
2724
ctx.logger.warn('Missing "project" option. Will not create release.');
2825
return;
@@ -45,9 +42,6 @@ export async function uploadSourceMaps(options: InternalOptions, ctx: BuildConte
4542
} else if (options.org === undefined) {
4643
ctx.logger.warn('Missing "org" option. Will not create release.');
4744
return Promise.resolve();
48-
} else if (options.url === undefined) {
49-
ctx.logger.warn('Missing "url" option. Will not create release.');
50-
return Promise.resolve();
5145
} else if (options.project === undefined) {
5246
ctx.logger.warn('Missing "project" option. Will not create release.');
5347
return Promise.resolve();
@@ -86,9 +80,6 @@ export async function cleanArtifacts(options: InternalOptions, ctx: BuildContext
8680
} else if (options.org === undefined) {
8781
ctx.logger.warn('Missing "org" option. Will not clean existing artifacts.');
8882
return;
89-
} else if (options.url === undefined) {
90-
ctx.logger.warn('Missing "url" option. Will not clean existing artifacts.');
91-
return;
9283
} else if (options.project === undefined) {
9384
ctx.logger.warn('Missing "project" option. Will not clean existing artifacts.');
9485
return;

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,32 @@ export type Options = Omit<IncludeEntry, "paths"> & {
1212
/**
1313
* The slug of the Sentry organization associated with the app.
1414
*
15-
* This is a required field.
15+
* This value can also be set via the `SENTRY_ORG` env variable.
1616
*/
17-
org: string;
17+
org?: string;
1818

1919
/**
2020
* The slug of the Sentry project associated with the app.
2121
*
22-
* This is a required field.
22+
* This value can also be set via the `SENTRY_PROJECT` env variable.
2323
*/
24-
project: string;
24+
project?: string;
2525

2626
/**
2727
* The authentication token to use for all communication with Sentry.
2828
* Can be obtained from https://sentry.io/settings/account/api/auth-tokens/.
2929
* Required scopes: project:releases (and org:read if setCommits option is used).
3030
*
31-
* This is a required field.
31+
* This value can also be set via the `SENTRY_AUTH_TOKEN` env variable
3232
*/
33-
authToken: string;
33+
authToken?: string;
3434

3535
/**
3636
* The base URL of your Sentry instance. Use this if you are using a self-hosted
3737
* or Sentry instance other than sentry.io.
3838
*
39+
* This value can also be set via the `SENTRY_URL` env variable.
40+
*
3941
* Defaults to https://sentry.io/, which is the correct value for SAAS customers.
4042
*/
4143
url?: string;
@@ -45,6 +47,8 @@ export type Options = Omit<IncludeEntry, "paths"> & {
4547
/**
4648
* Unique identifier for the release.
4749
*
50+
* This value can also be set via the `SENTRY_RELEASE` env variable.
51+
*
4852
* Defaults to the output of the sentry-cli releases propose-version command,
4953
* which automatically detects values for Cordova, Heroku, AWS CodeBuild, CircleCI,
5054
* Xcode, and Gradle, and otherwise uses HEAD's commit SHA. (For HEAD option,
@@ -93,13 +97,17 @@ export type Options = Omit<IncludeEntry, "paths"> & {
9397
/**
9498
* Version control system remote name.
9599
*
100+
* This value can also be set via the `SENTRY_VSC_REMOTE` env variable.
101+
*
96102
* Defaults to 'origin'.
97103
*/
98104
vcsRemote?: string;
99105

100106
/**
101107
* A header added to every outgoing network request.
102108
* The format should be `header-key: header-value`.
109+
*
110+
* This value can also be set via the `CUSTOM_HEADER` env variable.
103111
*/
104112
customHeader?: string;
105113

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Checks whether the given input is already an array, and if it isn't, wraps it in one.
3+
*
4+
* @param maybeArray Input to turn into an array, if necessary
5+
* @returns The input, if already an array, or an array with the input as the only element, if not
6+
*/
7+
export function arrayify<T = unknown>(maybeArray: T | T[]): T[] {
8+
return Array.isArray(maybeArray) ? maybeArray : [maybeArray];
9+
}

packages/bundler-plugin-core/test/option-mappings.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ describe("normalizeUserOptions()", () => {
3333
release: "my-release",
3434
silent: false,
3535
telemetry: true,
36-
url: "https://sentry.io/",
37-
vcsRemote: "origin",
3836
injectReleasesMap: false,
3937
});
4038
});
@@ -77,8 +75,6 @@ describe("normalizeUserOptions()", () => {
7775
release: "my-release",
7876
silent: false,
7977
telemetry: true,
80-
url: "https://sentry.io/",
81-
vcsRemote: "origin",
8278
injectReleasesMap: false,
8379
});
8480
});

0 commit comments

Comments
 (0)