Skip to content

Commit

Permalink
fix: use tsconfck to handle json-c
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Nov 3, 2023
1 parent 0b15536 commit 7f0c9f0
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 69 deletions.
49 changes: 7 additions & 42 deletions src/config/ts-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,30 @@ import * as TSNode from 'ts-node'
import {memoizedWarn} from '../errors'
import {Plugin, TSConfig} from '../interfaces'
import {settings} from '../settings'
import {existsSync, safeReadFile} 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 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
function isErrno(error: any): error is NodeJS.ErrnoException {
return 'code' in error
}

async function loadTSConfig(root: string): Promise<TSConfig | undefined> {
try {
if (TS_CONFIGS[root]) return TS_CONFIGS[root]

const typescript = importTypescript(root)
if (!typescript) return

const tsconfigPath = join(root, 'tsconfig.json')
const raw = await safeReadFile(tsconfigPath)
if (!raw) return

const {config} = typescript.parseConfigFileTextToJson(tsconfigPath, raw)

if (!config || Object.keys(config.compilerOptions).length === 0) return

TS_CONFIGS[root] = config

if (config.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 {
} catch (error) {
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
21 changes: 9 additions & 12 deletions src/util/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,21 @@ export function readJsonSync<T = unknown>(path: string, parse = true): T | strin
return parse ? (JSON.parse(contents) as T) : contents
}

/**
* Read a JSON file, returning undefined if the file does not exist.
*/
export async function safeReadJson<T>(path: string): Promise<T | undefined> {
try {
return await readJson<T>(path)
} catch {}
}

/**
* Read a file, returning undefined if the file does not exist.
*/
export async function safeReadFile(path: string): Promise<string | undefined> {
try {
return await readFile(path, 'utf8')
} catch {}
}

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: 13 additions & 15 deletions test/config/ts-node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,50 +42,48 @@ describe('tsPath', () => {
})

it('should resolve a .js file to ts src', async () => {
sandbox.stub(util, 'safeReadFile').resolves(JSON.stringify(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, 'safeReadFile').resolves(JSON.stringify(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, 'safeReadFile').resolves(JSON.stringify(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, 'safeReadFile').resolves(
JSON.stringify({
compilerOptions: {
baseUrl: '.src/',
outDir: 'lib',
},
}),
)
sandbox.stub(util, 'readTSConfig').resolves({
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, 'safeReadFile').resolves(JSON.stringify({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, 'safeReadFile').resolves(JSON.stringify({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, 'safeReadFile').resolves(JSON.stringify(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,7 +96,7 @@ describe('tsPath', () => {
})

it('should resolve to js if disabled', async () => {
sandbox.stub(util, 'safeReadFile').resolves(JSON.stringify(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))
Expand Down

0 comments on commit 7f0c9f0

Please sign in to comment.