Skip to content

Commit

Permalink
fix #274: avoid npm for install (also closes #93)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 19, 2020
1 parent 5f37fe7 commit 8da1b58
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 58 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

Previously the `extends` field in `tsconfig.json` only worked with relative paths (paths starting with `./` or `../`). Now this field can also take a package path, which will be resolved by looking for the package in the `node_modules` directory.

* Install script now avoids the `npm` command ([#274](https://github.com/evanw/esbuild/issues/274))

The install script now downloads the binary directly from npmjs.org instead of using the `npm` command to install the package. This should be more compatible with unusual node environments (e.g. having multiple old copies of npm installed).

## 0.6.3

* Fix `/* @__PURE__ */` IIFEs at start of statement ([#258](https://github.com/evanw/esbuild/issues/258))
Expand Down
95 changes: 37 additions & 58 deletions npm/esbuild/install.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const child_process = require('child_process');
const zlib = require('zlib');
const https = require('https');
const version = require('./package.json').version;
const installDir = path.join(__dirname, '.install');
const binPath = path.join(__dirname, 'bin', 'esbuild');
const stampPath = path.join(__dirname, 'stamp.txt');

Expand All @@ -16,86 +16,65 @@ function installBinaryFromPackage(package, fromPath, toPath) {
return;
}

// Clone the environment without "npm_" environment variables. If we don't do
// this, invoking this script via "npm install -g esbuild" will hang because
// our call to "npm install" below will magically be transformed into
// "npm install -g" and, I assume, deadlock waiting for the global lock.
const env = {};
for (const key in process.env) {
if (!key.startsWith('npm_')) {
env[key] = process.env[key];
}
}

// Run "npm install" recursively to install this specific package
fs.mkdirSync(installDir);
fs.writeFileSync(path.join(installDir, 'package.json'), '{}');
child_process.execSync(`npm install --silent --prefer-offline --no-audit --progress=false ${package}@${version}`,
{ cwd: installDir, stdio: 'inherit', env });

// Move the binary from the nested package into our package
fs.renameSync(fromPath, toPath);
// Download the package from npm
let url = `https://registry.npmjs.org/${package}/-/${package}-${version}.tgz`
downloadURL(url, buffer => {
// Extract the binary executable from the package
fs.writeFileSync(toPath, extractFileFromTarGzip(buffer, fromPath));

// Remove the install directory afterwards to avoid tripping up tools that scan
// for nested directories named "node_modules" and make assumptions. See this
// issue for an example: https://github.com/ds300/patch-package/issues/243
removeRecursive(installDir);
// Mark the operation as successful so this script is idempotent
fs.writeFileSync(stampPath, '');
});
}

// Mark the operation as successful so this script is idempotent
fs.writeFileSync(stampPath, '');
function downloadURL(url, done) {
https.get(url, res => {
let chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => done(Buffer.concat(chunks)));
}).on('error', err => { throw err; });
}

function removeRecursive(dir) {
for (const entry of fs.readdirSync(dir)) {
const entryPath = path.join(dir, entry);
let stats;
try {
stats = fs.lstatSync(entryPath);
} catch (e) {
continue; // Guard against https://github.com/nodejs/node/issues/4760
}
if (stats.isDirectory()) {
removeRecursive(entryPath);
} else {
fs.unlinkSync(entryPath);
function extractFileFromTarGzip(buffer, file) {
buffer = zlib.unzipSync(buffer);
let str = (i, n) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, '');
let offset = 0;
while (offset < buffer.length) {
let name = str(offset, 100);
let size = parseInt(str(offset + 124, 12), 8);
offset += 512;
if (!isNaN(size)) {
if (name === file) return buffer.subarray(offset, offset + size);
offset += (size + 511) & ~511;
}
}
fs.rmdirSync(dir);
throw new Error(`Could not find ${JSON.stringify(file)}`)
}

function installOnUnix(package) {
if (process.env.ESBUILD_BIN_PATH_FOR_TESTS) {
fs.unlinkSync(binPath);
fs.symlinkSync(process.env.ESBUILD_BIN_PATH_FOR_TESTS, binPath);
} else {
installBinaryFromPackage(
package,
path.join(installDir, 'node_modules', package, 'bin', 'esbuild'),
binPath
);
installBinaryFromPackage(package, 'package/bin/esbuild', binPath);
}
}

function installOnWindows() {
const exePath = path.join(__dirname, 'esbuild.exe');
if (process.env.ESBUILD_BIN_PATH_FOR_TESTS) {
fs.symlinkSync(process.env.ESBUILD_BIN_PATH_FOR_TESTS, exePath);
} else {
installBinaryFromPackage(
'esbuild-windows-64',
path.join(installDir, 'node_modules', 'esbuild-windows-64', 'esbuild.exe'),
exePath
);
}
fs.writeFileSync(
binPath,
`#!/usr/bin/env node
const path = require('path');
const esbuild_exe = path.join(__dirname, '..', 'esbuild.exe');
const child_process = require('child_process');
child_process.spawnSync(esbuild_exe, process.argv.slice(2), { stdio: 'inherit' });
`
);
`);
const exePath = path.join(__dirname, 'esbuild.exe');
if (process.env.ESBUILD_BIN_PATH_FOR_TESTS) {
fs.symlinkSync(process.env.ESBUILD_BIN_PATH_FOR_TESTS, exePath);
} else {
installBinaryFromPackage('esbuild-windows-64', 'package/esbuild.exe', exePath);
}
}

const knownUnixlikePackages = {
Expand Down

0 comments on commit 8da1b58

Please sign in to comment.