Skip to content

Commit 3fb1353

Browse files
authored
feat(js): Add live: 'rejectOnError' execution mode to execute method (#2605)
The Sentry CLI JS interface's `execute` method takes a `live` parameter as a second argument: - if `false`, stdio is returned and the promise returned from `execute` rejects/resolves based on the CLI binary's exit code. - if `true`, CLI stdio is piped to the process calling `execute`, but the returned promise always resolves regardless of the exit code (raised in #2062) This feature adds a third value (`'rejectOnError'`) to `live`, which still pipes stdio but also rejects if the CLI binary's exit code was non-zero. Arguably, this is what `true` should do but to avoid a breaking change for now, let's introduce this as a new value instead.
1 parent eebcb3c commit 3fb1353

4 files changed

Lines changed: 71 additions & 6 deletions

File tree

js/__tests__/helper.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@ describe('SentryCli helper', () => {
2222
expect(helper.getPath()).toMatch(pattern);
2323
});
2424

25+
describe('execute', () => {
26+
test('execute with live=false returns stdout', async () => {
27+
const output = await helper.execute(['--version'], false);
28+
expect(output.trim()).toBe('sentry-cli DEV');
29+
});
30+
31+
test('execute with live=true resolves without output', async () => {
32+
// TODO (v3): This should resolve with a string, not undefined/void
33+
const result = await helper.execute(['--version'], true);
34+
expect(result).toBeUndefined();
35+
});
36+
37+
test('execute with live=rejectOnError resolves on success', async () => {
38+
const result = await helper.execute(['--version'], 'rejectOnError');
39+
expect(result).toBe('success (live mode)');
40+
});
41+
42+
test('execute with live=rejectOnError rejects on failure', async () => {
43+
await expect(helper.execute(['fail'], 'rejectOnError')).rejects.toThrow(
44+
'Command fail failed with exit code 1'
45+
);
46+
});
47+
});
48+
2549
describe('`prepare` command', () => {
2650
test('call prepare command add default ignore', () => {
2751
const command = ['releases', 'files', 'release', 'upload-sourcemaps', '/dev/null'];

js/helper.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,11 @@ function getPath() {
281281
* expect(output.trim()).toBe('sentry-cli x.y.z');
282282
*
283283
* @param {string[]} args Command line arguments passed to `sentry-cli`.
284-
* @param {boolean} live We inherit stdio to display `sentry-cli` output directly.
284+
* @param {boolean | 'rejectOnError'} live can be set to:
285+
* - `true` to inherit stdio to display `sentry-cli` output directly.
286+
* - `false` to not inherit stdio and return the output as a string.
287+
* - `'rejectOnError'` to inherit stdio and reject the promise if the command
288+
* exits with a non-zero exit code.
285289
* @param {boolean} silent Disable stdout for silents build (CI/Webpack Stats, ...)
286290
* @param {string} [configFile] Relative or absolute path to the configuration file.
287291
* @param {Object} [config] More configuration to pass to the CLI
@@ -322,16 +326,27 @@ async function execute(args, live, silent, configFile, config = {}) {
322326
]);
323327
args = [...headers, ...args];
324328
}
329+
325330
return new Promise((resolve, reject) => {
326-
if (live === true) {
331+
if (live === true || live === 'rejectOnError') {
327332
const output = silent ? 'ignore' : 'inherit';
328333
const pid = childProcess.spawn(getPath(), args, {
329334
env,
330335
// stdin, stdout, stderr
331336
stdio: ['ignore', output, output],
332337
});
333-
pid.on('exit', () => {
334-
// @ts-expect-error - this is a TODO (v3) to fix and resolve a string here
338+
pid.on('exit', (exitCode) => {
339+
if (live === 'rejectOnError') {
340+
if (exitCode === 0) {
341+
resolve('success (live mode)');
342+
}
343+
reject(new Error(`Command ${args.join(' ')} failed with exit code ${exitCode}`));
344+
}
345+
// According to the type definition, resolving with void is not allowed.
346+
// However, for backwards compatibility, we resolve void here to
347+
// avoid a behaviour-breaking change.
348+
// TODO (v3): Clean this up and always resolve a string (or change the type definition)
349+
// @ts-expect-error - see comment above
335350
resolve();
336351
});
337352
} else {

js/releases/__tests__/index.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,25 @@ describe('SentryCli releases', () => {
159159
{ silent: false }
160160
);
161161
});
162+
163+
test.each([true, false, 'rejectOnError'])('handles live mode %s', async (live) => {
164+
await cli.releases.uploadSourceMaps('my-version', { include: ['path'], live });
165+
expect(mockExecute).toHaveBeenCalledWith(
166+
[
167+
'releases',
168+
'files',
169+
'my-version',
170+
'upload-sourcemaps',
171+
'path',
172+
'--ignore',
173+
'node_modules',
174+
],
175+
live,
176+
false,
177+
undefined,
178+
{ silent: false }
179+
);
180+
});
162181
});
163182
});
164183
});

js/releases/index.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ class Releases {
199199

200200
return uploadPaths.map((path) =>
201201
// `execute()` is async and thus we're returning a promise here
202-
this.execute(helper.prepareCommand([...args, path], SOURCEMAPS_SCHEMA, newOptions), true)
202+
this.execute(
203+
helper.prepareCommand([...args, path], SOURCEMAPS_SCHEMA, newOptions),
204+
options.live != null ? options.live : true
205+
)
203206
);
204207
});
205208

@@ -255,7 +258,11 @@ class Releases {
255258
/**
256259
* See {helper.execute} docs.
257260
* @param {string[]} args Command line arguments passed to `sentry-cli`.
258-
* @param {boolean} live We inherit stdio to display `sentry-cli` output directly.
261+
* @param {boolean | 'rejectOnError'} live can be set to:
262+
* - `true` to inherit stdio to display `sentry-cli` output directly.
263+
* - `false` to not inherit stdio and return the output as a string.
264+
* - `'rejectOnError'` to inherit stdio and reject the promise if the command
265+
* exits with a non-zero exit code.
259266
* @returns {Promise.<string>} A promise that resolves to the standard output.
260267
*/
261268
async execute(args, live) {

0 commit comments

Comments
 (0)