Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(npm): Add support for proxies in the NPM installer #230

Merged
merged 13 commits into from
Jan 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmignore
Expand Up @@ -7,6 +7,7 @@
!LICENSE
!bin
!js
!scripts/install.js

# ignore JS tests
__mocks__
Expand Down
9 changes: 0 additions & 9 deletions .travis.yml
Expand Up @@ -19,15 +19,6 @@ matrix:
env: TARGET=x86_64-apple-darwin

# Test minimum js
- os: linux
sudo: false
language: node_js
node_js: "0.12"
install: true
script: npm install --production
env: SENTRYCLI_LOCAL_CDNURL="http://localhost:8999/"

# Node.js 4
- os: linux
sudo: false
language: node_js
Expand Down
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -61,6 +61,10 @@ Another option is to use the environment variable `SENTRYCLI_CDNURL`.
SENTRYCLI_CDNURL=https://mymirror.local/path npm install @sentry/cli
```

If you're installing the CLI with NPM from behind a proxy, the install script will
use either NPM's configured HTTPS proxy server, or the value from your `HTTPS_PROXY`
environment variable.

### Homebrew

A homebrew recipe is provided in the `getsentry/tools` tap:
Expand Down
184 changes: 0 additions & 184 deletions bin/install-sentry-cli

This file was deleted.

9 changes: 6 additions & 3 deletions package.json
Expand Up @@ -3,7 +3,7 @@
"version": "1.28.2",
"description": "A command line utility to work with Sentry. https://docs.sentry.io/hosted/learn/cli/",
"scripts": {
"install": "node ./bin/install-sentry-cli",
"install": "node scripts/install.js",
"fix:eslint": "eslint --fix 'bin/*' js",
"fix:prettier": "prettier --write 'bin/*' 'js/**/*.js'",
"fix": "npm-run-all fix:eslint fix:prettier",
Expand All @@ -30,7 +30,10 @@
"sentry-cli": "bin/sentry-cli"
},
"dependencies": {
"progress": "2.0.0"
"https-proxy-agent": "^2.1.1",
"node-fetch": "^1.7.3",
"progress": "2.0.0",
"proxy-from-env": "^1.0.0"
},
"homepage": "https://docs.sentry.io/hosted/learn/cli/",
"license": "BSD-3-Clause",
Expand All @@ -44,7 +47,7 @@
"prettier-check": "^2.0.0"
},
"engines": {
"node": ">= 0.12.18"
"node": ">= 4.5.0"
},
"jest": {
"collectCoverage": true,
Expand Down
File renamed without changes.
146 changes: 146 additions & 0 deletions scripts/install.js
@@ -0,0 +1,146 @@
#!/usr/bin/env node
'use strict';

const os = require('os');
const fs = require('fs');
const path = require('path');

const HttpsProxyAgent = require('https-proxy-agent');
const fetch = require('node-fetch');
const ProgressBar = require('progress');
const Proxy = require('proxy-from-env');

const pkgInfo = require('../package.json');
const helper = require('../js/helper');

const CDN_URL =
process.env.SENTRYCLI_LOCAL_CDNURL ||
process.env.npm_config_sentrycli_cdnurl ||
process.env.SENTRYCLI_CDNURL ||
'https://github.com/getsentry/sentry-cli/releases/download';

function getDownloadUrl(platform, arch) {
const releasesUrl = `${CDN_URL}/${pkgInfo.version}/sentry-cli-`;
switch (platform) {
case 'darwin':
return releasesUrl + 'Darwin-x86_64';
case 'win32':
return arch.indexOf('64') > -1
? releasesUrl + 'Windows-x86_64.exe'
: releasesUrl + 'Windows-i686.exe';
case 'linux':
case 'freebsd':
return arch.indexOf('64') > -1
? releasesUrl + 'Linux-x86_64'
: releasesUrl + 'Linux-i686';
default:
return null;
}
}

function createProgressBar(name, total) {
if (process.stdout.isTTY) {
return new ProgressBar(`fetching ${name} :bar :percent :etas`, {
complete: '█',
incomplete: '░',
width: 20,
total
});
}

if (/yarn/.test(process.env.npm_config_user_agent)) {
let pct = null;
let current = 0;
return {
tick: length => {
current += length;
const next = Math.round(current / total * 100);
if (next > pct) {
pct = next;
process.stdout.write(`fetching ${name} ${pct}%\n`);
}
}
};
}

return { tick: () => {} };
}

function downloadBinary() {
const arch = os.arch();
const platform = os.platform();
const outputPath = path.resolve(
__dirname,
platform === 'win32' ? 'sentry-cli.exe' : '../sentry-cli'
);

if (fs.existsSync(outputPath)) {
return Promise.resolve();
}

const downloadUrl = getDownloadUrl(platform, arch);
if (!downloadUrl) {
return Promise.reject(new Error(`unsupported target ${platform}-${arch}`));
}

const proxyUrl = Proxy.getProxyForUrl(downloadUrl);
const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null;

// temporary fix for https://github.com/TooTallNate/node-https-proxy-agent/pull/43
if (agent) {
agent.defaultPort = 443;
}

return fetch(downloadUrl, { redirect: 'follow', agent }).then(response => {
if (!response.ok) {
throw new Error('Received ' + response.status + ': ' + response.statusText);
}

const name = downloadUrl.match(/.*\/(.*?)$/)[1];
const total = parseInt(response.headers.get('content-length'), 10);
const progressBar = createProgressBar(name, total);

return new Promise((resolve, reject) => {
response.body
.on('error', e => reject(e))
.on('data', chunk => progressBar.tick(chunk.length))
.pipe(fs.createWriteStream(outputPath, { mode: '0755' }))
.on('error', e => reject(e))
.on('finish', () => resolve());
});
});
}

function checkVersion() {
return helper.execute(['--version']).then(output => {
const version = output.replace('sentry-cli ', '').trim();
const expected = process.env.SENTRYCLI_LOCAL_CDNURL ? 'DEV' : pkgInfo.version;
if (version !== expected) {
throw new Error(`Unexpected sentry-cli version: ${version}`);
}
});
}

if (process.env.SENTRYCLI_LOCAL_CDNURL) {
// For testing, mock the CDN by spawning a local server
const server = require('http')
.createServer((request, response) => {
var contents = fs.readFileSync(path.join(__dirname, '../js/__mocks__/sentry-cli'));
response.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Length': String(contents.byteLength)
});
response.end(contents);
})
.listen(8999);

process.on('exit', () => server.close());
}

downloadBinary()
.then(() => checkVersion())
.then(() => process.exit(0))
.catch(e => {
console.error(e.toString());
process.exit(1);
});