Skip to content

Commit

Permalink
fix: support jsonc tsconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Nov 3, 2023
1 parent 7aec22e commit 026f835
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 36 deletions.
41 changes: 31 additions & 10 deletions src/config/ts-node.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {readFile} from 'node:fs/promises'
import {join, relative as pathRelative, sep} from 'node:path'
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} from '../util/fs'
import {isProd} from '../util/util'
import Cache from './cache'
import {Debug} from './util'
Expand All @@ -15,17 +16,39 @@ const debug = Debug('ts-node')
export const TS_CONFIGS: Record<string, TSConfig> = {}
const REGISTERED = new Set<string>()

function importTypescript(root: string) {
let typescript: typeof import('typescript') | undefined
try {
typescript = require('typescript')
} catch {
try {
typescript = require(require.resolve('typescript', {paths: [root, __dirname]}))
} catch {
debug(`Could not find typescript dependency. Skipping ts-node registration for ${root}.`)
memoizedWarn(
'Could not find typescript. Please ensure that typescript is a devDependency. Falling back to compiled source.',
)
return
}
}

return typescript
}

async function loadTSConfig(root: string): Promise<TSConfig | undefined> {
try {
if (TS_CONFIGS[root]) return TS_CONFIGS[root]
const tsconfigPath = join(root, 'tsconfig.json')
const tsconfig = await readJson<TSConfig>(tsconfigPath)
const typescript = importTypescript(root)
if (!typescript) return

if (!tsconfig || Object.keys(tsconfig.compilerOptions).length === 0) return
const {config} = typescript.parseConfigFileTextToJson(tsconfigPath, await readFile(tsconfigPath, 'utf8'))

TS_CONFIGS[root] = tsconfig
if (!config || Object.keys(config.compilerOptions).length === 0) return

if (tsconfig.extends) {
TS_CONFIGS[root] = config

if (config.extends) {
const {parse} = await import('tsconfck')
const result = await parse(tsconfigPath)
const tsNodeOpts = Object.fromEntries(
Expand All @@ -36,11 +59,9 @@ async function loadTSConfig(root: string): Promise<TSConfig | undefined> {
}

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.`)
}
} catch {
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.`)
}
}

Expand Down
42 changes: 16 additions & 26 deletions test/config/ts-node.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {expect} from 'chai'
import fs from 'node:fs/promises'
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'

const root = resolve(__dirname, 'fixtures/typescript')
const tsSource = 'src/hooks/init.ts'
Expand Down Expand Up @@ -44,48 +42,50 @@ describe('tsPath', () => {
})

it('should resolve a .js file to ts src', async () => {
sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG)
sandbox.stub(fs, 'readFile').resolves(JSON.stringify(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(fs, 'readFile').resolves(JSON.stringify(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(fs, 'readFile').resolves(JSON.stringify(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({
compilerOptions: {
baseUrl: '.src/',
outDir: 'lib',
},
})
sandbox.stub(fs, 'readFile').resolves(
JSON.stringify({
compilerOptions: {
baseUrl: '.src/',
outDir: 'lib',
},
}),
)
const result = await configTsNode.tsPath(root, tsSource)
expect(result).to.equal(join(root, tsSource))
})

it('should resolve .ts with no outDir', async () => {
sandbox.stub(util, 'readJson').resolves({compilerOptions: {rootDir: 'src'}})
sandbox.stub(fs, 'readFile').resolves(JSON.stringify({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(fs, 'readFile').resolves(JSON.stringify({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(fs, 'readFile').resolves(JSON.stringify(DEFAULT_TS_CONFIG))
settings.tsnodeEnabled = true
const originalNodeEnv = process.env.NODE_ENV
delete process.env.NODE_ENV
Expand All @@ -98,21 +98,11 @@ describe('tsPath', () => {
})

it('should resolve to js if disabled', async () => {
sandbox.stub(util, 'readJson').resolves(DEFAULT_TS_CONFIG)
sandbox.stub(fs, 'readFile').resolves(JSON.stringify(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',
)
})
})

0 comments on commit 026f835

Please sign in to comment.