Skip to content

Commit

Permalink
Merge pull request #850 from oclif/mdonnalley/json-c
Browse files Browse the repository at this point in the history
fix: support jsonc tsconfig
  • Loading branch information
mdonnalley authored Nov 3, 2023
2 parents 7aec22e + 44fb9dc commit edc3cd2
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 41 deletions.
31 changes: 10 additions & 21 deletions src/config/ts-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, TSConfig> = {}
const REGISTERED = new Set<string>()

function isErrno(error: any): error is NodeJS.ErrnoException {
return 'code' in error && error.code === 'ENOENT'
}

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)

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

Expand Down
9 changes: 9 additions & 0 deletions src/util/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,12 @@ export async function safeReadJson<T>(path: string): Promise<T | undefined> {
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}
}
28 changes: 8 additions & 20 deletions test/config/ts-node.test.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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',
Expand All @@ -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
Expand All @@ -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',
)
})
})

0 comments on commit edc3cd2

Please sign in to comment.