From 8dcf35b74757a315ddbb26233d1b0d6c3fb7d083 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 1 Aug 2021 17:59:14 -0400 Subject: [PATCH] install using "optionalDependencies" --- lib/npm/install.ts | 307 ------------------------ lib/npm/node-install.ts | 77 ++++++ lib/npm/node-platform.ts | 115 +++++++++ lib/npm/node-shim.ts | 4 + lib/npm/node.ts | 36 +-- npm/esbuild-android-arm64/package.json | 5 +- npm/esbuild-darwin-64/package.json | 5 +- npm/esbuild-darwin-arm64/package.json | 5 +- npm/esbuild-freebsd-64/package.json | 5 +- npm/esbuild-freebsd-arm64/package.json | 5 +- npm/esbuild-linux-32/package.json | 5 +- npm/esbuild-linux-64/package.json | 5 +- npm/esbuild-linux-arm/package.json | 5 +- npm/esbuild-linux-arm64/package.json | 5 +- npm/esbuild-linux-mips64le/package.json | 5 +- npm/esbuild-linux-ppc64le/package.json | 5 +- npm/esbuild-openbsd-64/package.json | 5 +- npm/esbuild-sunos-64/package.json | 5 +- npm/esbuild-windows-32/package.json | 5 +- npm/esbuild-windows-64/package.json | 5 +- npm/esbuild-windows-arm64/package.json | 5 +- npm/esbuild/bin/esbuild | 13 - npm/esbuild/package.json | 18 ++ scripts/esbuild.js | 37 ++- 24 files changed, 267 insertions(+), 420 deletions(-) delete mode 100644 lib/npm/install.ts create mode 100644 lib/npm/node-install.ts create mode 100644 lib/npm/node-platform.ts create mode 100644 lib/npm/node-shim.ts delete mode 100755 npm/esbuild/bin/esbuild diff --git a/lib/npm/install.ts b/lib/npm/install.ts deleted file mode 100644 index b33058b431b..00000000000 --- a/lib/npm/install.ts +++ /dev/null @@ -1,307 +0,0 @@ -import fs = require('fs'); -import os = require('os'); -import path = require('path'); -import zlib = require('zlib'); -import https = require('https'); -import child_process = require('child_process'); - -declare const ESBUILD_VERSION: string; - -const version = ESBUILD_VERSION; -const binPath = path.join(__dirname, 'bin', 'esbuild'); - -async function installBinaryFromPackage(name: string, fromPath: string, toPath: string): Promise { - // Try to install from the cache if possible - const cachePath = getCachePath(name); - try { - // Copy from the cache - fs.copyFileSync(cachePath, toPath); - fs.chmodSync(toPath, 0o755); - - // Verify that the binary is the correct version - validateBinaryVersion(toPath); - - // Mark the cache entry as used for LRU - const now = new Date; - fs.utimesSync(cachePath, now, now); - return; - } catch { - } - - // Next, try to install using npm. This should handle various tricky cases - // such as environments where requests to npmjs.org will hang (in which case - // there is probably a proxy and/or a custom registry configured instead). - let buffer: Buffer | undefined; - let didFail = false; - try { - buffer = installUsingNPM(name, fromPath); - } catch (err: any) { - didFail = true; - console.error(`Trying to install "${name}" using npm`); - console.error(`Failed to install "${name}" using npm: ${err && err.message || err}`); - } - - // If that fails, the user could have npm configured incorrectly or could not - // have npm installed. Try downloading directly from npm as a last resort. - if (!buffer) { - const url = `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`; - console.error(`Trying to download ${JSON.stringify(url)}`); - try { - buffer = extractFileFromTarGzip(await fetch(url), fromPath); - } catch (err: any) { - console.error(`Failed to download ${JSON.stringify(url)}: ${err && err.message || err}`); - } - } - - // Give up if none of that worked - if (!buffer) { - console.error(`Install unsuccessful`); - process.exit(1); - } - - // Write out the binary executable that was extracted from the package - fs.writeFileSync(toPath, buffer, { mode: 0o755 }); - - // Verify that the binary is the correct version - try { - validateBinaryVersion(toPath); - } catch (err: any) { - console.error(`The version of the downloaded binary is incorrect: ${err && err.message || err}`); - console.error(`Install unsuccessful`); - process.exit(1); - } - - // Also try to cache the file to speed up future installs - try { - fs.mkdirSync(path.dirname(cachePath), { - recursive: true, - mode: 0o700, // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - }); - fs.copyFileSync(toPath, cachePath); - cleanCacheLRU(cachePath); - } catch { - } - - if (didFail) console.error(`Install successful`); -} - -function validateBinaryVersion(binaryPath: string): void { - const stdout = child_process.execFileSync(binaryPath, ['--version']).toString().trim(); - if (stdout !== version) { - throw new Error(`Expected ${JSON.stringify(version)} but got ${JSON.stringify(stdout)}`); - } -} - -function getCachePath(name: string): string { - const home = os.homedir(); - const common = ['esbuild', 'bin', `${name}@${version}`]; - if (process.platform === 'darwin') return path.join(home, 'Library', 'Caches', ...common); - if (process.platform === 'win32') return path.join(home, 'AppData', 'Local', 'Cache', ...common); - - // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - const XDG_CACHE_HOME = process.env.XDG_CACHE_HOME; - if (process.platform === 'linux' && XDG_CACHE_HOME && path.isAbsolute(XDG_CACHE_HOME)) - return path.join(XDG_CACHE_HOME, ...common); - - return path.join(home, '.cache', ...common); -} - -function cleanCacheLRU(fileToKeep: string): void { - // Gather all entries in the cache - const dir = path.dirname(fileToKeep); - const entries: { path: string, mtime: Date }[] = []; - for (const entry of fs.readdirSync(dir)) { - const entryPath = path.join(dir, entry); - try { - const stats = fs.statSync(entryPath); - entries.push({ path: entryPath, mtime: stats.mtime }); - } catch { - } - } - - // Only keep the most recent entries - entries.sort((a, b) => +b.mtime - +a.mtime); - for (const entry of entries.slice(5)) { - try { - fs.unlinkSync(entry.path); - } catch { - } - } -} - -function fetch(url: string): Promise { - return new Promise((resolve, reject) => { - https.get(url, res => { - if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) - return fetch(res.headers.location).then(resolve, reject); - if (res.statusCode !== 200) - return reject(new Error(`Server responded with ${res.statusCode}`)); - let chunks: Buffer[] = []; - res.on('data', chunk => chunks.push(chunk)); - res.on('end', () => resolve(Buffer.concat(chunks))); - }).on('error', reject); - }); -} - -function extractFileFromTarGzip(buffer: Buffer, file: string): Buffer { - try { - buffer = zlib.unzipSync(buffer); - } catch (err: any) { - throw new Error(`Invalid gzip data in archive: ${err && err.message || err}`); - } - let str = (i: number, n: number) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, ''); - let offset = 0; - file = `package/${file}`; - 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; - } - } - throw new Error(`Could not find ${JSON.stringify(file)} in archive`); -} - -function installUsingNPM(name: string, file: string): Buffer { - const installDir = path.join(os.tmpdir(), 'esbuild-' + Math.random().toString(36).slice(2)); - fs.mkdirSync(installDir, { recursive: true }); - fs.writeFileSync(path.join(installDir, 'package.json'), '{}'); - - // Erase "npm_config_global" so that "npm install --global esbuild" works. - // Otherwise this nested "npm install" will also be global, and the install - // will deadlock waiting for the global installation lock. - const env = { ...process.env, npm_config_global: undefined }; - - child_process.execSync(`npm install --loglevel=error --prefer-offline --no-audit --progress=false ${name}@${version}`, - { cwd: installDir, stdio: 'pipe', env }); - const buffer = fs.readFileSync(path.join(installDir, 'node_modules', name, file)); - try { - removeRecursive(installDir); - } catch (e) { - // Removing a file or directory can randomly break on Windows, returning - // EBUSY for an arbitrary length of time. I think this happens when some - // other program has that file or directory open (e.g. an anti-virus - // program). This is fine on Unix because the OS just unlinks the entry - // but keeps the reference around until it's unused. In this case we just - // ignore errors because this directory is in a temporary directory, so in - // theory it should get cleaned up eventually anyway. - } - return buffer; -} - -function removeRecursive(dir: string): void { - 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); - } - fs.rmdirSync(dir); -} - -function isYarnBerryOrNewer(): boolean { - const { npm_config_user_agent } = process.env; - if (npm_config_user_agent) { - const match = npm_config_user_agent.match(/yarn\/(\d+)/); - if (match && match[1]) { - return parseInt(match[1], 10) >= 2; - } - } - return false; -} - -function installDirectly(name: string) { - if (process.env.ESBUILD_BINARY_PATH) { - fs.copyFileSync(process.env.ESBUILD_BINARY_PATH, binPath); - validateBinaryVersion(binPath); - } else { - // Write to a temporary file, then move the file into place. This is an - // attempt to avoid problems with package managers like pnpm which will - // usually turn each file into a hard link. We don't want to mutate the - // hard-linked file which may be shared with other files. - const tempBinPath = binPath + '__'; - installBinaryFromPackage(name, 'bin/esbuild', tempBinPath) - .then(() => fs.renameSync(tempBinPath, binPath)) - .catch(e => setImmediate(() => { throw e; })); - } -} - -function installWithWrapper(name: string, fromPath: string, toPath: string): void { - fs.writeFileSync( - binPath, - `#!/usr/bin/env node -const path = require('path'); -const esbuild_exe = path.join(__dirname, '..', ${JSON.stringify(toPath)}); -const child_process = require('child_process'); -const { status } = child_process.spawnSync(esbuild_exe, process.argv.slice(2), { stdio: 'inherit' }); -process.exitCode = status === null ? 1 : status; -`); - const absToPath = path.join(__dirname, toPath); - if (process.env.ESBUILD_BINARY_PATH) { - fs.copyFileSync(process.env.ESBUILD_BINARY_PATH, absToPath); - validateBinaryVersion(absToPath); - } else { - installBinaryFromPackage(name, fromPath, absToPath) - .catch(e => setImmediate(() => { throw e; })); - } -} - -function installOnUnix(name: string): void { - // Yarn 2 is deliberately incompatible with binary modules because the - // developers of Yarn 2 don't think they should be used. See this thread for - // details: https://github.com/yarnpkg/berry/issues/882. - // - // We want to avoid slowing down esbuild for everyone just because of this - // decision by the Yarn 2 developers, so we explicitly detect if esbuild is - // being installed using Yarn 2 and install a compatability shim only for - // Yarn 2. Normal package managers can just run the binary directly for - // maximum speed. - if (isYarnBerryOrNewer()) { - installWithWrapper(name, "bin/esbuild", "esbuild"); - } else { - installDirectly(name); - } -} - -function installOnWindows(name: string): void { - installWithWrapper(name, "esbuild.exe", "esbuild.exe"); -} - -const platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`; -const knownWindowsPackages: Record = { - 'win32 arm64 LE': 'esbuild-windows-arm64', - 'win32 ia32 LE': 'esbuild-windows-32', - 'win32 x64 LE': 'esbuild-windows-64', -}; -const knownUnixlikePackages: Record = { - 'android arm64 LE': 'esbuild-android-arm64', - 'darwin arm64 LE': 'esbuild-darwin-arm64', - 'darwin x64 LE': 'esbuild-darwin-64', - 'freebsd arm64 LE': 'esbuild-freebsd-arm64', - 'freebsd x64 LE': 'esbuild-freebsd-64', - 'openbsd x64 LE': 'esbuild-openbsd-64', - 'linux arm LE': 'esbuild-linux-arm', - 'linux arm64 LE': 'esbuild-linux-arm64', - 'linux ia32 LE': 'esbuild-linux-32', - 'linux mips64el LE': 'esbuild-linux-mips64le', - 'linux ppc64 LE': 'esbuild-linux-ppc64le', - 'linux x64 LE': 'esbuild-linux-64', - 'sunos x64 LE': 'esbuild-sunos-64', -}; - -// Pick a package to install -if (platformKey in knownWindowsPackages) { - installOnWindows(knownWindowsPackages[platformKey]); -} else if (platformKey in knownUnixlikePackages) { - installOnUnix(knownUnixlikePackages[platformKey]); -} else { - console.error(`Unsupported platform: ${platformKey}`); - process.exit(1); -} diff --git a/lib/npm/node-install.ts b/lib/npm/node-install.ts new file mode 100644 index 00000000000..1da5fe7dd3a --- /dev/null +++ b/lib/npm/node-install.ts @@ -0,0 +1,77 @@ +import { pkgAndBinForCurrentPlatform } from './node-platform'; + +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import child_process = require('child_process'); + +declare const ESBUILD_VERSION: string; +const toPath = path.join(__dirname, 'bin', 'esbuild'); + +function validateBinaryVersion(...command: string[]): void { + command.push('--version'); + const stdout = child_process.execFileSync(command.shift()!, command).toString().trim(); + if (stdout !== ESBUILD_VERSION) { + throw new Error(`Expected ${JSON.stringify(ESBUILD_VERSION)} but got ${JSON.stringify(stdout)}`); + } +} + +function isYarn2OrAbove(): boolean { + const { npm_config_user_agent } = process.env; + if (npm_config_user_agent) { + const match = npm_config_user_agent.match(/yarn\/(\d+)/); + if (match && match[1]) { + return parseInt(match[1], 10) >= 2; + } + } + return false; +} + +// This feature was added to give external code a way to modify the binary +// path without modifying the code itself. Do not remove this because +// external code relies on this (in addition to esbuild's own test suite). +if (process.env.ESBUILD_BINARY_PATH) { + // Patch the CLI use case (the "esbuild" command) + const pathString = JSON.stringify(process.env.ESBUILD_BINARY_PATH); + fs.writeFileSync(toPath, `#!/usr/bin/env node\n` + + `require('child_process').execFileSync(${pathString}, process.argv.slice(2), { stdio: 'inherit' });\n`); + + // Patch the JS API use case (the "require('esbuild')" workflow) + const libMain = path.join(__dirname, 'lib', 'main.js'); + const code = fs.readFileSync(libMain, 'utf8'); + fs.writeFileSync(libMain, `var ESBUILD_BINARY_PATH = ${pathString};\n${code}`); + + // Windows needs "node" before this command since it's a JavaScript file + validateBinaryVersion('node', toPath); +} + +// This package contains a "bin/esbuild" JavaScript file that finds and runs +// the appropriate binary executable. However, this means that running the +// "esbuild" command runs another instance of "node" which is way slower than +// just running the binary executable directly. +// +// Here we optimize for this by replacing the JavaScript file with the binary +// executable at install time. This optimization does not work on Windows +// because on Windows the binary executable must be called "esbuild.exe" +// instead of "esbuild". This also doesn't work with Yarn 2+ because the Yarn +// developers don't think binary modules should be used. See this thread for +// details: https://github.com/yarnpkg/berry/issues/882. This optimization also +// doesn't apply when npm's "--ignore-scripts" flag is used since in that case +// this install script will not be run. +else if (os.platform() !== 'win32' && !isYarn2OrAbove()) { + const { bin } = pkgAndBinForCurrentPlatform(); + try { + fs.unlinkSync(toPath); + fs.linkSync(bin, toPath); + } catch (e) { + // Ignore errors here since this optimization is optional + } + + // This is no longer a JavaScript file so don't run it using "node" + validateBinaryVersion(toPath); +} + +else { + // Windows needs "node" before this command since it's a JavaScript file + validateBinaryVersion('node', toPath); +} diff --git a/lib/npm/node-platform.ts b/lib/npm/node-platform.ts new file mode 100644 index 00000000000..d2c7e08c0a1 --- /dev/null +++ b/lib/npm/node-platform.ts @@ -0,0 +1,115 @@ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); + +declare const ESBUILD_VERSION: string; + +// This feature was added to give external code a way to modify the binary +// path without modifying the code itself. Do not remove this because +// external code relies on this. +var ESBUILD_BINARY_PATH: string | undefined = process.env.ESBUILD_BINARY_PATH || ESBUILD_BINARY_PATH; + +export const knownWindowsPackages: Record = { + 'win32 arm64 LE': 'esbuild-windows-arm64', + 'win32 ia32 LE': 'esbuild-windows-32', + 'win32 x64 LE': 'esbuild-windows-64', +}; + +export const knownUnixlikePackages: Record = { + 'android arm64 LE': 'esbuild-android-arm64', + 'darwin arm64 LE': 'esbuild-darwin-arm64', + 'darwin x64 LE': 'esbuild-darwin-64', + 'freebsd arm64 LE': 'esbuild-freebsd-arm64', + 'freebsd x64 LE': 'esbuild-freebsd-64', + 'openbsd x64 LE': 'esbuild-openbsd-64', + 'linux arm LE': 'esbuild-linux-arm', + 'linux arm64 LE': 'esbuild-linux-arm64', + 'linux ia32 LE': 'esbuild-linux-32', + 'linux mips64el LE': 'esbuild-linux-mips64le', + 'linux ppc64 LE': 'esbuild-linux-ppc64le', + 'linux x64 LE': 'esbuild-linux-64', + 'sunos x64 LE': 'esbuild-sunos-64', +}; + +export function pkgAndBinForCurrentPlatform(): { pkg: string, bin: string } { + let pkg: string; + let bin: string; + let platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`; + + if (platformKey in knownWindowsPackages) { + pkg = knownWindowsPackages[platformKey]; + bin = `${pkg}/esbuild.exe`; + } + + else if (platformKey in knownUnixlikePackages) { + pkg = knownUnixlikePackages[platformKey]; + bin = `${pkg}/bin/esbuild`; + } + + else { + throw new Error(`Unsupported platform: ${platformKey}`); + } + + try { + bin = require.resolve(bin); + } catch (e) { + try { + require.resolve(pkg) + } catch { + throw new Error(`The package "${pkg}" could not be found, and is needed by esbuild. + +If you are installing esbuild with npm, make sure that you don't specify the +"--no-optional" flag. The "optionalDependencies" package.json feature is used +by esbuild to install the correct binary executable for your current platform.`) + } + throw e + } + + return { pkg, bin }; +} + +function getCachePath(name: string): string { + const home = os.homedir(); + const common = ['esbuild', 'bin', `${name}@${ESBUILD_VERSION}`]; + if (process.platform === 'darwin') return path.join(home, 'Library', 'Caches', ...common); + if (process.platform === 'win32') return path.join(home, 'AppData', 'Local', 'Cache', ...common); + + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + const XDG_CACHE_HOME = process.env.XDG_CACHE_HOME; + if (process.platform === 'linux' && XDG_CACHE_HOME && path.isAbsolute(XDG_CACHE_HOME)) + return path.join(XDG_CACHE_HOME, ...common); + + return path.join(home, '.cache', ...common); +} + +export function extractedBinPath(): string { + // This feature was added to give external code a way to modify the binary + // path without modifying the code itself. Do not remove this because + // external code relies on this (in addition to esbuild's own test suite). + if (ESBUILD_BINARY_PATH) { + return ESBUILD_BINARY_PATH; + } + + const { pkg, bin } = pkgAndBinForCurrentPlatform(); + + // The esbuild binary executable can't be used in Yarn 2 in PnP mode because + // it's inside a virtual file system and the OS needs it in the real file + // system. So we need to copy the file out of the virtual file system into + // the real file system. + let isYarnPnP = false; + try { + require('pnpapi'); + isYarnPnP = true; + } catch (e) { + } + if (isYarnPnP) { + const binTargetPath = getCachePath(pkg); + if (!fs.existsSync(binTargetPath)) { + fs.copyFileSync(bin, binTargetPath); + fs.chmodSync(binTargetPath, 0o755); + } + return binTargetPath; + } + + return bin; +} diff --git a/lib/npm/node-shim.ts b/lib/npm/node-shim.ts new file mode 100644 index 00000000000..e029374dd97 --- /dev/null +++ b/lib/npm/node-shim.ts @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +import { extractedBinPath } from "./node-platform"; +require('child_process').execFileSync(extractedBinPath(), process.argv.slice(2), { stdio: 'inherit' }); diff --git a/lib/npm/node.ts b/lib/npm/node.ts index 5ab1e37bbb3..b7f5cf9f128 100644 --- a/lib/npm/node.ts +++ b/lib/npm/node.ts @@ -1,5 +1,6 @@ import * as types from "../shared/types"; import * as common from "../shared/common"; +import { extractedBinPath } from "./node-platform"; import child_process = require('child_process'); import crypto = require('crypto'); @@ -45,13 +46,6 @@ if (process.env.ESBUILD_WORKER_THREADS !== '0') { let isInternalWorkerThread = worker_threads?.workerData?.esbuildVersion === ESBUILD_VERSION; let esbuildCommandAndArgs = (): [string, string[]] => { - // This feature was added to give external code a way to modify the binary - // path without modifying the code itself. Do not remove this because - // external code relies on this. - if (process.env.ESBUILD_BINARY_PATH) { - return [path.resolve(process.env.ESBUILD_BINARY_PATH), []]; - } - // Try to have a nice error message when people accidentally bundle esbuild if (path.basename(__filename) !== 'main.js' || path.basename(__dirname) !== 'lib') { throw new Error( @@ -72,33 +66,7 @@ let esbuildCommandAndArgs = (): [string, string[]] => { return ['node', [path.join(__dirname, '..', 'bin', 'esbuild')]]; } - if (process.platform === 'win32') { - return [path.join(__dirname, '..', 'esbuild.exe'), []]; - } - - // Yarn 2 is deliberately incompatible with binary modules because the - // developers of Yarn 2 don't think they should be used. See this thread for - // details: https://github.com/yarnpkg/berry/issues/882. - // - // As a compatibility hack we replace the binary with a wrapper script only - // for Yarn 2. The wrapper script is avoided for other platforms because - // running the binary directly without going through node first is faster. - // However, this will make using the JavaScript API with Yarn 2 unnecessarily - // slow because the wrapper means running the binary will now start another - // nested node process just to call "spawnSync" and run the actual binary. - // - // To work around this workaround, we query for the place the binary is moved - // to if the original location is replaced by our Yarn 2 compatibility hack. - // If it exists, we can infer that we are running within Yarn 2 and the - // JavaScript API should invoke the binary here instead to avoid a slowdown. - // Calling the binary directly can be over 6x faster than calling the wrapper - // script instead. - let pathForYarn2 = path.join(__dirname, '..', 'esbuild'); - if (fs.existsSync(pathForYarn2)) { - return [pathForYarn2, []]; - } - - return [path.join(__dirname, '..', 'bin', 'esbuild'), []]; + return [extractedBinPath(), []]; }; // Return true if stderr is a TTY diff --git a/npm/esbuild-android-arm64/package.json b/npm/esbuild-android-arm64/package.json index 94d846a0104..427358a5f00 100644 --- a/npm/esbuild-android-arm64/package.json +++ b/npm/esbuild-android-arm64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "arm64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-darwin-64/package.json b/npm/esbuild-darwin-64/package.json index a33af9ca7d8..4c31383ce9d 100644 --- a/npm/esbuild-darwin-64/package.json +++ b/npm/esbuild-darwin-64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "x64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-darwin-arm64/package.json b/npm/esbuild-darwin-arm64/package.json index e8a8ef7e88b..c0ff1021fb3 100644 --- a/npm/esbuild-darwin-arm64/package.json +++ b/npm/esbuild-darwin-arm64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "arm64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-freebsd-64/package.json b/npm/esbuild-freebsd-64/package.json index 44a69665a36..7181959126e 100644 --- a/npm/esbuild-freebsd-64/package.json +++ b/npm/esbuild-freebsd-64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "x64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-freebsd-arm64/package.json b/npm/esbuild-freebsd-arm64/package.json index 79c7616080a..fc3dc54b076 100644 --- a/npm/esbuild-freebsd-arm64/package.json +++ b/npm/esbuild-freebsd-arm64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "arm64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-linux-32/package.json b/npm/esbuild-linux-32/package.json index b4049e755a0..00d72d87c29 100644 --- a/npm/esbuild-linux-32/package.json +++ b/npm/esbuild-linux-32/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "ia32" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-linux-64/package.json b/npm/esbuild-linux-64/package.json index 174bcb7b531..e6ebb69ef58 100644 --- a/npm/esbuild-linux-64/package.json +++ b/npm/esbuild-linux-64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "x64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-linux-arm/package.json b/npm/esbuild-linux-arm/package.json index 2c78e3fb787..e7d1d731fcc 100644 --- a/npm/esbuild-linux-arm/package.json +++ b/npm/esbuild-linux-arm/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "arm" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-linux-arm64/package.json b/npm/esbuild-linux-arm64/package.json index 525486f8d76..615d1e17f38 100644 --- a/npm/esbuild-linux-arm64/package.json +++ b/npm/esbuild-linux-arm64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "arm64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-linux-mips64le/package.json b/npm/esbuild-linux-mips64le/package.json index 523bc6a4a85..439a7ff17cb 100644 --- a/npm/esbuild-linux-mips64le/package.json +++ b/npm/esbuild-linux-mips64le/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "mips64el" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-linux-ppc64le/package.json b/npm/esbuild-linux-ppc64le/package.json index 5257daded5a..99a375fdf6d 100644 --- a/npm/esbuild-linux-ppc64le/package.json +++ b/npm/esbuild-linux-ppc64le/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "ppc64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-openbsd-64/package.json b/npm/esbuild-openbsd-64/package.json index 7384c8bd5a2..04fb8fbeb0c 100644 --- a/npm/esbuild-openbsd-64/package.json +++ b/npm/esbuild-openbsd-64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "x64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-sunos-64/package.json b/npm/esbuild-sunos-64/package.json index 99f29cc4019..82d3679cb32 100644 --- a/npm/esbuild-sunos-64/package.json +++ b/npm/esbuild-sunos-64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "x64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-windows-32/package.json b/npm/esbuild-windows-32/package.json index 6f706af1499..99df3125c49 100644 --- a/npm/esbuild-windows-32/package.json +++ b/npm/esbuild-windows-32/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "ia32" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-windows-64/package.json b/npm/esbuild-windows-64/package.json index d7168b29424..443460eface 100644 --- a/npm/esbuild-windows-64/package.json +++ b/npm/esbuild-windows-64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "x64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild-windows-arm64/package.json b/npm/esbuild-windows-arm64/package.json index 9f0095dc8e3..4c5936fa541 100644 --- a/npm/esbuild-windows-arm64/package.json +++ b/npm/esbuild-windows-arm64/package.json @@ -9,8 +9,5 @@ ], "cpu": [ "arm64" - ], - "directories": { - "bin": "bin" - } + ] } diff --git a/npm/esbuild/bin/esbuild b/npm/esbuild/bin/esbuild deleted file mode 100755 index 6b02ee0cb12..00000000000 --- a/npm/esbuild/bin/esbuild +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env node -throw new Error(`esbuild: Failed to install correctly - -Make sure you don't have "ignore-scripts" set to true. You can check this with -"npm config get ignore-scripts". If that returns true you can reset it back to -false using "npm config set ignore-scripts false" and then reinstall esbuild. - -If you're using npm v7, make sure your package-lock.json file contains either -"lockfileVersion": 1 or the code "hasInstallScript": true. If it doesn't have -either of those, then it is likely the case that a known bug in npm v7 has -corrupted your package-lock.json file. Regenerating your package-lock.json file -should fix this issue. -`); diff --git a/npm/esbuild/package.json b/npm/esbuild/package.json index 8cca71afd19..237a1dad2d8 100644 --- a/npm/esbuild/package.json +++ b/npm/esbuild/package.json @@ -11,5 +11,23 @@ "bin": { "esbuild": "bin/esbuild" }, + "optionalDependencies": { + "esbuild-android-arm64": "0.12.28", + "esbuild-darwin-64": "0.12.28", + "esbuild-darwin-arm64": "0.12.28", + "esbuild-freebsd-64": "0.12.28", + "esbuild-freebsd-arm64": "0.12.28", + "esbuild-linux-32": "0.12.28", + "esbuild-linux-64": "0.12.28", + "esbuild-linux-arm": "0.12.28", + "esbuild-linux-arm64": "0.12.28", + "esbuild-linux-mips64le": "0.12.28", + "esbuild-linux-ppc64le": "0.12.28", + "esbuild-openbsd-64": "0.12.28", + "esbuild-sunos-64": "0.12.28", + "esbuild-windows-32": "0.12.28", + "esbuild-windows-64": "0.12.28", + "esbuild-windows-arm64": "0.12.28" + }, "license": "MIT" } diff --git a/scripts/esbuild.js b/scripts/esbuild.js index 224667d8244..7869a56c0ab 100644 --- a/scripts/esbuild.js +++ b/scripts/esbuild.js @@ -14,12 +14,15 @@ const esmBrowserTarget = 'es2017'; // Preserves "async" exports.buildNativeLib = (esbuildPath) => { const libDir = path.join(npmDir, 'lib') + const binDir = path.join(npmDir, 'bin') fs.mkdirSync(libDir, { recursive: true }) + fs.mkdirSync(binDir, { recursive: true }) // Generate "npm/esbuild/install.js" childProcess.execFileSync(esbuildPath, [ - path.join(repoDir, 'lib', 'npm', 'install.ts'), + path.join(repoDir, 'lib', 'npm', 'node-install.ts'), '--outfile=' + path.join(npmDir, 'install.js'), + '--bundle', '--target=' + nodeTarget, '--define:ESBUILD_VERSION=' + JSON.stringify(version), '--platform=node', @@ -32,16 +35,46 @@ exports.buildNativeLib = (esbuildPath) => { '--outfile=' + path.join(libDir, 'main.js'), '--bundle', '--target=' + nodeTarget, - '--format=cjs', '--define:WASM=false', '--define:ESBUILD_VERSION=' + JSON.stringify(version), '--platform=node', '--log-level=warning', ], { cwd: repoDir }) + // Generate "npm/esbuild/bin/esbuild" + childProcess.execFileSync(esbuildPath, [ + path.join(repoDir, 'lib', 'npm', 'node-shim.ts'), + '--outfile=' + path.join(binDir, 'esbuild'), + '--bundle', + '--target=' + nodeTarget, + '--define:ESBUILD_VERSION=' + JSON.stringify(version), + '--platform=node', + '--log-level=warning', + ], { cwd: repoDir }) + // Generate "npm/esbuild/lib/main.d.ts" const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'types.ts'), 'utf8') fs.writeFileSync(path.join(libDir, 'main.d.ts'), types_ts) + + // Get supported platforms + const platforms = {} + new Function('exports', 'require', childProcess.execFileSync(esbuildPath, [ + path.join(repoDir, 'lib', 'npm', 'node-platform.ts'), + '--bundle', + '--target=' + nodeTarget, + '--platform=node', + '--log-level=warning', + ], { cwd: repoDir }))(platforms, require) + const optionalDependencies = Object.fromEntries(Object.values({ + ...platforms.knownWindowsPackages, + ...platforms.knownUnixlikePackages, + }).sort().map(x => [x, version])) + + // Update "npm/esbuild/package.json" + const pjPath = path.join(npmDir, 'package.json') + const package_json = JSON.parse(fs.readFileSync(pjPath, 'utf8')) + package_json.optionalDependencies = optionalDependencies + fs.writeFileSync(pjPath, JSON.stringify(package_json, null, 2) + '\n') } exports.buildWasmLib = async (esbuildPath) => {