Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ we should rename this section to "Unreleased" -->
The following changes only apply when using `sentry-cli` via the npm package [`@sentry/cli`](https://www.npmjs.com/package/@sentry/cli):

- Drop support for Node.js <18. The minimum required Node.js version is now 18.0.0 ([#2985](https://github.com/getsentry/sentry-cli/issues/2985)).
- The type export `SentryCliReleases` has been removed
- The JavaScript wrapper now uses named exports instead of default exports ([#2989](https://github.com/getsentry/sentry-cli/pull/2989))
. You need to update your imports:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: CHANGELOG contains syntax error

Line 13 contains a stray period . on its own line, appearing to be an accidental character left after editing. This creates malformed markdown in the CHANGELOG.

Fix in Cursor Fix in Web

```js
// Old (default import)
const SentryCli = require('@sentry/cli');

// New (named import)
const { SentryCli } = require('@sentry/cli');
```

For ESM imports:
```js
// Old
import SentryCli from '@sentry/cli';

// New
import { SentryCli } from '@sentry/cli';
```


### Improvements

Expand Down Expand Up @@ -546,7 +566,7 @@ We made several refactors and added several tests in this release. These changes

<details>
<summary><h3>Changes to tests</h3></summary>

- ref(test): Broaden `with_header_matcher` types (#2261) by @szokeasaurusrex
- ref(test): Accept `impl Into<Matcher>` for `with_matcher` (#2260) by @szokeasaurusrex
- ref(test): Align `with_reponse_body` parameter to `mockito` (#2259) by @szokeasaurusrex
Expand Down
2 changes: 1 addition & 1 deletion bin/sentry-cli
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'use strict';

const childProcess = require('child_process');
const SentryCli = require('../js');
const { SentryCli } = require('../js');

const child = childProcess
.spawn(SentryCli.getPath(), process.argv.slice(2), {
Expand Down
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module.exports = {
setupFiles: ['<rootDir>/setupTests.js'],
testPathIgnorePatterns: ['<rootDir>/src/'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
};
2 changes: 1 addition & 1 deletion lib/__tests__/helper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const os = require('os');

const helper = require('../helper');

const SOURCEMAPS_OPTIONS = require('../releases/options/uploadSourcemaps');
const { SOURCEMAPS_OPTIONS } = require('../releases/options/uploadSourcemaps');

describe('SentryCli helper', () => {
test('call sentry-cli --version', () => {
Expand Down
104 changes: 54 additions & 50 deletions lib/helper.js → lib/helper.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use strict';

const os = require('os');
const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
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' },
Expand All @@ -23,9 +24,9 @@ const BINARY_DISTRIBUTIONS = [
* Without this, the binary can be detected as an asset and included by bundlers
* that use @vercel/nft.
*
* @returns {string} The path to the sentry-cli binary
* @returns The path to the sentry-cli binary
*/
function getFallbackBinaryPath() {
function getFallbackBinaryPath(): string {
const parts = [];
parts.push(__dirname);
parts.push('..');
Expand Down Expand Up @@ -93,9 +94,9 @@ function getDistributionForThisPlatform() {
/**
* Throws an error with a message stating that Sentry CLI doesn't support the current platform.
*
* @returns {never} nothing. It throws.
* @returns nothing. It throws.
*/
function throwUnsupportedPlatformError() {
function throwUnsupportedPlatformError(): void {
throw new Error(
`Unsupported operating system or architecture! Sentry CLI does not work on this architecture.

Expand All @@ -110,9 +111,9 @@ Sentry CLI supports:
* 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 {string} The path to the sentry-cli binary
* @returns The path to the sentry-cli binary
*/
function getBinaryPath() {
function getBinaryPath(): string {
if (process.env.SENTRY_BINARY_PATH) {
return process.env.SENTRY_BINARY_PATH;
}
Expand Down Expand Up @@ -161,47 +162,41 @@ It seems like none of the "@sentry/cli" package's optional dependencies got inst

/**
* Will be used as the binary path when defined with `mockBinaryPath`.
* @type {string | undefined}
*/
let mockedBinaryPath;
let mockedBinaryPath: string | undefined;

/**
* Overrides the default binary path with a mock value, useful for testing.
*
* @param {string} mockPath The new path to the mock sentry-cli binary
* @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) {
function mockBinaryPath(mockPath: string) {
mockedBinaryPath = mockPath;
}

/**
* The javascript type of a command line option.
* @typedef {'array'|'string'|'boolean'|'inverted-boolean'} OptionType
*/

/**
* Schema definition of a command line option.
* @typedef {object} OptionSchema
* @prop {string} param The flag of the command line option including dashes.
* @prop {OptionType} type The value type of the command line option.
* @prop {string} [invertedParam] The flag of the command line option including dashes (optional).
*/

/**
* Schema definition for a command.
* @typedef {Object.<string, OptionSchema>} OptionsSchema
*/
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 {OptionsSchema} schema An options schema required by the command.
* @param {object} options An options object according to the schema.
* @returns {string[]} An arguments array that can be passed via command line.
* @param schema An options schema required by the command.
* @param options An options object according to the schema.
*/
function serializeOptions(schema, options) {
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) {
Expand Down Expand Up @@ -246,20 +241,23 @@ function serializeOptions(schema, options) {
/**
* Serializes the command and its options into an arguments array.
*
* @param {string[]} command The literal name of the command.
* @param {OptionsSchema} [schema] An options schema required by the command.
* @param {object} [options] An options object according to the schema.
* @returns {string[]} An arguments array that can be passed via command line.
* @param command The literal name of the command.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Undefined paramName used in non-boolean types

When OptionsSchema entries have only invertedParam without param, the paramName variable becomes undefined. For non-boolean types like string or number, line 237 concatenates undefined into the arguments array, creating invalid command-line arguments. This case needs proper handling to skip entries without a param for non-boolean types.

Fix in Cursor Fix in Web

* @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, schema, options) {
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.
* @returns {string}
*/
function getPath() {
function getPath(): string {
return mockedBinaryPath !== undefined ? mockedBinaryPath : getBinaryPath();
}

Expand All @@ -280,17 +278,23 @@ function getPath() {
* const output = await execute(['--version']);
* expect(output.trim()).toBe('sentry-cli x.y.z');
*
* @param {string[]} args Command line arguments passed to `sentry-cli`.
* @param {boolean} live can be set to:
* @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 {boolean} silent Disable stdout for silents build (CI/Webpack Stats, ...)
* @param {string} [configFile] Relative or absolute path to the configuration file.
* @param {import('./index').SentryCliOptions} [config] More configuration to pass to the CLI
* @returns {Promise<string>} A promise that resolves to the standard output.
* @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, live, silent, configFile, config = {}) {
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;
Expand Down Expand Up @@ -353,7 +357,7 @@ function getProjectFlagsFromOptions({ projects = [] } = {}) {
return projects.reduce((flags, project) => flags.concat('-p', project), []);
}

module.exports = {
export {
execute,
getPath,
getProjectFlagsFromOptions,
Expand Down
50 changes: 24 additions & 26 deletions lib/index.js → lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
'use strict';

const pkgInfo = require('../package.json');
const helper = require('./helper');
const Releases = require('./releases');
import * as pkgInfo from '../package.json';
import * as helper from './helper';
import Releases from './releases';
import type { SentryCliOptions } from './types';

/**
* @typedef {import('./types').SentryCliOptions} SentryCliOptions
* @typedef {import('./types').SentryCliUploadSourceMapsOptions} SentryCliUploadSourceMapsOptions
* @typedef {import('./types').SourceMapsPathDescriptor} SourceMapsPathDescriptor
* @typedef {import('./types').SentryCliNewDeployOptions} SentryCliNewDeployOptions
* @typedef {import('./types').SentryCliCommitsOptions} SentryCliCommitsOptions
* @typedef {import('./types').SentryCliReleases} SentryCliReleases
*/
export type {
SentryCliOptions,
SentryCliUploadSourceMapsOptions,
SourceMapsPathDescriptor,
SentryCliNewDeployOptions,
SentryCliCommitsOptions,
} from './types';

/**
* Interface to and wrapper around the `sentry-cli` executable.
Expand All @@ -28,55 +28,53 @@ const Releases = require('./releases');
* const release = await cli.releases.proposeVersion());
* console.log(release);
*/
class SentryCli {
export class SentryCli {
public releases: Releases;

/**
* Creates a new `SentryCli` instance.
*
* If the `configFile` parameter is specified, configuration located in the default
* location and the value specified in the `SENTRY_PROPERTIES` environment variable is
* overridden.
*
* @param {string | null} [configFile] - Path to Sentry CLI config properties, as described in https://docs.sentry.io/learn/cli/configuration/#properties-files.
* @param configFile Path to Sentry CLI config properties, as described in https://docs.sentry.io/learn/cli/configuration/#properties-files.
* By default, the config file is looked for upwards from the current path and defaults from ~/.sentryclirc are always loaded.
* This value will update `SENTRY_PROPERTIES` env variable.
* @param {SentryCliOptions} [options] - More options to pass to the CLI
* @param options More options to pass to the CLI
*/
constructor(configFile, options) {
constructor(public configFile: string | null, public options: SentryCliOptions) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: SentryCli constructor breaks backwards compatibility

The constructor declares both configFile and options as required parameters without default values, but existing code calls new SentryCli() with no arguments. The original JavaScript version allowed both parameters to be optional. This breaks backwards compatibility beyond just the import syntax change documented in the CHANGELOG.

Fix in Cursor Fix in Web

if (typeof configFile === 'string') {
this.configFile = configFile;
}
this.options = options || { silent: false };
this.releases = new Releases({ ...this.options, configFile });
this.releases = new Releases(this.options, configFile);
}

/**
* Returns the version of the installed `sentry-cli` binary.
* @returns {string}
*/
static getVersion() {
static getVersion(): string {
return pkgInfo.version;
}

/**
* Returns an absolute path to the `sentry-cli` binary.
* @returns {string}
*/
static getPath() {
static getPath(): string {
return helper.getPath();
}

/**
* See {helper.execute} docs.
* @param {string[]} args Command line arguments passed to `sentry-cli`.
* @param {boolean} live can be set to:
* @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.
* @returns {Promise<string>} A promise that resolves to the standard output.
* @returns A promise that resolves to the standard output.
*/
execute(args, live) {
execute(args: string[], live: boolean): Promise<string> {
return helper.execute(args, live, this.options.silent, this.configFile, this.options);
}
}

module.exports = SentryCli;
8 changes: 3 additions & 5 deletions lib/logger.js → lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
'use strict';

const format = require('util').format;
import { format } from 'node:util';

module.exports = class Logger {
constructor(stream) {
this.stream = stream;
}
export class Logger {
constructor(public stream: NodeJS.WriteStream) {}

log() {
const message = format(...arguments);
Expand Down
4 changes: 2 additions & 2 deletions lib/releases/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const SentryCli = require('../..');
const { SentryCli } = require('../..');

describe('SentryCli releases', () => {
afterEach(() => {
Expand All @@ -23,7 +23,7 @@ describe('SentryCli releases', () => {
beforeEach(() => {
mockExecute.mockClear();
// eslint-disable-next-line global-require
const SentryCliLocal = require('../..');
const { SentryCli: SentryCliLocal } = require('../..');
cli = new SentryCliLocal();
});
describe('new', () => {
Expand Down
Loading