-
-
Notifications
You must be signed in to change notification settings - Fork 241
Expand file tree
/
Copy pathhelper.ts
More file actions
370 lines (332 loc) · 11.6 KB
/
helper.ts
File metadata and controls
370 lines (332 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
'use strict';
import * as os from 'node:os';
import * as path from 'node:path';
import * as fs from 'node:fs';
import * as childProcess from 'node:child_process';
import { SentryCliOptions } from './types';
const BINARY_DISTRIBUTIONS = [
{ packageName: '@sentry/cli-darwin', subpath: 'bin/sentry-cli' },
{ packageName: '@sentry/cli-linux-x64', subpath: 'bin/sentry-cli' },
{ packageName: '@sentry/cli-linux-i686', subpath: 'bin/sentry-cli' },
{ packageName: '@sentry/cli-linux-arm64', subpath: 'bin/sentry-cli' },
{ packageName: '@sentry/cli-linux-arm', subpath: 'bin/sentry-cli' },
{ packageName: '@sentry/cli-win32-x64', subpath: 'bin/sentry-cli.exe' },
{ packageName: '@sentry/cli-win32-i686', subpath: 'bin/sentry-cli.exe' },
{ packageName: '@sentry/cli-win32-arm64', subpath: 'bin/sentry-cli.exe' },
];
/**
* This convoluted function resolves the path to the manually downloaded fallback
* `sentry-cli` binary in a way that can't be analysed by @vercel/nft.
*
* Without this, the binary can be detected as an asset and included by bundlers
* that use @vercel/nft.
*
* @returns The path to the sentry-cli binary
*/
function getFallbackBinaryPath(): string {
const parts = [];
parts.push(__dirname);
parts.push('..');
parts.push(`sentry-cli${process.platform === 'win32' ? '.exe' : ''}`);
return path.resolve(...parts);
}
function getDistributionForThisPlatform() {
const arch = os.arch();
const platform = os.platform();
let packageName = undefined;
if (platform === 'darwin') {
packageName = '@sentry/cli-darwin';
} else if (platform === 'linux' || platform === 'freebsd' || platform === 'android') {
switch (arch) {
case 'x64':
packageName = '@sentry/cli-linux-x64';
break;
case 'x86':
case 'ia32':
packageName = '@sentry/cli-linux-i686';
break;
case 'arm64':
packageName = '@sentry/cli-linux-arm64';
break;
case 'arm':
packageName = '@sentry/cli-linux-arm';
break;
}
} else if (platform === 'win32') {
switch (arch) {
case 'x64':
packageName = '@sentry/cli-win32-x64';
break;
case 'x86':
case 'ia32':
packageName = '@sentry/cli-win32-i686';
break;
case 'arm64':
packageName = '@sentry/cli-win32-arm64';
break;
}
}
let subpath = undefined;
switch (platform) {
case 'win32':
subpath = 'bin/sentry-cli.exe';
break;
case 'darwin':
case 'linux':
case 'freebsd':
case 'android':
subpath = 'bin/sentry-cli';
break;
default:
subpath = 'bin/sentry-cli';
break;
}
return { packageName, subpath };
}
/**
* Throws an error with a message stating that Sentry CLI doesn't support the current platform.
*
* @returns nothing. It throws.
*/
function throwUnsupportedPlatformError(): void {
throw new Error(
`Unsupported operating system or architecture! Sentry CLI does not work on this architecture.
Sentry CLI supports:
- Darwin (macOS)
- Linux and FreeBSD on x64, x86, ia32, arm64, and arm architectures
- Windows x64, x86, and ia32 architectures`
);
}
/**
* Tries to find the installed Sentry CLI binary - either by looking into the relevant
* optional dependencies or by trying to resolve the fallback binary.
*
* @returns The path to the sentry-cli binary
*/
function getBinaryPath(): string {
if (process.env.SENTRY_BINARY_PATH) {
return process.env.SENTRY_BINARY_PATH;
}
const { packageName, subpath } = getDistributionForThisPlatform();
if (packageName === undefined) {
throwUnsupportedPlatformError();
}
let fallbackBinaryPath = getFallbackBinaryPath();
if (fs.existsSync(fallbackBinaryPath)) {
// Since the fallback got installed, the optional dependencies likely didn't get installed, so we just default to the fallback.
return fallbackBinaryPath;
}
let compatibleBinaryPath;
try {
compatibleBinaryPath = require.resolve(`${packageName}/${subpath}`);
} catch (e) {
const otherInstalledDistribution = BINARY_DISTRIBUTIONS.find(({ packageName, subpath }) => {
try {
require.resolve(`${packageName}/${subpath}`);
return true;
} catch (e) {
return false;
}
});
// These error messages are heavily inspired by esbuild's error messages: https://github.com/evanw/esbuild/blob/f3d535262e3998d845d0f102b944ecd5a9efda57/lib/npm/node-platform.ts#L150
if (otherInstalledDistribution) {
throw new Error(`Sentry CLI binary for this platform/architecture not found!
The "${otherInstalledDistribution.packageName}" package is installed, but for the current platform, you should have the "${packageName}" package installed instead. This usually happens if the "@sentry/cli" package is installed on one platform (for example Windows or MacOS) and then the "node_modules" folder is reused on another operating system (for example Linux in Docker).
To fix this, avoid copying the "node_modules" folder, and instead freshly install your dependencies on the target system. You can also configure your package manager to install the right package. For example, yarn has the "supportedArchitectures" feature: https://yarnpkg.com/configuration/yarnrc/#supportedArchitecture.`);
} else {
throw new Error(`Sentry CLI binary for this platform/architecture not found!
It seems like none of the "@sentry/cli" package's optional dependencies got installed. Please make sure your package manager is configured to install optional dependencies. If you are using npm to install your dependencies, please don't set the "--no-optional", "--ignore-optional", or "--omit=optional" flags. Sentry CLI needs the "optionalDependencies" feature in order to install its binary.`);
}
}
return compatibleBinaryPath;
}
/**
* Will be used as the binary path when defined with `mockBinaryPath`.
*/
let mockedBinaryPath: string | undefined;
/**
* Overrides the default binary path with a mock value, useful for testing.
*
* @param mockPath The new path to the mock sentry-cli binary
* @deprecated This was used in tests internally and will be removed in the next major version.
*/
// TODO(v3): Remove this function
function mockBinaryPath(mockPath: string) {
mockedBinaryPath = mockPath;
}
export type OptionsSchema = Record<
string,
| {
param: string;
type: 'array' | 'string' | 'number' | 'boolean' | 'inverted-boolean';
invertedParam?: string;
}
| {
param?: never;
type: 'array' | 'string' | 'number' | 'boolean' | 'inverted-boolean';
invertedParam: string;
}
>;
/**
* Serializes command line options into an arguments array.
*
* @param schema An options schema required by the command.
* @param options An options object according to the schema.
*/
function serializeOptions(schema: OptionsSchema, options: Record<string, unknown>): string[] {
return Object.keys(schema).reduce((newOptions, option) => {
const paramValue = options[option];
if (paramValue === undefined || paramValue === null) {
return newOptions;
}
const paramType = schema[option].type;
const paramName = schema[option].param;
if (paramType === 'array') {
if (!Array.isArray(paramValue)) {
throw new Error(`${option} should be an array`);
}
return newOptions.concat(
paramValue.reduce((acc, value) => acc.concat([paramName, String(value)]), [])
);
}
if (paramType === 'boolean') {
if (typeof paramValue !== 'boolean') {
throw new Error(`${option} should be a bool`);
}
const invertedParamName = schema[option].invertedParam;
if (paramValue && paramName !== undefined) {
return newOptions.concat([paramName]);
}
if (!paramValue && invertedParamName !== undefined) {
return newOptions.concat([invertedParamName]);
}
return newOptions;
}
return newOptions.concat(paramName, paramValue);
}, []);
}
/**
* Serializes the command and its options into an arguments array.
*
* @param command The literal name of the command.
* @param schema An options schema required by the command.
* @param options An options object according to the schema.
* @returns An arguments array that can be passed via command line.
*/
function prepareCommand(
command: string[],
schema: OptionsSchema,
options: Record<string, unknown>
): string[] {
return command.concat(serializeOptions(schema || {}, options || {}));
}
/**
* Returns the absolute path to the `sentry-cli` binary.
*/
function getPath(): string {
return mockedBinaryPath !== undefined ? mockedBinaryPath : getBinaryPath();
}
/**
* Runs `sentry-cli` with the given command line arguments.
*
* Use {@link prepareCommand} to specify the command and add arguments for command-
* specific options. For top-level options, use {@link serializeOptions} directly.
*
* The returned promise resolves with the standard output of the command invocation
* including all newlines. In order to parse this output, be sure to trim the output
* first.
*
* If the command failed to execute, the Promise rejects with the error returned by the
* CLI. This error includes a `code` property with the process exit status.
*
* @example
* const output = await execute(['--version']);
* expect(output.trim()).toBe('sentry-cli x.y.z');
*
* @param args Command line arguments passed to `sentry-cli`.
* @param live can be set to:
* - `true` to inherit stdio and reject the promise if the command
* exits with a non-zero exit code.
* - `false` to not inherit stdio and return the output as a string.
* @param silent Disable stdout for silents build (CI/Webpack Stats, ...)
* @param configFile Relative or absolute path to the configuration file.
* @param config More configuration to pass to the CLI
* @returns A promise that resolves to the standard output.
*/
async function execute(
args: string[],
live: boolean,
silent: boolean,
configFile: string | undefined,
config: SentryCliOptions = {}
): Promise<string> {
const env = { ...process.env };
if (configFile) {
env.SENTRY_PROPERTIES = configFile;
}
if (config.url) {
env.SENTRY_URL = config.url;
}
if (config.authToken) {
env.SENTRY_AUTH_TOKEN = config.authToken;
}
if (config.dsn) {
env.SENTRY_DSN = config.dsn;
}
if (config.org) {
env.SENTRY_ORG = config.org;
}
if (config.project) {
env.SENTRY_PROJECT = config.project;
}
if (config.vcsRemote) {
env.SENTRY_VCS_REMOTE = config.vcsRemote;
}
if (config.customHeader) {
env.CUSTOM_HEADER = config.customHeader;
} else if (config.headers) {
const headers = Object.entries(config.headers).flatMap(([key, value]) => [
'--header',
`${key}:${value}`,
]);
args = [...headers, ...args];
}
return new Promise((resolve, reject) => {
if (live) {
const output = silent ? 'ignore' : 'inherit';
const pid = childProcess.spawn(getPath(), args, {
env,
// stdin, stdout, stderr
stdio: ['ignore', output, output],
});
pid.on('exit', (exitCode) => {
if (exitCode === 0) {
resolve('success (live mode)');
}
reject(new Error(`Command ${args.join(' ')} failed with exit code ${exitCode}`));
});
} else {
childProcess.execFile(getPath(), args, { env }, (err, stdout) => {
if (err) {
reject(err);
} else {
resolve(stdout);
}
});
}
});
}
function getProjectFlagsFromOptions({ projects = [] } = {}) {
return projects.reduce((flags, project) => flags.concat('-p', project), []);
}
export {
execute,
getPath,
getProjectFlagsFromOptions,
mockBinaryPath,
prepareCommand,
serializeOptions,
getDistributionForThisPlatform,
throwUnsupportedPlatformError,
getFallbackBinaryPath,
};