import { downloadedBinPath, ESBUILD_BINARY_PATH, isValidBinaryPath, pkgAndSubpathForCurrentPlatform } from './node-platform'
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')
const versionFromPackageJSON: string = require(path.join(__dirname, 'package.json')).version
const toPath = path.join(__dirname, 'bin', 'esbuild')
let isToPathJS = true
function validateBinaryVersion(...command: string[]): void {
command.push('--version')
let stdout: string
try {
stdout = child_process.execFileSync(command.shift()!, command, {
stdio: 'pipe',
}).toString().trim()
} catch (err) {
if (os.platform() === 'darwin' && /_SecTrustEvaluateWithError/.test(err + '')) {
let os = 'this version of macOS'
try {
os = 'macOS ' + child_process.execFileSync('sw_vers', ['-productVersion']).toString().trim()
} catch {
}
throw new Error(`The "esbuild" package cannot be installed because ${os} is too outdated.
The Go compiler (which esbuild relies on) no longer supports ${os},
which means the "esbuild" binary executable can't be run. You can either:
* Update your version of macOS to one that the Go compiler supports
* Use the "esbuild-wasm" package instead of the "esbuild" package
* Build esbuild yourself using an older version of the Go compiler
`)
}
throw err
}
if (stdout !== versionFromPackageJSON) {
throw new Error(`Expected ${JSON.stringify(versionFromPackageJSON)} but got ${JSON.stringify(stdout)}`)
}
}
function isYarn(): boolean {
const { npm_config_user_agent } = process.env
if (npm_config_user_agent) {
return /\byarn\//.test(npm_config_user_agent)
}
return false
}
function fetch(url: string): Promise<Buffer> {
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, subpath: 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
subpath = `package/${subpath}`
while (offset < buffer.length) {
let name = str(offset, 100)
let size = parseInt(str(offset + 124, 12), 8)
offset += 512
if (!isNaN(size)) {
if (name === subpath) return buffer.subarray(offset, offset + size)
offset += (size + 511) & ~511
}
}
throw new Error(`Could not find ${JSON.stringify(subpath)} in archive`)
}
function installUsingNPM(pkg: string, subpath: string, binPath: string): void {
const env = { ...process.env, npm_config_global: undefined }
const esbuildLibDir = path.dirname(require.resolve('esbuild'))
const installDir = path.join(esbuildLibDir, 'npm-install')
fs.mkdirSync(installDir)
try {
fs.writeFileSync(path.join(installDir, 'package.json'), '{}')
child_process.execSync(`npm install --loglevel=error --prefer-offline --no-audit --progress=false ${pkg}@${versionFromPackageJSON}`,
{ cwd: installDir, stdio: 'pipe', env })
const installedBinPath = path.join(installDir, 'node_modules', pkg, subpath)
fs.renameSync(installedBinPath, binPath)
} finally {
try {
removeRecursive(installDir)
} catch {
}
}
}
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 {
continue;
}
if (stats.isDirectory()) removeRecursive(entryPath)
else fs.unlinkSync(entryPath)
}
fs.rmdirSync(dir)
}
function applyManualBinaryPathOverride(overridePath: string): void {
const pathString = JSON.stringify(overridePath)
fs.writeFileSync(toPath, `#!/usr/bin/env node\n` +
`require('child_process').execFileSync(${pathString}, process.argv.slice(2), { stdio: 'inherit' });\n`)
const libMain = path.join(__dirname, 'lib', 'main.js')
const code = fs.readFileSync(libMain, 'utf8')
fs.writeFileSync(libMain, `var ESBUILD_BINARY_PATH = ${pathString};\n${code}`)
}
function maybeOptimizePackage(binPath: string): void {
if (os.platform() !== 'win32' && !isYarn()) {
const tempPath = path.join(__dirname, 'bin-esbuild')
try {
fs.linkSync(binPath, tempPath)
fs.renameSync(tempPath, toPath)
isToPathJS = false
fs.unlinkSync(tempPath)
} catch {
}
}
}
async function downloadDirectlyFromNPM(pkg: string, subpath: string, binPath: string): Promise<void> {
const url = `https://registry.npmjs.org/${pkg}/-/${pkg.replace('@esbuild/', '')}-${versionFromPackageJSON}.tgz`
console.error(`[esbuild] Trying to download ${JSON.stringify(url)}`)
try {
fs.writeFileSync(binPath, extractFileFromTarGzip(await fetch(url), subpath))
fs.chmodSync(binPath, 0o755)
} catch (e: any) {
console.error(`[esbuild] Failed to download ${JSON.stringify(url)}: ${e && e.message || e}`)
throw e
}
}
async function checkAndPreparePackage(): Promise<void> {
if (isValidBinaryPath(ESBUILD_BINARY_PATH)) {
if (!fs.existsSync(ESBUILD_BINARY_PATH)) {
console.warn(`[esbuild] Ignoring bad configuration: ESBUILD_BINARY_PATH=${ESBUILD_BINARY_PATH}`)
} else {
applyManualBinaryPathOverride(ESBUILD_BINARY_PATH)
return
}
}
const { pkg, subpath } = pkgAndSubpathForCurrentPlatform()
let binPath: string
try {
binPath = require.resolve(`${pkg}/${subpath}`)
} catch (e) {
console.error(`[esbuild] Failed to find package "${pkg}" on the file system
This can happen if you use the "--no-optional" flag. The "optionalDependencies"
package.json feature is used by esbuild to install the correct binary executable
for your current platform. This install script will now attempt to work around
this. If that fails, you need to remove the "--no-optional" flag to use esbuild.
`)
binPath = downloadedBinPath(pkg, subpath)
try {
console.error(`[esbuild] Trying to install package "${pkg}" using npm`)
installUsingNPM(pkg, subpath, binPath)
} catch (e2: any) {
console.error(`[esbuild] Failed to install package "${pkg}" using npm: ${e2 && e2.message || e2}`)
try {
await downloadDirectlyFromNPM(pkg, subpath, binPath)
} catch (e3: any) {
throw new Error(`Failed to install package "${pkg}"`)
}
}
}
maybeOptimizePackage(binPath)
}
checkAndPreparePackage().then(() => {
if (isToPathJS) {
validateBinaryVersion(process.execPath, toPath)
} else {
validateBinaryVersion(toPath)
}
})