diff --git a/src/config/ts-node.ts b/src/config/ts-node.ts index 28d2ed7a0..15c52678b 100644 --- a/src/config/ts-node.ts +++ b/src/config/ts-node.ts @@ -4,43 +4,32 @@ import * as TSNode from 'ts-node' import {memoizedWarn} from '../errors' import {Plugin, TSConfig} from '../interfaces' import {settings} from '../settings' -import {existsSync, readJson} from '../util/fs' +import {existsSync, readTSConfig} from '../util/fs' import {isProd} from '../util/util' import Cache from './cache' import {Debug} from './util' - // eslint-disable-next-line new-cap const debug = Debug('ts-node') export const TS_CONFIGS: Record = {} const REGISTERED = new Set() +function isErrno(error: any): error is NodeJS.ErrnoException { + return 'code' in error && error.code === 'ENOENT' +} + async function loadTSConfig(root: string): Promise { try { if (TS_CONFIGS[root]) return TS_CONFIGS[root] - const tsconfigPath = join(root, 'tsconfig.json') - const tsconfig = await readJson(tsconfigPath) - - if (!tsconfig || Object.keys(tsconfig.compilerOptions).length === 0) return - - TS_CONFIGS[root] = tsconfig - if (tsconfig.extends) { - const {parse} = await import('tsconfck') - const result = await parse(tsconfigPath) - const tsNodeOpts = Object.fromEntries( - (result.extended ?? []).flatMap((e) => Object.entries(e.tsconfig['ts-node'] ?? {})).reverse(), - ) - - TS_CONFIGS[root] = {...result.tsconfig, 'ts-node': tsNodeOpts} - } + TS_CONFIGS[root] = await readTSConfig(join(root, 'tsconfig.json')) return TS_CONFIGS[root] } catch (error) { - if (error instanceof SyntaxError) { - debug(`Could not parse tsconfig.json. Skipping ts-node registration for ${root}.`) - memoizedWarn(`Could not parse tsconfig.json for ${root}. Falling back to compiled source.`) - } + if (isErrno(error)) return + + debug(`Could not parse tsconfig.json. Skipping ts-node registration for ${root}.`) + memoizedWarn(`Could not parse tsconfig.json for ${root}. Falling back to compiled source.`) } } diff --git a/src/util/fs.ts b/src/util/fs.ts index 50e04a99f..2afc49591 100644 --- a/src/util/fs.ts +++ b/src/util/fs.ts @@ -69,3 +69,12 @@ export async function safeReadJson(path: string): Promise { export function existsSync(path: string): boolean { return fsExistsSync(path) } + +export async function readTSConfig(path: string) { + const {parse} = await import('tsconfck') + const result = await parse(path) + const tsNodeOpts = Object.fromEntries( + (result.extended ?? []).flatMap((e) => Object.entries(e.tsconfig['ts-node'] ?? {})).reverse(), + ) + return {...result.tsconfig, 'ts-node': tsNodeOpts} +} diff --git a/test/config/ts-node.test.ts b/test/config/ts-node.test.ts index 83ba9861b..e8470b1b7 100644 --- a/test/config/ts-node.test.ts +++ b/test/config/ts-node.test.ts @@ -1,10 +1,8 @@ import {expect} from 'chai' import {join, resolve} from 'node:path' import {SinonSandbox, createSandbox} from 'sinon' -import stripAnsi from 'strip-ansi' import * as tsNode from 'ts-node' -import write from '../../src/cli-ux/write' import * as configTsNode from '../../src/config/ts-node' import {Interfaces, settings} from '../../src/index' import * as util from '../../src/util/fs' @@ -44,25 +42,25 @@ describe('tsPath', () => { }) it('should resolve a .js file to ts src', async () => { - sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG) + sandbox.stub(util, 'readTSConfig').resolves(DEFAULT_TS_CONFIG) const result = await configTsNode.tsPath(root, jsCompiled) expect(result).to.equal(join(root, tsModule)) }) it('should resolve a module file to ts src', async () => { - sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG) + sandbox.stub(util, 'readTSConfig').resolves(DEFAULT_TS_CONFIG) const result = await configTsNode.tsPath(root, jsCompiledModule) expect(result).to.equal(join(root, tsModule)) }) it('should resolve a .ts file', async () => { - sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG) + sandbox.stub(util, 'readTSConfig').resolves(DEFAULT_TS_CONFIG) const result = await configTsNode.tsPath(root, tsSource) expect(result).to.equal(join(root, tsSource)) }) it('should resolve a .ts file using baseUrl', async () => { - sandbox.stub(util, 'readJson').resolves({ + sandbox.stub(util, 'readTSConfig').resolves({ compilerOptions: { baseUrl: '.src/', outDir: 'lib', @@ -73,19 +71,19 @@ describe('tsPath', () => { }) it('should resolve .ts with no outDir', async () => { - sandbox.stub(util, 'readJson').resolves({compilerOptions: {rootDir: 'src'}}) + sandbox.stub(util, 'readTSConfig').resolves({compilerOptions: {rootDir: 'src'}}) const result = await configTsNode.tsPath(root, tsSource) expect(result).to.equal(join(root, tsSource)) }) it('should resolve .js with no rootDir and outDir', async () => { - sandbox.stub(util, 'readJson').resolves({compilerOptions: {strict: true}}) + sandbox.stub(util, 'readTSConfig').resolves({compilerOptions: {strict: true}}) const result = await configTsNode.tsPath(root, jsCompiled) expect(result).to.equal(join(root, jsCompiled)) }) it('should resolve to .ts file if enabled and prod', async () => { - sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG) + sandbox.stub(util, 'readTSConfig').resolves(DEFAULT_TS_CONFIG) settings.tsnodeEnabled = true const originalNodeEnv = process.env.NODE_ENV delete process.env.NODE_ENV @@ -98,21 +96,11 @@ describe('tsPath', () => { }) it('should resolve to js if disabled', async () => { - sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG) + sandbox.stub(util, 'readTSConfig').resolves(DEFAULT_TS_CONFIG) settings.tsnodeEnabled = false const result = await configTsNode.tsPath(root, jsCompiled) expect(result).to.equal(join(root, jsCompiled)) delete settings.tsnodeEnabled }) - - it('should handle SyntaxError', async () => { - sandbox.stub(util, 'readJson').throws(new SyntaxError('Unexpected token } in JSON at position 0')) - const stderrStub = sandbox.stub(write, 'stderr') - const result = await configTsNode.tsPath(root, tsSource) - expect(result).to.equal(join(root, tsSource)) - expect(stripAnsi(stderrStub.firstCall.firstArg).split('\n').join(' ')).to.include( - 'Warning: Could not parse tsconfig.json', - ) - }) })