const childProcess = require('child_process') const { buildBinary, dirname, removeRecursiveSync, writeFileAtomic } = require('./esbuild.js') const assert = require('assert') const path = require('path') const util = require('util') const url = require('url') const fs = require('fs').promises const execFileAsync = util.promisify(childProcess.execFile) const execAsync = util.promisify(childProcess.exec) const nodeMajorVersion = +process.versions.node.split('.')[0] const testDir = path.join(dirname, '.end-to-end-tests') const errorIcon = process.platform !== 'win32' ? '✘' : 'X' const esbuildPath = buildBinary() const tests = [] let testCount = 0 // Tests for "--define" tests.push( test(['--define:foo=null', 'in.js', '--outfile=node.js'], { 'in.js': `if (foo !== null) throw 'fail'` }), test(['--define:foo=true', 'in.js', '--outfile=node.js'], { 'in.js': `if (foo !== true) throw 'fail'` }), test(['--define:foo=false', 'in.js', '--outfile=node.js'], { 'in.js': `if (foo !== false) throw 'fail'` }), test(['--define:foo="abc"', 'in.js', '--outfile=node.js'], { 'in.js': `if (foo !== "abc") throw 'fail'` }), test(['--define:foo=123.456', 'in.js', '--outfile=node.js'], { 'in.js': `if (foo !== 123.456) throw 'fail'` }), test(['--define:foo=-123.456', 'in.js', '--outfile=node.js'], { 'in.js': `if (foo !== -123.456) throw 'fail'` }), test(['--define:foo=global', 'in.js', '--outfile=node.js'], { 'in.js': `foo.bar = 123; if (bar !== 123) throw 'fail'` }), test(['--define:foo=bar', 'in.js', '--outfile=node.js'], { 'in.js': `let bar = {x: 123}; if (foo.x !== 123) throw 'fail'` }), test(['--define:a.x=1', 'in.js', '--outfile=node.js'], { 'in.js': `if (a.x !== 1) throw 'fail'` }), test(['--define:a.x=1', '--define:a.y=2', 'in.js', '--outfile=node.js'], { 'in.js': `if (a.x + a.y !== 3) throw 'fail'` }), test(['--define:a.x=1', '--define:b.y=2', 'in.js', '--outfile=node.js'], { 'in.js': `if (a.x + b.y !== 3) throw 'fail'` }), test(['--define:a.x=1', '--define:b.x=2', 'in.js', '--outfile=node.js'], { 'in.js': `if (a.x + b.x !== 3) throw 'fail'` }), test(['--define:x=y', '--define:y=x', 'in.js', '--outfile=node.js'], { 'in.js': `eval('var x="x",y="y"'); if (x + y !== 'yx') throw 'fail'`, }), ) // Test recursive directory creation tests.push( test(['entry.js', '--outfile=a/b/c/d/index.js'], { 'entry.js': `exports.foo = 123`, 'node.js': `const ns = require('./a/b/c/d'); if (ns.foo !== 123) throw 'fail'`, }), ) // Test bogus paths with a file as a parent directory (this happens when you use "pnpx esbuild") tests.push( test(['entry.js', '--bundle'], { 'entry.js': `import "./file.js/what/is/this"`, 'file.js': `some file`, }, { expectedStderr: `${errorIcon} [ERROR] Could not resolve "./file.js/what/is/this" entry.js:1:7: 1 │ import "./file.js/what/is/this" ╵ ~~~~~~~~~~~~~~~~~~~~~~~~ `, }), ) // Test absolute paths in log messages tests.push( test(['entry.js', '--bundle', '--abs-paths=log'], { 'entry.js': `import "./foo"`, }, { expectedStderr: `${errorIcon} [ERROR] Could not resolve "./foo" $ABS_PATH_PREFIX$entry.js:1:7: 1 │ import "./foo" ╵ ~~~~~~~ `, }), ) // Test resolving paths with a question mark (an invalid path on Windows) tests.push( test(['entry.js', '--bundle', '--outfile=node.js'], { 'entry.js': ` import x from "./file.js?ignore-me" if (x !== 123) throw 'fail' `, 'file.js': `export default 123`, }), ) // Test TypeScript enum stuff tests.push( // Scope merging test(['entry.ts', '--bundle', '--minify', '--outfile=node.js'], { 'entry.ts': ` const id = x => x enum a { b = 1 } enum a { c = 2 } if (id(a).c !== 2 || id(a)[2] !== 'c' || id(a).b !== 1 || id(a)[1] !== 'b') throw 'fail' `, }), test(['entry.ts', '--bundle', '--minify', '--outfile=node.js'], { 'entry.ts': ` const id = x => x { enum a { b = 1 } } { enum a { c = 2 } if (id(a).c !== 2 || id(a)[2] !== 'c' || id(a).b !== void 0 || id(a)[1] !== void 0) throw 'fail' } `, }), test(['entry.ts', '--bundle', '--minify', '--outfile=node.js'], { 'entry.ts': ` const id = x => x enum a { b = 1 } namespace a { if (id(a).b !== 1 || id(a)[1] !== 'b') throw 'fail' } `, }), test(['entry.ts', '--bundle', '--minify', '--outfile=node.js'], { 'entry.ts': ` const id = x => x namespace a { export function foo() { if (id(a).b !== 1 || id(a)[1] !== 'b') throw 'fail' } } enum a { b = 1 } a.foo() `, }), test(['entry.ts', '--bundle', '--minify', '--outfile=node.js'], { 'entry.ts': ` import './enum-to-namespace' import './namespace-to-enum' import './namespace-to-namespace' `, 'enum-to-namespace.ts': ` let foo, bar, y = 2, z = 4 enum x { y = 1 } namespace x { foo = y } enum x { z = y * 3 } namespace x { bar = z } if (foo !== 2 || bar !== 4) throw 'fail' `, 'namespace-to-enum.ts': ` let y = 2, z = 4 namespace x { export let y = 1 } enum x { foo = y } namespace x { export let z = y * 3 } enum x { bar = z } if (x.foo !== 2 || x.bar !== 4) throw 'fail' `, 'namespace-to-namespace.ts': ` let foo, bar, y = 2, z = 4 namespace x { export const y = 1 } namespace x { foo = y } namespace x { export const z = y * 3 } namespace x { bar = z } if (foo !== 1 || bar !== 3) throw 'fail' `, }), // https://github.com/evanw/esbuild/issues/3205 test(['entry.ts', '--outfile=node.js'], { 'entry.ts': ` // Note: The parentheses are important here let x = (() => { const enum E { a = 123 } return () => E.a }) if (x()() !== 123) throw 'fail' `, }), // https://github.com/evanw/esbuild/issues/3210 test(['entry.ts', '--bundle', '--outfile=node.js'], { 'entry.ts': ` import { MyEnum } from './enums'; enum MyEnum2 { 'A.A' = 'a', 'aa' = 'aa', } if ( MyEnum['A.A'] !== 'a' || MyEnum2['A.A'] !== 'a' || MyEnum.aa !== 'aa' || MyEnum2['aa'] !== 'aa' || MyEnum['aa'] !== 'aa' || MyEnum2.aa !== 'aa' ) throw 'fail' `, 'enums.ts': ` export enum MyEnum { 'A.A' = 'a', 'aa' = 'aa', } `, }), ) // Check "tsconfig.json" behavior tests.push( // See: https://github.com/evanw/esbuild/issues/2481 test(['main.ts', '--bundle', '--outfile=node.js'], { 'main.ts': ` import { foo } from 'js-pkg' import { bar } from 'ts-pkg' import { foo as shimFoo, bar as shimBar } from 'pkg' if (foo !== 'foo') throw 'fail: foo' if (bar !== 'bar') throw 'fail: bar' if (shimFoo !== 'shimFoo') throw 'fail: shimFoo' if (shimBar !== 'shimBar') throw 'fail: shimBar' `, 'shim.ts': ` export let foo = 'shimFoo' export let bar = 'shimBar' `, 'tsconfig.json': `{ "compilerOptions": { "paths": { "pkg": ["./shim"], }, }, }`, 'node_modules/js-pkg/index.js': ` import { foo as pkgFoo } from 'pkg' export let foo = pkgFoo `, 'node_modules/ts-pkg/index.ts': ` import { bar as pkgBar } from 'pkg' export let bar = pkgBar `, 'node_modules/pkg/index.js': ` export let foo = 'foo' export let bar = 'bar' `, }), // See: https://github.com/evanw/esbuild/issues/3767 test(['apps/client/src/index.ts', '--bundle', '--outfile=node.js'], { 'apps/client/src/index.ts': ` import { foo } from '~/foo' if (foo !== 'foo') throw 'fail' `, 'apps/client/src/foo.ts': ` export const foo = 'foo' `, 'apps/client/tsconfig.json': `{ "extends": "@repo/tsconfig/base" }`, 'apps/client/node_modules/@repo/tsconfig': { symlink: `../../../../tooling/typescript`, }, 'tooling/typescript/base.json': `{ "compilerOptions": { "paths": { "~/*": ["../../apps/client/src/*"] } } }`, }), ) // Test coverage for a special JSX error message tests.push( test(['example.jsx', '--outfile=node.js'], { 'example.jsx': `let button =