@sentry/cli 2.0.4 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  <p align="center">
2
- <img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
3
- <br />
2
+ <a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
3
+ <picture>
4
+ <source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-white.png" media="(prefers-color-scheme: dark)" />
5
+ <source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
6
+ <img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" alt="Sentry" width="280">
7
+ </picture>
8
+ </a>
4
9
  </p>
5
10
 
6
11
  # Official Sentry Command Line Interface
@@ -20,10 +25,31 @@ fastlane tools.
20
25
 
21
26
  ## Installation
22
27
 
23
- The recommended way to install is with everybody's favorite curl to bash:
28
+ If you are on OS X or Linux, you can use the automated downloader which will fetch the latest release version for you and install it:
24
29
 
25
30
  curl -sL https://sentry.io/get-cli/ | bash
26
31
 
32
+ We do however, encourage you to pin the specific version of the CLI, so your builds are always reproducible.
33
+ To do that, you can use the exact same method, with an additional version specifier:
34
+
35
+ curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=2.0.4 bash
36
+
37
+ This will automatically download the correct version of `sentry-cli` for your operating system and install it. If necessary, it will prompt for your admin password for `sudo`. For a different installation location or for systems without `sudo` (like Windows), you can `export INSTALL_DIR=/custom/installation/path` before running this command.
38
+
39
+ If you are using `sentry-cli` on Windows environments, [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) is required.
40
+
41
+ To verify it’s installed correctly you can bring up the help:
42
+
43
+ sentry-cli --help
44
+
45
+ ### pip
46
+
47
+ _New in 2.14.3_: `sentry-cli` can also be installed using `pip`:
48
+
49
+ ```bash
50
+ pip install sentry-cli
51
+ ```
52
+
27
53
  ### Node
28
54
 
29
55
  Additionally you can also install this binary via npm:
@@ -79,6 +105,15 @@ docker pull getsentry/sentry-cli
79
105
  docker run --rm -v $(pwd):/work getsentry/sentry-cli --help
80
106
  ```
81
107
 
108
+ Starting version _`2.8.0`_, in case you see `"error: config value 'safe.directory' was not found;"` message,
109
+ you also need to correctly set UID and GID of mounted volumes like so:
110
+
111
+ ```sh
112
+ docker run --rm -u "$(id -u):$(id -g)" -v $(pwd):/work getsentry/sentry-cli --help
113
+ ```
114
+
115
+ This is required due to security issue in older `git` implementations. See [here](https://github.blog/2022-04-12-git-security-vulnerability-announced/) for more details.
116
+
82
117
  ## Compiling
83
118
 
84
119
  In case you want to compile this yourself, you need to install at minimum the
package/checksums.txt CHANGED
@@ -1,9 +1,9 @@
1
- sentry-cli-Darwin-arm64=6ad23605f6e345375a80aeebc5db7dc9e42226abf63e39dd91730b8b831511a8
2
- sentry-cli-Darwin-universal=283370a6bf7356a422f39ab805094edefe15f087d512c4579b60e584cf4a3e20
3
- sentry-cli-Darwin-x86_64=f1f3ea7f5d1ed481080e8eb150b3adbf4ab02890f9ec1227e1703f7ec8e25c84
4
- sentry-cli-Linux-aarch64=e796a2d535cc59c6d72e5e19aa7604e964328b5a7e09a62f695e0d9acde9b07e
5
- sentry-cli-Linux-armv7=19ea3f1a843b37df82b310ab82e330f6f26e4ff8b20b4c98c2a47c894c2164ff
6
- sentry-cli-Linux-i686=abb2f9daeb78ba01ae3f2c36e4d64fb54fd3e937683c25f980872331c2a0e1b3
7
- sentry-cli-Linux-x86_64=46b6d913e3bfeccaf3170493942120a1b7b11b07c04e932b8fa643187c621fdf
8
- sentry-cli-Windows-i686.exe=7df3c7e672dae7338245a07685a05847dfa8d18daea51bbbab78feb95a7c0199
9
- sentry-cli-Windows-x86_64.exe=ecbc89c357bf67cfa9a2f937a10253c46f1183e8480a86c688885a0e3017003b
1
+ sentry-cli-Darwin-arm64=9bfaca2efe775f0ae4f3683d325fa98ce19a46acbeafd137674f3b21822564ab
2
+ sentry-cli-Darwin-universal=94e1249b692301e7287ce059bbff83a68d9568e134bcb196bee79a20813b0c69
3
+ sentry-cli-Darwin-x86_64=c0356f589eb28c9dcd530b6cdbadefac9e4ee7ddafb74ceae0754582fbead60e
4
+ sentry-cli-Linux-aarch64=0e04e810af16520b39a4edb3dd163edd5991de4ea479e55834cf4ac7e36bfa90
5
+ sentry-cli-Linux-armv7=1b97cce44b32ef10f992dbf627e75632ad72cdc00d9b06e9d75b7676f632ae86
6
+ sentry-cli-Linux-i686=2003f137a2038c431723508680c01d058e35eea2db789f0ffc22d667c2c3f9be
7
+ sentry-cli-Linux-x86_64=272fefd13e7076cec6aa1896820f4edf7d1e4478781a2c3538cfb7a62dee1214
8
+ sentry-cli-Windows-i686.exe=379c2cf9434a670529aa51d1c9ff381a30106f29639852484252cdd017da0d06
9
+ sentry-cli-Windows-x86_64.exe=a33e783bfdf70276aaa85cad6db38b994406f58119269c3f30976ba498ed86a6
package/js/helper.js CHANGED
@@ -1,21 +1,29 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const childProcess = require('child_process');
4
5
 
5
6
  /**
6
- * Absolute path to the sentry-cli binary (platform dependent).
7
- * @type {string}
7
+ * This convoluted function resolves the path to the `sentry-cli` binary in a
8
+ * way that can't be analysed by @vercel/nft.
9
+ *
10
+ * Without this, the binary can be detected as an asset and included by bundlers
11
+ * that use @vercel/nft.
12
+ * @returns {string} The path to the sentry-cli binary
8
13
  */
9
- let binaryPath = eval(
10
- "require('path').resolve(__dirname, require('os').platform() === 'win32' ? '..\\sentry-cli.exe' : '../sentry-cli')"
11
- );
14
+ function getBinaryPath() {
15
+ const parts = [];
16
+ parts.push(__dirname);
17
+ parts.push('..');
18
+ parts.push(`sentry-cli${process.platform === 'win32' ? '.exe' : ''}`);
19
+ return path.resolve(...parts);
20
+ }
12
21
 
13
22
  /**
14
- * NOTE: `eval` usage is a workaround for @vercel/nft detecting the binary itself as the hard dependency
15
- * and effectively always including it in the bundle, which is not what we want.
16
- * ref: https://github.com/getsentry/sentry-javascript/issues/3865
17
- * ref: https://github.com/vercel/nft/issues/203
23
+ * Absolute path to the sentry-cli binary (platform dependent).
24
+ * @type {string}
18
25
  */
26
+ let binaryPath = getBinaryPath();
19
27
 
20
28
  /**
21
29
  * Overrides the default binary path with a mock value, useful for testing.
@@ -53,7 +61,7 @@ function mockBinaryPath(mockPath) {
53
61
  function serializeOptions(schema, options) {
54
62
  return Object.keys(schema).reduce((newOptions, option) => {
55
63
  const paramValue = options[option];
56
- if (paramValue === undefined) {
64
+ if (paramValue === undefined || paramValue === null) {
57
65
  return newOptions;
58
66
  }
59
67
 
@@ -164,6 +172,12 @@ async function execute(args, live, silent, configFile, config = {}) {
164
172
  }
165
173
  if (config.customHeader) {
166
174
  env.CUSTOM_HEADER = config.customHeader;
175
+ } else if (config.headers) {
176
+ const headers = Object.entries(config.headers).flatMap(([key, value]) => [
177
+ '--header',
178
+ `${key}:${value}`,
179
+ ]);
180
+ args = [...headers, ...args];
167
181
  }
168
182
  return new Promise((resolve, reject) => {
169
183
  if (live === true) {
package/js/index.d.ts CHANGED
@@ -38,11 +38,6 @@ declare module '@sentry/cli' {
38
38
  * This value will update `SENTRY_VCS_REMOTE` env variable.
39
39
  */
40
40
  vcsRemote?: string;
41
- /**
42
- * Unique identifier for the distribution, used to further segment your release.
43
- * Usually your build number.
44
- */
45
- dist?: string;
46
41
  /**
47
42
  * If true, all logs are suppressed.
48
43
  */
@@ -52,6 +47,11 @@ declare module '@sentry/cli' {
52
47
  * This value will update `CUSTOM_HEADER` env variable.
53
48
  */
54
49
  customHeader?: string;
50
+ /**
51
+ * Headers added to every outgoing network request.
52
+ * This value does not set any env variable, and is overridden by `customHeader`.
53
+ */
54
+ headers?: Record<string, string>;
55
55
  }
56
56
 
57
57
  /**
@@ -59,7 +59,9 @@ declare module '@sentry/cli' {
59
59
  * case `paths` takes the place of `include` in the options so as to make it
60
60
  * clear that this is not recursive.
61
61
  */
62
- export type SourceMapsPathDescriptor = Omit<SentryCliUploadSourceMapsOptions, 'include'> & { paths: string[] }
62
+ export type SourceMapsPathDescriptor = Omit<SentryCliUploadSourceMapsOptions, 'include'> & {
63
+ paths: string[];
64
+ };
63
65
 
64
66
  export interface SentryCliUploadSourceMapsOptions {
65
67
  /**
@@ -85,6 +87,11 @@ declare module '@sentry/cli' {
85
87
  * This prevents the automatic detection of sourcemap references.
86
88
  */
87
89
  sourceMapReference?: boolean;
90
+ /**
91
+ * Enable artifacts deduplication prior to uploading. This will skip uploading
92
+ * any artifacts that are already present on the server. Defaults to `true`.
93
+ */
94
+ dedupe?: boolean;
88
95
  /**
89
96
  * When paired with the rewrite option this will remove a prefix from uploaded files.
90
97
  * For instance you can use this to remove a path that is build machine specific.
@@ -116,6 +123,15 @@ declare module '@sentry/cli' {
116
123
  * By default the following file extensions are processed: js, map, jsbundle and bundle.
117
124
  */
118
125
  ext?: string[];
126
+ /**
127
+ * Unique identifier for the distribution, used to further segment your release.
128
+ * Usually your build number.
129
+ */
130
+ dist?: string;
131
+ /**
132
+ * Use new Artifact Bundles upload, that enables use of Debug ID for Source Maps discovery.
133
+ */
134
+ useArtifactBundle?: boolean;
119
135
  }
120
136
 
121
137
  export interface SentryCliNewDeployOptions {
@@ -176,31 +192,19 @@ declare module '@sentry/cli' {
176
192
  }
177
193
 
178
194
  export interface SentryCliReleases {
179
- ['new'](
180
- release: string,
181
- options?: { projects: string[] } | string[]
182
- ): Promise<string>;
195
+ ['new'](release: string, options?: { projects: string[] } | string[]): Promise<string>;
183
196
 
184
- setCommits(
185
- release: string,
186
- options: SentryCliCommitsOptions
187
- ): Promise<string>;
197
+ setCommits(release: string, options: SentryCliCommitsOptions): Promise<string>;
188
198
 
189
- finalize(release: string): Promise<string>
199
+ finalize(release: string): Promise<string>;
190
200
 
191
- proposeVersion(): Promise<string>
201
+ proposeVersion(): Promise<string>;
192
202
 
193
- uploadSourceMaps(
194
- release: string,
195
- options: SentryCliUploadSourceMapsOptions
196
- ): Promise<string>
203
+ uploadSourceMaps(release: string, options: SentryCliUploadSourceMapsOptions): Promise<string>;
197
204
 
198
205
  listDeploys(release: string): Promise<string>;
199
206
 
200
- newDeploy(
201
- release: string,
202
- options: SentryCliNewDeployOptions
203
- ): Promise<string>
207
+ newDeploy(release: string, options: SentryCliNewDeployOptions): Promise<string>;
204
208
 
205
209
  execute(args: string[], live: boolean): Promise<string>;
206
210
  }
@@ -214,14 +218,14 @@ declare module '@sentry/cli' {
214
218
  * This value will update `SENTRY_PROPERTIES` env variable.
215
219
  * @param options {@link SentryCliOptions}
216
220
  */
217
- constructor(configFile?: string | null, options?: SentryCliOptions)
221
+ constructor(configFile?: string | null, options?: SentryCliOptions);
218
222
 
219
223
  public configFile?: string;
220
224
  public options?: SentryCliOptions;
221
- public releases: SentryCliReleases
225
+ public releases: SentryCliReleases;
222
226
 
223
- public static getVersion(): string
224
- public static getPath(): string
225
- public execute(args: string[], live: boolean): Promise<string>
227
+ public static getVersion(): string;
228
+ public static getPath(): string;
229
+ public execute(args: string[], live: boolean): Promise<string>;
226
230
  }
227
231
  }
package/js/logger.js ADDED
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const format = require('util').format;
4
+
5
+ module.exports = class Logger {
6
+ constructor(stream) {
7
+ this.stream = stream;
8
+ }
9
+
10
+ log() {
11
+ const message = format(...arguments);
12
+ this.stream.write(`[sentry-cli] ${message}\n`);
13
+ }
14
+ };
@@ -140,13 +140,15 @@ class Releases {
140
140
  * ignoreFile: null, // path to a file with ignore rules
141
141
  * rewrite: false, // preprocess sourcemaps before uploading
142
142
  * sourceMapReference: true, // add a source map reference to source files
143
+ * dedupe: true, // deduplicate already uploaded files
143
144
  * stripPrefix: [], // remove certain prefices from filenames
144
145
  * stripCommonPrefix: false, // guess common prefices to remove from filenames
145
146
  * validate: false, // validate source maps and cancel the upload on error
146
147
  * urlPrefix: '', // add a prefix source map urls after stripping them
147
148
  * urlSuffix: '', // add a suffix source map urls after stripping them
148
149
  * ext: ['js', 'map', 'jsbundle', 'bundle'], // override file extensions to scan for
149
- * projects: ['node'] // provide a list of projects
150
+ * projects: ['node'], // provide a list of projects
151
+ * decompress: false // decompress gzip files before uploading
150
152
  * });
151
153
  *
152
154
  * @param {string} release Unique name of the release.
@@ -11,6 +11,10 @@ module.exports = {
11
11
  param: '--dist',
12
12
  type: 'string',
13
13
  },
14
+ decompress: {
15
+ param: '--decompress',
16
+ type: 'boolean',
17
+ },
14
18
  rewrite: {
15
19
  param: '--rewrite',
16
20
  invertedParam: '--no-rewrite',
@@ -20,6 +24,10 @@ module.exports = {
20
24
  invertedParam: '--no-sourcemap-reference',
21
25
  type: 'boolean',
22
26
  },
27
+ dedupe: {
28
+ invertedParam: '--no-dedupe',
29
+ type: 'boolean',
30
+ },
23
31
  stripPrefix: {
24
32
  param: '--strip-prefix',
25
33
  type: 'array',
@@ -44,4 +52,8 @@ module.exports = {
44
52
  param: '--ext',
45
53
  type: 'array',
46
54
  },
55
+ useArtifactBundle: {
56
+ param: '--use-artifact-bundle',
57
+ type: 'boolean',
58
+ },
47
59
  };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@sentry/cli",
3
- "version": "2.0.4",
3
+ "version": "2.1.4",
4
4
  "description": "A command line utility to work with Sentry. https://docs.sentry.io/hosted/learn/cli/",
5
5
  "repository": "git://github.com/getsentry/sentry-cli.git",
6
6
  "homepage": "https://docs.sentry.io/hosted/learn/cli/",
7
7
  "author": "Sentry",
8
8
  "license": "BSD-3-Clause",
9
9
  "engines": {
10
- "node": ">= 12"
10
+ "node": ">= 10"
11
11
  },
12
12
  "main": "js/index.js",
13
13
  "types": "js/index.d.ts",
@@ -17,13 +17,13 @@
17
17
  "dependencies": {
18
18
  "https-proxy-agent": "^5.0.0",
19
19
  "node-fetch": "^2.6.7",
20
- "npmlog": "^6.0.1",
21
20
  "progress": "^2.0.3",
22
21
  "proxy-from-env": "^1.1.0",
23
22
  "which": "^2.0.2"
24
23
  },
25
24
  "devDependencies": {
26
- "eslint": "^8.13.0",
25
+ "@vercel/nft": "^0.22.1",
26
+ "eslint": "^7.32.0",
27
27
  "eslint-config-prettier": "^8.5.0",
28
28
  "jest": "^27.5.1",
29
29
  "npm-run-all": "^4.1.5",
@@ -34,11 +34,12 @@
34
34
  "fix": "npm-run-all fix:eslint fix:prettier",
35
35
  "fix:eslint": "eslint --fix bin/* scripts/**/*.js js/**/*.js",
36
36
  "fix:prettier": "prettier --write bin/* scripts/**/*.js js/**/*.js",
37
- "test": "npm-run-all test:jest test:eslint test:prettier",
37
+ "test": "npm-run-all test:jest test:eslint test:prettier test:vercel-nft",
38
38
  "test:jest": "jest",
39
39
  "test:watch": "jest --watch --notify",
40
40
  "test:eslint": "eslint bin/* scripts/**/*.js js/**/*.js",
41
- "test:prettier": "prettier --check bin/* scripts/**/*.js js/**/*.js"
41
+ "test:prettier": "prettier --check bin/* scripts/**/*.js js/**/*.js",
42
+ "test:vercel-nft": "node scripts/test-vercel-nft.js"
42
43
  },
43
44
  "jest": {
44
45
  "collectCoverage": true,
@@ -46,5 +47,9 @@
46
47
  "testPathIgnorePatterns": [
47
48
  "<rootDir>/src"
48
49
  ]
50
+ },
51
+ "volta": {
52
+ "node": "10.24.1",
53
+ "yarn": "1.22.19"
49
54
  }
50
55
  }
@@ -15,11 +15,13 @@ const fetch = require('node-fetch');
15
15
  const HttpsProxyAgent = require('https-proxy-agent');
16
16
  const ProgressBar = require('progress');
17
17
  const Proxy = require('proxy-from-env');
18
- const npmLog = require('npmlog');
19
18
  const which = require('which');
20
19
 
21
20
  const helper = require('../js/helper');
22
21
  const pkgInfo = require('../package.json');
22
+ const Logger = require('../js/logger');
23
+
24
+ const logger = new Logger(getLogStream('stderr'));
23
25
 
24
26
  const CDN_URL =
25
27
  process.env.SENTRYCLI_LOCAL_CDNURL ||
@@ -47,9 +49,10 @@ function shouldRenderProgressBar() {
47
49
  const silentFlag = process.argv.some((v) => v === '--silent');
48
50
  const silentConfig = process.env.npm_config_loglevel === 'silent';
49
51
  const silentEnv = process.env.SENTRYCLI_NO_PROGRESS_BAR;
50
- const ciEnv = process.env.CI === 'true';
52
+ const ciEnv = process.env.CI === 'true' || process.env.CI === '1';
53
+ const notTTY = !process.stdout.isTTY;
51
54
  // If any of possible options is set, skip rendering of progress bar
52
- return !(silentFlag || silentConfig || silentEnv || ciEnv);
55
+ return !(silentFlag || silentConfig || silentEnv || ciEnv || notTTY);
53
56
  }
54
57
 
55
58
  function getDownloadUrl(platform, arch) {
@@ -120,13 +123,17 @@ function createProgressBar(name, total) {
120
123
  }
121
124
 
122
125
  function npmCache() {
123
- const env = process.env;
124
- return (
125
- env.npm_config_cache ||
126
- env.npm_config_cache_folder ||
127
- env.npm_config_yarn_offline_mirror ||
128
- (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'))
129
- );
126
+ const keys = ['npm_config_cache', 'npm_config_cache_folder', 'npm_config_yarn_offline_mirror'];
127
+
128
+ for (let key of [...keys, ...keys.map((k) => k.toUpperCase())]) {
129
+ if (process.env[key]) return process.env[key];
130
+ }
131
+
132
+ if (process.env.APPDATA) {
133
+ return path.join(process.env.APPDATA, 'npm-cache');
134
+ }
135
+
136
+ return path.join(os.homedir(), '.npm');
130
137
  }
131
138
 
132
139
  function getCachedPath(url) {
@@ -156,14 +163,14 @@ function validateChecksum(tempPath, name) {
156
163
  }
157
164
  }
158
165
  } catch (e) {
159
- npmLog.info(
166
+ logger.log(
160
167
  'Checksums are generated when the package is published to npm. They are not available directly in the source repository. Skipping validation.'
161
168
  );
162
169
  return;
163
170
  }
164
171
 
165
172
  if (!storedHash) {
166
- npmLog.info(`Checksum for ${name} not found, skipping validation.`);
173
+ logger.log(`Checksum for ${name} not found, skipping validation.`);
167
174
  return;
168
175
  }
169
176
 
@@ -175,7 +182,7 @@ function validateChecksum(tempPath, name) {
175
182
  `Checksum validation for ${name} failed.\nExpected: ${storedHash}\nReceived: ${currentHash}`
176
183
  );
177
184
  } else {
178
- npmLog.info('Checksum validation passed.');
185
+ logger.log('Checksum validation passed.');
179
186
  }
180
187
  }
181
188
 
@@ -187,7 +194,7 @@ async function downloadBinary() {
187
194
  if (process.env.SENTRYCLI_USE_LOCAL === '1') {
188
195
  try {
189
196
  const binPath = which.sync('sentry-cli');
190
- npmLog.info('sentry-cli', `Using local binary: ${binPath}`);
197
+ logger.log(`Using local binary: ${binPath}`);
191
198
  fs.copyFileSync(binPath, outputPath);
192
199
  return Promise.resolve();
193
200
  } catch (e) {
@@ -205,7 +212,7 @@ async function downloadBinary() {
205
212
 
206
213
  const cachedPath = getCachedPath(downloadUrl);
207
214
  if (fs.existsSync(cachedPath)) {
208
- npmLog.info('sentry-cli', `Using cached binary: ${cachedPath}`);
215
+ logger.log(`Using cached binary: ${cachedPath}`);
209
216
  fs.copyFileSync(cachedPath, outputPath);
210
217
  return;
211
218
  }
@@ -213,10 +220,10 @@ async function downloadBinary() {
213
220
  const proxyUrl = Proxy.getProxyForUrl(downloadUrl);
214
221
  const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null;
215
222
 
216
- npmLog.info('sentry-cli', `Downloading from ${downloadUrl}`);
223
+ logger.log(`Downloading from ${downloadUrl}`);
217
224
 
218
225
  if (proxyUrl) {
219
- npmLog.info('sentry-cli', `Using proxy URL: ${proxyUrl}`);
226
+ logger.log(`Using proxy URL: ${proxyUrl}`);
220
227
  }
221
228
 
222
229
  let response;
@@ -257,19 +264,29 @@ async function downloadBinary() {
257
264
  decompressor = new stream.PassThrough();
258
265
  }
259
266
  const name = downloadUrl.match(/.*\/(.*?)$/)[1];
260
- const total = parseInt(response.headers.get('content-length'), 10);
261
- const progressBar = createProgressBar(name, total);
267
+ let downloadedBytes = 0;
268
+ const totalBytes = parseInt(response.headers.get('content-length'), 10);
269
+ const progressBar = createProgressBar(name, totalBytes);
262
270
  const tempPath = getTempFile(cachedPath);
263
271
  fs.mkdirSync(path.dirname(tempPath), { recursive: true });
264
272
 
265
273
  await new Promise((resolve, reject) => {
266
274
  response.body
267
275
  .on('error', (e) => reject(e))
268
- .on('data', (chunk) => progressBar.tick(chunk.length))
276
+ .on('data', (chunk) => {
277
+ downloadedBytes += chunk.length;
278
+ progressBar.tick(chunk.length);
279
+ })
269
280
  .pipe(decompressor)
270
281
  .pipe(fs.createWriteStream(tempPath, { mode: '0755' }))
271
282
  .on('error', (e) => reject(e))
272
- .on('close', () => resolve());
283
+ .on('close', () => {
284
+ if (downloadedBytes >= totalBytes) {
285
+ resolve();
286
+ } else {
287
+ reject(new Error('connection interrupted'));
288
+ }
289
+ });
273
290
  });
274
291
 
275
292
  if (process.env.SENTRYCLI_SKIP_CHECKSUM_VALIDATION !== '1') {
@@ -305,10 +322,8 @@ if (process.env.SENTRYCLI_LOCAL_CDNURL) {
305
322
  process.on('exit', () => server.close());
306
323
  }
307
324
 
308
- npmLog.stream = getLogStream('stderr');
309
-
310
325
  if (process.env.SENTRYCLI_SKIP_DOWNLOAD === '1') {
311
- npmLog.info('sentry-cli', `Skipping download because SENTRYCLI_SKIP_DOWNLOAD=1 detected.`);
326
+ logger.log(`Skipping download because SENTRYCLI_SKIP_DOWNLOAD=1 detected.`);
312
327
  process.exit(0);
313
328
  }
314
329
 
@@ -0,0 +1,16 @@
1
+ const { nodeFileTrace } = require('@vercel/nft');
2
+
3
+ const entryPoint = require.resolve('..');
4
+
5
+ // Trace the module entrypoint
6
+ nodeFileTrace([entryPoint]).then((result) => {
7
+ console.log('@vercel/nft traced dependencies:', Array.from(result.fileList));
8
+
9
+ // If either binary is picked up, fail the test
10
+ if (result.fileList.has('sentry-cli') || result.fileList.has('sentry-cli.exe')) {
11
+ console.error('ERROR: The sentry-cli binary should not be found by @vercel/nft');
12
+ process.exit(-1);
13
+ } else {
14
+ console.log('The sentry-cli binary was not traced by @vercel/nft');
15
+ }
16
+ });
package/scripts/wheels ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import base64
4
+ import hashlib
5
+ import os.path
6
+ import shutil
7
+ import tempfile
8
+ import zipfile
9
+ from typing import NamedTuple
10
+
11
+
12
+ class Wheel(NamedTuple):
13
+ src: str
14
+ plat: str
15
+ exe: str = 'sentry-cli'
16
+
17
+
18
+ WHEELS = (
19
+ Wheel(
20
+ src='sentry-cli-Darwin-arm64',
21
+ plat='macosx_11_0_arm64',
22
+ ),
23
+ Wheel(
24
+ src='sentry-cli-Darwin-universal',
25
+ plat='macosx_11_0_universal2',
26
+ ),
27
+ Wheel(
28
+ src='sentry-cli-Darwin-x86_64',
29
+ plat='macosx_10_15_x86_64',
30
+ ),
31
+ Wheel(
32
+ src='sentry-cli-Linux-aarch64',
33
+ plat='manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_2_aarch64',
34
+ ),
35
+ Wheel(
36
+ src='sentry-cli-Linux-armv7',
37
+ plat='manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_2_armv7l',
38
+ ),
39
+ Wheel(
40
+ src='sentry-cli-Linux-i686',
41
+ plat='manylinux_2_17_i686.manylinux2014_i686.musllinux_1_2_i686',
42
+ ),
43
+ Wheel(
44
+ src='sentry-cli-Linux-x86_64',
45
+ plat='manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_2_x86_64',
46
+ ),
47
+ Wheel(
48
+ src='sentry-cli-Windows-i686.exe',
49
+ plat='win32',
50
+ exe='sentry-cli.exe',
51
+ ),
52
+ Wheel(
53
+ src='sentry-cli-Windows-x86_64.exe',
54
+ plat='win_amd64',
55
+ exe='sentry-cli.exe',
56
+ ),
57
+ )
58
+
59
+
60
+ def main() -> int:
61
+ parser = argparse.ArgumentParser()
62
+ parser.add_argument('--binaries', required=True)
63
+ parser.add_argument('--base', required=True)
64
+ parser.add_argument('--dest', required=True)
65
+ args = parser.parse_args()
66
+
67
+ expected = {wheel.src for wheel in WHEELS}
68
+ received = set(os.listdir(args.binaries))
69
+ if expected < received:
70
+ raise SystemExit(
71
+ f'Unexpected binaries:\n\n'
72
+ f'- extra: {", ".join(sorted(received - expected))}\n'
73
+ f'- missing: {", ".join(sorted(expected - received))}'
74
+ )
75
+
76
+ sdist_path = wheel_path = None
77
+ for fname in os.listdir(args.base):
78
+ if fname.endswith('.tar.gz'):
79
+ sdist_path = os.path.join(args.base, fname)
80
+ elif fname.endswith('.whl'):
81
+ wheel_path = os.path.join(args.base, fname)
82
+ else:
83
+ raise SystemExit(f'unexpected file in `--base`: {fname}')
84
+
85
+ if sdist_path is None or wheel_path is None:
86
+ raise SystemExit('expected wheel and sdist in `--base`')
87
+
88
+ os.makedirs(args.dest, exist_ok=True)
89
+ shutil.copy(sdist_path, args.dest)
90
+
91
+ for wheel in WHEELS:
92
+ binary_src = os.path.join(args.binaries, wheel.src)
93
+ binary_size = os.stat(binary_src).st_size
94
+ with open(binary_src, 'rb') as bf:
95
+ digest = hashlib.sha256(bf.read()).digest()
96
+ digest_b64 = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
97
+
98
+ basename = os.path.basename(wheel_path)
99
+ wheelname, _ = os.path.splitext(basename)
100
+ name, version, py, abi, plat = wheelname.split('-')
101
+
102
+ with tempfile.TemporaryDirectory() as tmp:
103
+ with zipfile.ZipFile(wheel_path) as zipf:
104
+ zipf.extractall(tmp)
105
+
106
+ distinfo = os.path.join(tmp, f'{name}-{version}.dist-info')
107
+ scripts = os.path.join(tmp, f'{name}-{version}.data', 'scripts')
108
+
109
+ # replace the script binary with our copy
110
+ os.remove(os.path.join(scripts, 'sentry-cli'))
111
+ shutil.copy(binary_src, os.path.join(scripts, wheel.exe))
112
+
113
+ # rewrite RECORD to include the new file
114
+ record_fname = os.path.join(distinfo, 'RECORD')
115
+ with open(record_fname) as f:
116
+ record_lines = list(f)
117
+
118
+ record = f'{name}-{version}.data/scripts/sentry-cli,'
119
+ for i, line in enumerate(record_lines):
120
+ if line.startswith(record):
121
+ record_lines[i] = (
122
+ f'{name}-{version}.data/scripts/{wheel.exe},'
123
+ f'sha256={digest_b64},'
124
+ f'{binary_size}\n'
125
+ )
126
+ break
127
+ else:
128
+ raise SystemExit(f'could not find {record!r} in RECORD')
129
+
130
+ with open(record_fname, 'w') as f:
131
+ f.writelines(record_lines)
132
+
133
+ # rewrite WHEEL to have the new tags
134
+ wheel_fname = os.path.join(distinfo, 'WHEEL')
135
+ with open(wheel_fname) as f:
136
+ wheel_lines = list(f)
137
+
138
+ for i, line in enumerate(wheel_lines):
139
+ if line.startswith('Tag: '):
140
+ wheel_lines[i:i + 1] = [
141
+ f'Tag: {py}-{abi}-{plat}\n'
142
+ for plat in wheel.plat.split('.')
143
+ ]
144
+ break
145
+ else:
146
+ raise SystemExit("could not find 'Tag: ' in WHEEL")
147
+
148
+ with open(wheel_fname, 'w') as f:
149
+ f.writelines(wheel_lines)
150
+
151
+ # write out the final zip
152
+ new_basename = f'{name}-{version}-{py}-{abi}-{wheel.plat}.whl'
153
+ tmp_new_wheel = os.path.join(tmp, new_basename)
154
+ fnames = sorted(
155
+ os.path.join(root, fname)
156
+ for root, _, fnames in os.walk(tmp)
157
+ for fname in fnames
158
+ )
159
+ with zipfile.ZipFile(tmp_new_wheel, 'w') as zipf:
160
+ for fname in fnames:
161
+ zinfo = zipfile.ZipInfo(os.path.relpath(fname, tmp))
162
+ if '/scripts/' in zinfo.filename:
163
+ zinfo.external_attr = 0o100755 << 16
164
+ with open(fname, 'rb') as fb:
165
+ zipf.writestr(zinfo, fb.read())
166
+
167
+ # move into dest
168
+ shutil.move(tmp_new_wheel, args.dest)
169
+
170
+ return 0
171
+
172
+
173
+ if __name__ == '__main__':
174
+ raise SystemExit(main())