@@ -3,25 +3,24 @@ import { CypressError, getError } from '@packages/errors'
3
3
import type { FullConfig , TestingType } from '@packages/types'
4
4
import { ChildProcess , fork , ForkOptions , spawn } from 'child_process'
5
5
import EventEmitter from 'events'
6
- import fs from 'fs-extra'
7
6
import path from 'path'
8
7
import inspector from 'inspector'
9
8
import debugLib from 'debug'
9
+ import { getTsconfig } from 'get-tsconfig'
10
10
import { autoBindDebug , hasTypeScriptInstalled , toPosix } from '../util'
11
11
import _ from 'lodash'
12
- import { pathToFileURL } from 'url'
13
12
import os from 'os'
14
13
import semver from 'semver'
15
14
import type { OTLPTraceExporterCloud } from '@packages/telemetry'
16
15
import { telemetry , encodeTelemetryContext } from '@packages/telemetry'
17
16
18
17
const pkg = require ( '@packages/root' )
19
18
const debug = debugLib ( `cypress:lifecycle:ProjectConfigIpc` )
19
+ const debugVerbose = debugLib ( `cypress-verbose:lifecycle:ProjectConfigIpc` )
20
20
21
21
const CHILD_PROCESS_FILE_PATH = require . resolve ( '@packages/server/lib/plugins/child/require_async_child' )
22
22
23
- const tsNodeEsm = pathToFileURL ( require . resolve ( 'ts-node/esm/transpile-only' ) ) . href
24
- const tsNode = toPosix ( require . resolve ( '@packages/server/lib/plugins/child/register_ts_node' ) )
23
+ const tsx = toPosix ( require . resolve ( 'tsx' ) )
25
24
26
25
export type IpcHandler = ( ipc : ProjectConfigIpc ) => void
27
26
@@ -250,10 +249,11 @@ export class ProjectConfigIpc extends EventEmitter {
250
249
251
250
private forkConfigProcess ( ) {
252
251
const configProcessArgs = [ '--projectRoot' , this . projectRoot , '--file' , this . configFilePath ]
253
- // allow the use of ts-node in subprocesses tests by removing the env constant from it
252
+ // allow the use of tsx in subprocesses tests by removing the env constant from it
254
253
// without this line, packages/ts/register.js never registers the ts-node module for config and
255
254
// run_plugins can't use the config module.
256
- const env = _ . omit ( process . env , 'CYPRESS_INTERNAL_E2E_TESTING_SELF' )
255
+ // we also do not want telemetry enabled within our cy-in-cy tests as it isn't configured to handled it
256
+ const env = _ . omit ( process . env , 'CYPRESS_INTERNAL_E2E_TESTING_SELF' , 'CYPRESS_INTERNAL_ENABLE_TELEMETRY' )
257
257
258
258
env . NODE_OPTIONS = process . env . ORIGINAL_NODE_OPTIONS || ''
259
259
@@ -273,80 +273,60 @@ export class ProjectConfigIpc extends EventEmitter {
273
273
274
274
debug ( 'fork child process %o' , { CHILD_PROCESS_FILE_PATH , configProcessArgs, childOptions : _ . omit ( childOptions , 'env' ) } )
275
275
276
- let isProjectUsingESModules = false
276
+ if ( ! childOptions . env ) {
277
+ childOptions . env = { }
278
+ }
277
279
278
- try {
279
- // TODO: convert this to async FS methods
280
- // eslint-disable-next-line no-restricted-syntax
281
- const pkgJson = fs . readJsonSync ( path . join ( this . projectRoot , 'package.json' ) )
280
+ // use for 20.6.0 and above
281
+ let tsxLoader = `--import ${ tsx } `
282
282
283
- isProjectUsingESModules = pkgJson . type === 'module'
284
- } catch ( e ) {
285
- // project does not have `package.json` or it was not found
286
- // reasonable to assume not using es modules
283
+ // @see https://tsx.is/dev-api/node-cli#node-js-cli
284
+ if ( this . nodeVersion && semver . lte ( this . nodeVersion , '20.5.1' ) ) {
285
+ debug ( `detected node version ${ this . nodeVersion } . Using deprecated "--loader" flag.` )
286
+ tsxLoader = `--loader ${ tsx } `
287
287
}
288
288
289
- if ( ! childOptions . env ) {
290
- childOptions . env = { }
289
+ // in nodejs 22.7.0, the --experimental-detect-module option is now enabled by default.
290
+ // We need to disable it with the --no-experimental-detect-module flag.
291
+ // @see https://github.com/cypress-io/cypress/issues/30084
292
+ if ( this . nodeVersion && semver . gte ( this . nodeVersion , '22.7.0' ) ) {
293
+ debug ( `detected node version ${ this . nodeVersion } , adding --no-experimental-detect-module option to child_process NODE_OPTIONS.` )
294
+ tsxLoader = `${ tsxLoader } --no-experimental-detect-module`
295
+ }
296
+
297
+ // in nodejs 22.12.0, the --experimental-require-module option is now enabled by default.
298
+ // We need to disable it with the --no-experimental-require-module flag.
299
+ // @see https://github.com/cypress-io/cypress/issues/30715
300
+ if ( this . nodeVersion && semver . gte ( this . nodeVersion , '22.12.0' ) ) {
301
+ debug ( `detected node version ${ this . nodeVersion } , adding --no-experimental-require-module option to child_process NODE_OPTIONS.` )
302
+ tsxLoader = `${ tsxLoader } --no-experimental-require-module`
291
303
}
292
304
293
- // If they've got TypeScript installed, we can use
294
- // ts-node for CommonJS
295
- // ts-node/esm for ESM
296
- if ( hasTypeScriptInstalled ( this . projectRoot ) ) {
305
+ // If they've got TypeScript installed, we can use tsx for CommonJS and ESM.
306
+ // @see https://tsx.is/dev-api/node-cli#node-js-cli
307
+ const hasTs = hasTypeScriptInstalled ( this . projectRoot )
308
+
309
+ if ( hasTs ) {
297
310
debug ( 'found typescript in %s' , this . projectRoot )
298
- if ( isProjectUsingESModules ) {
299
- debug ( `using --experimental-specifier-resolution=node with --loader ${ tsNodeEsm } ` )
300
- // Use the ts-node/esm loader so they can use TypeScript with `"type": "module".
301
- // The loader API is experimental and will change.
302
- // The same can be said for the other alternative, esbuild, so this is the
303
- // best option that leverages the existing modules we bundle in the binary.
304
- // @see ts-node esm loader https://typestrong.org/ts-node/docs/usage/#node-flags-and-other-tools
305
- // @see Node.js Loader API https://nodejs.org/api/esm.html#customizing-esm-specifier-resolution-algorithm
306
- let tsNodeEsmLoader = `--experimental-specifier-resolution=node --loader ${ tsNodeEsm } `
307
-
308
- // in nodejs 22.7.0, the --experimental-detect-module option is now enabled by default.
309
- // We need to disable it with the --no-experimental-detect-module flag.
310
- // @see https://github.com/cypress-io/cypress/issues/30084
311
- if ( this . nodeVersion && semver . gte ( this . nodeVersion , '22.7.0' ) ) {
312
- debug ( `detected node version ${ this . nodeVersion } , adding --no-experimental-detect-module option to child_process NODE_OPTIONS.` )
313
- tsNodeEsmLoader = `${ tsNodeEsmLoader } --no-experimental-detect-module`
314
- }
315
-
316
- // in nodejs 22.12.0, the --experimental-require-module option is now enabled by default.
317
- // We need to disable it with the --no-experimental-require-module flag.
318
- // @see https://github.com/cypress-io/cypress/issues/30715
319
- if ( this . nodeVersion && semver . gte ( this . nodeVersion , '22.12.0' ) ) {
320
- debug ( `detected node version ${ this . nodeVersion } , adding --no-experimental-require-module option to child_process NODE_OPTIONS.` )
321
- tsNodeEsmLoader = `${ tsNodeEsmLoader } --no-experimental-require-module`
322
- }
323
-
324
- if ( childOptions . env . NODE_OPTIONS ) {
325
- childOptions . env . NODE_OPTIONS += ` ${ tsNodeEsmLoader } `
326
- } else {
327
- childOptions . env . NODE_OPTIONS = tsNodeEsmLoader
328
- }
311
+
312
+ const tsConfigIfExists = getTsconfig ( this . projectRoot )
313
+
314
+ if ( tsConfigIfExists ) {
315
+ debug ( `tsconfig.json found at ${ tsConfigIfExists . path } ` )
316
+ childOptions . env . TSX_TSCONFIG_PATH = tsConfigIfExists . path
317
+
318
+ debugVerbose ( `tsconfig.json parsed as follows: %o` , tsConfigIfExists . config )
329
319
} else {
330
- // Not using ES Modules (via "type": "module"),
331
- // so we just register the standard ts-node module
332
- // to handle TypeScript that is compiled to CommonJS.
333
- // We do NOT use the `--loader` flag because we have some additional
334
- // custom logic for ts-node when used with CommonJS that needs to be evaluated
335
- // so we need to load and evaluate the hook first using the `--require` module API.
336
- const tsNodeLoader = `--require "${ tsNode } "`
337
-
338
- debug ( `using cjs with --require ${ tsNode } ` )
339
-
340
- if ( childOptions . env . NODE_OPTIONS ) {
341
- childOptions . env . NODE_OPTIONS += ` ${ tsNodeLoader } `
342
- } else {
343
- childOptions . env . NODE_OPTIONS = tsNodeLoader
344
- }
320
+ debug ( `No tsconfig.json found! Attempting to parse file without tsconfig.json.` )
345
321
}
322
+ }
323
+
324
+ debug ( `using generic ${ tsxLoader } for esm and cjs ${ hasTs ? 'with typescript' : '' } .` )
325
+
326
+ if ( childOptions . env . NODE_OPTIONS ) {
327
+ childOptions . env . NODE_OPTIONS += ` ${ tsxLoader } `
346
328
} else {
347
- // Just use Node's built-in ESM support.
348
- // TODO: Consider using userland `esbuild` with Node's --loader API to handle ESM.
349
- debug ( `no typescript found, just use regular Node.js` )
329
+ childOptions . env . NODE_OPTIONS = tsxLoader
350
330
}
351
331
352
332
const telemetryCtx = encodeTelemetryContext ( { context : telemetry . getActiveContextObject ( ) , version : pkg . version } )
0 commit comments