@exodus/test 1.0.0-rc.4 → 1.0.0-rc.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -28
  3. package/bin/babel-worker.cjs +62 -0
  4. package/bin/bundle.js +305 -0
  5. package/bin/index.js +449 -60
  6. package/bin/jest.js +4 -0
  7. package/bin/typescript.js +3 -0
  8. package/bin/typescript.loader.js +24 -0
  9. package/package.json +116 -16
  10. package/src/bundle-apis/ansi-styles.cjs +49 -0
  11. package/src/bundle-apis/assert-strict.cjs +1 -0
  12. package/src/bundle-apis/child_process.cjs +10 -0
  13. package/src/bundle-apis/crypto.cjs +5 -0
  14. package/src/bundle-apis/empty/function-throw.cjs +4 -0
  15. package/src/bundle-apis/empty/module-throw.cjs +1 -0
  16. package/src/bundle-apis/fs-promises.cjs +1 -0
  17. package/src/bundle-apis/fs.cjs +88 -0
  18. package/src/bundle-apis/globals.cjs +185 -0
  19. package/src/bundle-apis/http.cjs +119 -0
  20. package/src/bundle-apis/https.cjs +11 -0
  21. package/src/bundle-apis/jest-message-util.js +5 -0
  22. package/src/bundle-apis/jest-util.js +22 -0
  23. package/src/bundle-apis/node-buffer.cjs +3 -0
  24. package/src/bundle-apis/util-format.cjs +41 -0
  25. package/src/bundle-apis/ws.cjs +20 -0
  26. package/src/dark.cjs +145 -0
  27. package/src/engine.js +22 -0
  28. package/src/engine.node.cjs +41 -0
  29. package/src/engine.pure.cjs +469 -0
  30. package/src/engine.select.cjs +5 -0
  31. package/src/jest.config.fs.js +54 -0
  32. package/src/jest.config.js +138 -0
  33. package/src/jest.environment.js +76 -0
  34. package/src/jest.fn.js +30 -27
  35. package/src/jest.js +226 -0
  36. package/src/jest.mock.js +265 -0
  37. package/src/jest.snapshot.js +179 -0
  38. package/src/jest.timers.js +98 -0
  39. package/src/node.js +10 -0
  40. package/src/replay.js +103 -0
  41. package/src/tape.cjs +15 -0
  42. package/src/tape.js +160 -0
  43. package/src/version.js +18 -0
  44. package/bin/preload.js +0 -3
  45. package/src/index.js +0 -141
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Exodus Movement, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,18 +1,84 @@
1
1
  # @exodus/test
2
2
 
3
+ A runner for `node:test`, `jest`, and `tape` test suites on top of `node:test` (and any runtime)
4
+
3
5
  Most likely it will just work on your simple jest tests as as drop-in replacement
4
6
 
7
+ Comes with typescript support, optional esm/cjs interop, and also loading babel transforms!
8
+
9
+ Use `--coverage` to generate coverage output
10
+
11
+ Default `NODE_ENV` value is "test", use `NODE_ENV=` to override (e.g. to empty)
12
+
13
+ ## Why?
14
+
15
+ - Can run your tests on Node.js, Bun, Deno, JavaScriptCore and Hermes without extra churn
16
+
17
+ - Unlike `jest`, it is fast
18
+
19
+ - Unlike `node:test`, it is a drop-in replacement for `jest`
20
+
21
+ - With `expect`, support for snapshots, mocks and matchers
22
+
23
+ - `jest-when` and `jest-extended` are fully compatible and can just be used
24
+
25
+ - Snapshots are compatible with Jest and can just be used both ways
26
+
27
+ - Also compatible to `node:test`
28
+
29
+ - Unlike `bun:test`, it runs all test files in isolated contexts
30
+
31
+ Bun leaks globals / side effects between test files and has incompatible `test()` lifecycle / order
32
+
33
+ - Can use Jest config
34
+
35
+ - Native coverage support (enable via `--coverage`)
36
+
37
+ - Can record / replay `fetch` and `WebSocket` sessions. And run them on all runtimes (including Hermes)
38
+
39
+ - Automatic polyfills for JavaScriptCore / Hermes, including crypto
40
+
41
+ - Hanging tests error by default (unlike `jest`)
42
+
43
+ - Native ESM out of the box
44
+
45
+ - Esbuild on the fly for babelified ESM interop (enable via `--esbuild`)
46
+
47
+ - TypeScript support in both transform (enable via `--esbuild`) and typestrip (via `--typescript`) modes
48
+
49
+ - Babel support, picks up your Babel config (enable via `--babel`)
50
+
51
+ - `--drop-network` support for guaranteed offline testing
52
+
5
53
  ## Library
6
54
 
55
+ ### Using with `node:test` natively
56
+
57
+ You can just use pure [`node:test`](https://nodejs.org/api/test.html) in your tests,
58
+ this runner is fully compatible with that (and will set version-specific options for you)!
59
+
7
60
  ### Moving from jest
8
61
 
9
- `import { describe, it, assert, jest, expect } from '@exodus/test'`
62
+ ```js
63
+ import {
64
+ jest,
65
+ expect,
66
+ describe,
67
+ it,
68
+ beforeEach,
69
+ afterEach,
70
+ beforeAll,
71
+ afterAll,
72
+ } from '@exodus/test/jest'
73
+ ```
74
+
75
+ Or, run with [`--jest` option](#options) to register jest globals
10
76
 
11
77
  ### Moving from tap/tape
12
78
 
13
- `import { tap as test } from '@exodus/test'`
14
-
15
- Not all features might be supported
79
+ ```js
80
+ import test from '@exodus/test/tape'
81
+ ```
16
82
 
17
83
  ### Running tests asynchronously
18
84
 
@@ -20,38 +86,19 @@ Add `{ concurrency: true }`, like this: `describe('my testsuite', { concurrency:
20
86
 
21
87
  ### List of exports
22
88
 
23
- Adapters:
24
-
25
- - `jest` -- jest mock adapter
26
- - `tap` -- tap/tape adapter
27
- - `mock`
28
-
29
- Assertions:
89
+ - `@exodus/test/jest` -- `jest` mock
30
90
 
31
- - `assert` -- alias for `node:assert/strict`
32
- - `expect` -- expect with additional features for function mocks
33
-
34
- Suite:
35
-
36
- - `describe`
37
- - `test`
38
- - `it` -- alias for `test`
39
- - `beforeEach`
40
- - `afterEach`
41
- - `before` -- alias for `beforeAll`
42
- - `after` -- alias for `afterAll`
91
+ - `@exodus/test/tape` -- `tape` mock (can also be helpful when moving from `tap`)
43
92
 
44
93
  ## Binary
45
94
 
46
- Just use `"test: "exodus-test"`
95
+ Just use `"test": "exodus-test"`
47
96
 
48
97
  ### Options
49
98
 
50
- - `--global` -- register all test helpers as global variables
51
-
52
- - `--typescript` -- use typescript loader (which also compiles esm to cjs where needed)
99
+ - `--jest` -- register jest test helpers as global variables, also load `jest.config.*` configuration options
53
100
 
54
- - `--esbuild` -- use esbuild loader (currently an alias for `--typescript`)
101
+ - `--esbuild` -- use esbuild loader, also enables Typescript support
55
102
 
56
103
  - `--babel` -- use babel loader (slower than `--esbuild`, makes sense if you have a special config)
57
104
 
@@ -61,4 +108,16 @@ Just use `"test: "exodus-test"`
61
108
 
62
109
  - `--coverage-engine node` -- use Node.js builtint coverage engine
63
110
 
111
+ - `--watch` -- operate in watch mode and re-run tests on file changes
112
+
113
+ - `--only` -- only run the tests marked with `test.only`
114
+
64
115
  - `--passWithNoTests` -- do not error when no test files were found
116
+
117
+ - `--write-snapshots` -- write snapshots instead of verifying them (has `--test-update-snapshots` alias)
118
+
119
+ - `--test-force-exit` -- force exit after tests are done (useful in integration tests where it could be unfeasible to resolve all open handles)
120
+
121
+ ## License
122
+
123
+ [MIT](./LICENSE)
@@ -0,0 +1,62 @@
1
+ const { Worker, MessageChannel, isMainThread, parentPort } = require('node:worker_threads')
2
+ const { once } = require('node:events')
3
+ const { availableParallelism } = require('node:os')
4
+
5
+ if (isMainThread) {
6
+ const maxWorkers = availableParallelism() >= 4 ? 2 : 1
7
+ const workers = []
8
+
9
+ const getWorker = () => {
10
+ const idle = workers.find((info) => info.busy === 0)
11
+ if (idle) return idle
12
+
13
+ if (workers.length < maxWorkers) {
14
+ const worker = new Worker(__filename)
15
+ worker.unref()
16
+ // unhandled top-level errors will crash automatically, which is desired behavior, no need to listen to error
17
+ workers.unshift({ worker, busy: 0 })
18
+ } else if (workers.length > 1) {
19
+ workers.sort((a, b) => a.busy - b.busy)
20
+ }
21
+
22
+ return workers[0]
23
+ }
24
+
25
+ const transformAsync = async (code, options) => {
26
+ const info = getWorker()
27
+ info.busy++
28
+ const channel = new MessageChannel()
29
+ info.worker.postMessage({ port: channel.port1, code, options }, [channel.port1])
30
+ const [{ result, error }] = await once(channel.port2, 'message')
31
+ info.busy--
32
+ if (error) throw error
33
+ return result
34
+ }
35
+
36
+ module.exports = { transformAsync }
37
+ } else {
38
+ const babel = require('@babel/core')
39
+ const tryLoadPlugin = (name) => {
40
+ // Try unwrapping plugin names, as otherwise Babel tries to require them from the wrong dir,
41
+ // which breaks strict directory structure under pnpm in some setups
42
+ try {
43
+ if (typeof name === 'string' && name.startsWith('@babel/plugin-')) return require(name)
44
+ } catch {}
45
+
46
+ return name
47
+ }
48
+
49
+ parentPort.on('message', ({ port, code: input, options }) => {
50
+ try {
51
+ // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only
52
+ if (options.plugins) options.plugins = options.plugins.map((name) => tryLoadPlugin(name))
53
+ const { code, sourcetype, map } = babel.transformSync(input, options) // async here is useless and slower
54
+ // additional properties are deleted as we don't want to transfer e.g. Plugin instances
55
+ port.postMessage({ result: { code, sourcetype, map } })
56
+ } catch (error) {
57
+ port.postMessage({ error })
58
+ }
59
+
60
+ port.close()
61
+ })
62
+ }
package/bin/bundle.js ADDED
@@ -0,0 +1,305 @@
1
+ import assert from 'node:assert/strict'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { existsSync } from 'node:fs'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+ import { basename, dirname, extname, resolve, join } from 'node:path'
6
+ import { createRequire } from 'node:module'
7
+ import { randomUUID as uuid, randomBytes } from 'node:crypto'
8
+ import * as esbuild from 'esbuild'
9
+ import glob from 'fast-glob'
10
+
11
+ const require = createRequire(import.meta.url)
12
+ const resolveRequire = (query) => require.resolve(query)
13
+ const resolveImport = import.meta.resolve && ((query) => fileURLToPath(import.meta.resolve(query)))
14
+
15
+ const readSnapshots = async (files, resolvers) => {
16
+ const snapshots = []
17
+ for (const file of files) {
18
+ for (const resolver of resolvers) {
19
+ const snapshotFile = join(...resolver(dirname(file), basename(file)))
20
+ try {
21
+ snapshots.push([snapshotFile, await readFile(snapshotFile, 'utf8')])
22
+ } catch (e) {
23
+ if (e.code !== 'ENOENT') throw e
24
+ }
25
+ }
26
+ }
27
+
28
+ return snapshots
29
+ }
30
+
31
+ // These packages throw on import
32
+ const blockedDeps = ['@pollyjs/adapter-node-http', '@pollyjs/node-server']
33
+ const loadPipeline = [
34
+ function (source, filepath) {
35
+ return source
36
+ .replace(/\bimport\.meta\.url\b/g, JSON.stringify(pathToFileURL(filepath)))
37
+ .replace(/\b(__dirname|import\.meta\.dirname)\b/g, JSON.stringify(dirname(filepath)))
38
+ .replace(/\b(__filename|import\.meta\.filename)\b/g, JSON.stringify(filepath))
39
+ },
40
+ function (source, filepath) {
41
+ // Just a convenience wrapper to show pretty errors instead of generic bundle-apis/empty/module-throw.cjs
42
+ for (const pkg of blockedDeps) {
43
+ const str = `require(${JSON.stringify(pkg)})`
44
+ assert(!str.includes("'"))
45
+ const err = `module unsupported in bundled form: ${pkg}\n loaded from ${filepath}`
46
+ const rep = `((() => { throw new Error(${JSON.stringify(err)}) })())`
47
+ for (const sub of [str, str.replaceAll('"', "'")]) source = source.replace(sub, rep)
48
+ }
49
+
50
+ return source
51
+ },
52
+ ]
53
+
54
+ const options = {}
55
+
56
+ export const init = async ({ platform, jest, flow, target, jestConfig, outdir }) => {
57
+ Object.assign(options, { platform, jest, flow, target, jestConfig, outdir })
58
+
59
+ if (options.flow) {
60
+ const { default: flowRemoveTypes } = await import('flow-remove-types')
61
+ loadPipeline.unshift((source) => flowRemoveTypes(source, { pretty: true }).toString())
62
+ }
63
+
64
+ if (options.platform === 'hermes') {
65
+ const babel = await import('./babel-worker.cjs')
66
+ loadPipeline.push(async (source) => {
67
+ const result = await babel.transformAsync(source, {
68
+ compact: false,
69
+ babelrc: false,
70
+ configFile: false,
71
+ plugins: [
72
+ '@babel/plugin-syntax-typescript',
73
+ '@babel/plugin-transform-block-scoping',
74
+ '@babel/plugin-transform-class-properties',
75
+ '@babel/plugin-transform-classes',
76
+ '@babel/plugin-transform-private-methods',
77
+ ],
78
+ })
79
+ return result.code
80
+ })
81
+ }
82
+ }
83
+
84
+ const hermesSupported = {
85
+ arrow: false,
86
+ class: false, // we get a safeguard check this way that it's not used
87
+ 'async-generator': false,
88
+ 'const-and-let': false, // have to explicitly set for esbuild to not emit that in helpers, also to get a safeguard check
89
+ 'for-await': false,
90
+ }
91
+
92
+ const getPackageFiles = async (dir) => {
93
+ // Returns an empty list on errors
94
+ let patterns
95
+ try {
96
+ patterns = JSON.parse(await readFile(resolve(dir, 'package.json'), 'utf8')).files
97
+ } catch {}
98
+
99
+ if (!patterns) {
100
+ const parent = dirname(dir)
101
+ if (parent !== dir) return getPackageFiles(parent)
102
+ return []
103
+ }
104
+
105
+ // Hack for now, TODO: fix this
106
+ const expanded = patterns.flatMap((x) => (x.includes('.') ? [x] : [x, `${x}/**/*`]))
107
+ return glob(expanded, { ignore: ['**/node_modules'], cwd: dir, absolute: true })
108
+ }
109
+
110
+ const loadCache = new Map()
111
+ const loadSourceFile = async (filepath) => {
112
+ if (!loadCache.has(filepath)) {
113
+ const load = async () => {
114
+ let contents = await readFile(filepath, 'utf8')
115
+ for (const transform of loadPipeline) contents = await transform(contents, filepath)
116
+ return contents
117
+ }
118
+
119
+ loadCache.set(filepath, load())
120
+ }
121
+
122
+ return loadCache.get(filepath)
123
+ }
124
+
125
+ export const build = async (...files) => {
126
+ const input = []
127
+ const importSource = async (file) => input.push(await loadSourceFile(resolveRequire(file)))
128
+ const importFile = (...args) => input.push(`await import(${JSON.stringify(resolve(...args))});`)
129
+ const stringify = (x) => ([undefined, null].includes(x) ? `${x}` : JSON.stringify(x))
130
+
131
+ if (!['node'].includes(options.platform)) {
132
+ if (['jsc', 'hermes', 'd8'].includes(options.platform)) {
133
+ const entropy = randomBytes(5 * 1024).toString('base64')
134
+ input.push(`globalThis.EXODUS_TEST_CRYPTO_ENTROPY = ${stringify(entropy)};`)
135
+ }
136
+
137
+ await importSource('../src/bundle-apis/globals.cjs')
138
+ }
139
+
140
+ if (options.jest) {
141
+ const { jestConfig } = options
142
+ const preload = [...(jestConfig.setupFiles || []), ...(jestConfig.setupFilesAfterEnv || [])]
143
+ if (jestConfig.testEnvironment && jestConfig.testEnvironment !== 'node') {
144
+ const { specialEnvironments } = await import('../src/jest.environment.js')
145
+ assert(Object.hasOwn(specialEnvironments, jestConfig.testEnvironment))
146
+ preload.push(...(specialEnvironments[jestConfig.testEnvironment].dependencies || []))
147
+ }
148
+
149
+ if (preload.length === 0) {
150
+ input.push(`globalThis.EXODUS_TEST_PRELOADED = []`)
151
+ } else {
152
+ assert(jestConfig.rootDir)
153
+ const local = createRequire(resolve(jestConfig.rootDir, 'package.json'))
154
+ const w = (f) => `[${stringify(f)}, () => require(${stringify(local.resolve(f))})]`
155
+ input.push(`globalThis.EXODUS_TEST_PRELOADED = [${preload.map((f) => w(f)).join(', ')}]`)
156
+ }
157
+
158
+ await importSource('./jest.js')
159
+ }
160
+
161
+ for (const file of files) importFile(file)
162
+
163
+ const filename = files.length === 1 ? `${files[0]}-${uuid().slice(0, 8)}` : `bundle-${uuid()}`
164
+ const outfile = `${join(options.outdir, filename)}.js`
165
+ const EXODUS_TEST_SNAPSHOTS = await readSnapshots(files, [
166
+ (dir, name) => [dir, `${name}.snapshot`], // node:test
167
+ (dir, name) => [dir, '__snapshots__', `${name}.snap`], // jest
168
+ ])
169
+ const EXODUS_TEST_RECORDINGS = await readSnapshots(files, [
170
+ (dir, name) => [dir, '__recordings__', 'fetch', `${name}.json`],
171
+ (dir, name) => [dir, '__recordings__', 'websocket', `${name}.json`],
172
+ ])
173
+ const buildWrap = async (opts) => esbuild.build(opts).catch((err) => err)
174
+ let main = input.join(';\n')
175
+ if (['jsc', 'hermes', 'd8'].includes(options.platform)) {
176
+ const exit = `EXODUS_TEST_PROCESS.exitCode = 1; EXODUS_TEST_PROCESS._maybeProcessExitCode();`
177
+ main = `try {\n${main}\n} catch (err) { print(err); ${exit} }`
178
+ }
179
+
180
+ const fsfiles = await getPackageFiles(filename ? dirname(resolve(filename)) : process.cwd())
181
+
182
+ const hasBuffer = ['node', 'bun'].includes(options.platform)
183
+ const api = (f) => resolveRequire(join('../src/bundle-apis', f))
184
+ const res = await buildWrap({
185
+ logLevel: 'silent',
186
+ stdin: {
187
+ contents: `(async function () {\n${main}\n})()`,
188
+ resolveDir: dirname(fileURLToPath(import.meta.url)),
189
+ },
190
+ bundle: true,
191
+ outdir: options.outdir,
192
+ entryNames: filename,
193
+ platform: 'neutral',
194
+ mainFields: ['browser', 'module', 'main'],
195
+ define: {
196
+ 'process.env.FORCE_COLOR': stringify('0'),
197
+ 'process.env.NO_COLOR': stringify('1'),
198
+ 'process.env.NODE_ENV': stringify(process.env.NODE_ENV),
199
+ 'process.env.EXODUS_TEST_CONTEXT': stringify('pure'),
200
+ 'process.env.EXODUS_TEST_ENVIRONMENT': stringify('bundle'), // always 'bundle'
201
+ 'process.env.EXODUS_TEST_PLATFORM': stringify(process.env.EXODUS_TEST_PLATFORM), // e.g. 'hermes', 'node'
202
+ 'process.env.EXODUS_TEST_ENGINE': stringify(process.env.EXODUS_TEST_ENGINE), // e.g. 'hermes:bundle', 'node:bundle'
203
+ 'process.env.EXODUS_TEST_JEST_CONFIG': stringify(JSON.stringify(options.jestConfig)),
204
+ 'process.env.EXODUS_TEST_EXECARGV': stringify(process.env.EXODUS_TEST_EXECARGV),
205
+ 'process.env.EXODUS_TEST_ONLY': stringify(process.env.EXODUS_TEST_ONLY),
206
+ 'process.env.NODE_DEBUG': stringify(),
207
+ 'process.env.DEBUG': stringify(),
208
+ 'process.env.READABLE_STREAM': stringify(),
209
+ 'process.env.CI': stringify(process.env.CI),
210
+ 'process.env.CI_ENABLE_VERBOSE_LOGS': stringify(process.env.CI_ENABLE_VERBOSE_LOGS),
211
+ 'process.browser': stringify(true),
212
+ 'process.emitWarning': 'undefined',
213
+ 'process.stderr': 'undefined',
214
+ 'process.stdout': 'undefined',
215
+ 'process.type': 'undefined',
216
+ 'process.version': stringify('v22.5.1'), // shouldn't depend on currently used Node.js version
217
+ 'process.versions.node': stringify('22.5.1'), // see line above
218
+ EXODUS_TEST_FILES: stringify(files.map((f) => [dirname(f), basename(f)])),
219
+ EXODUS_TEST_SNAPSHOTS: stringify(EXODUS_TEST_SNAPSHOTS),
220
+ EXODUS_TEST_RECORDINGS: stringify(EXODUS_TEST_RECORDINGS),
221
+ EXODUS_TEST_FSFILES: stringify(fsfiles), // TODO: can we safely use relative paths?
222
+ },
223
+ alias: {
224
+ // Jest and tape
225
+ '@jest/globals': resolveImport('../src/jest.js'),
226
+ tape: resolveImport('../src/tape.cjs'),
227
+ 'tape-promise/tape': resolveImport('../src/tape.cjs'),
228
+ // Node browserify
229
+ 'node:assert': dirname(dirname(resolveRequire('assert/'))),
230
+ 'node:assert/strict': api('assert-strict.cjs'),
231
+ 'node:fs': api('fs.cjs'),
232
+ 'node:fs/promises': api('fs-promises.cjs'),
233
+ fs: api('fs.cjs'),
234
+ 'fs/promises': api('fs-promises.cjs'),
235
+ assert: dirname(dirname(resolveRequire('assert/'))),
236
+ buffer: hasBuffer ? api('node-buffer.cjs') : dirname(resolveRequire('buffer/')),
237
+ child_process: api('child_process.cjs'),
238
+ constants: resolveRequire('constants-browserify'),
239
+ crypto: api('crypto.cjs'),
240
+ events: dirname(resolveRequire('events/')),
241
+ http: api('http.cjs'),
242
+ https: api('https.cjs'),
243
+ os: resolveRequire('os-browserify'),
244
+ path: resolveRequire('path-browserify'),
245
+ querystring: resolveRequire('querystring-es3'),
246
+ stream: resolveRequire('stream-browserify'),
247
+ timers: resolveRequire('timers-browserify'),
248
+ url: dirname(resolveRequire('url/')),
249
+ util: dirname(resolveRequire('util/')),
250
+ zlib: resolveRequire('browserify-zlib'),
251
+ // expect-related deps
252
+ 'ansi-styles': api('ansi-styles.cjs'),
253
+ 'jest-util': api('jest-util.js'),
254
+ 'jest-message-util': api('jest-message-util.js'),
255
+ // unwanted deps
256
+ bindings: api('empty/function-throw.cjs'),
257
+ 'node-gyp-build': api('empty/function-throw.cjs'),
258
+ ws: api('ws.cjs'),
259
+ // unsupported deps
260
+ ...Object.fromEntries(blockedDeps.map((n) => [n, api('empty/module-throw.cjs')])),
261
+ },
262
+ sourcemap: ['hermes', 'jsc', 'd8'].includes(options.platform) ? 'inline' : 'linked', // FIXME?
263
+ sourcesContent: false,
264
+ keepNames: true,
265
+ format: 'iife',
266
+ target: options.target || `node${process.versions.node}`,
267
+ supported: {
268
+ bigint: true,
269
+ ...(options.platform === 'hermes' ? hermesSupported : {}),
270
+ },
271
+ plugins: [
272
+ {
273
+ name: 'exodus-test.bundle',
274
+ setup({ onLoad }) {
275
+ onLoad({ filter: /\.[cm]?[jt]sx?$/, namespace: 'file' }, async (args) => {
276
+ let filepath = args.path
277
+ // Resolve .native versions
278
+ // TODO: move flag to engine options
279
+ // TODO: maybe follow package.json for this
280
+ if (['jsc', 'hermes'].includes(options.platform)) {
281
+ const maybeNative = filepath.replace(/(\.[cm]?[jt]sx?)$/u, '.native$1')
282
+ if (existsSync(maybeNative)) filepath = maybeNative
283
+ }
284
+
285
+ const loader = extname(filepath).replace(/^\.[cm]?/, '') // TODO: a flag to force jsx/tsx perhaps
286
+ assert(['js', 'ts', 'jsx', 'tx'].includes(loader))
287
+
288
+ return { contents: await loadSourceFile(filepath), loader }
289
+ })
290
+ },
291
+ },
292
+ ],
293
+ })
294
+ assert.equal(res instanceof Error, res.errors.length > 0)
295
+
296
+ // if (res.errors.length === 0) require('fs').copyFileSync(outfile, 'tempout.cjs') // DEBUG
297
+
298
+ // We treat warnings as errors, so just merge all them
299
+ const errors = []
300
+ const formatOpts = { color: process.stdout.hasColors?.(), terminalWidth: process.stdout.columns }
301
+ const formatMessages = (list, kind) => esbuild.formatMessages(list, { kind, ...formatOpts })
302
+ if (res.warnings.length > 0) errors.push(...(await formatMessages(res.warnings, 'warning')))
303
+ if (res.errors.length > 0) errors.push(...(await formatMessages(res.errors, 'error')))
304
+ return { file: outfile, errors }
305
+ }