Skip to content

Commit a549b5a

Browse files
authored
Fix #1004: resolve config relative to realpath of entrypoint (#1009)
* add failing test for #1004 * Fix #1004 * Tweak scriptMode entrypoint resolver to include all extensions
1 parent 3665824 commit a549b5a

File tree

6 files changed

+48
-2
lines changed

6 files changed

+48
-2
lines changed

src/bin.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Module = require('module')
77
import arg = require('arg')
88
import { diffLines } from 'diff'
99
import { Script } from 'vm'
10-
import { readFileSync, statSync } from 'fs'
10+
import { readFileSync, statSync, realpathSync } from 'fs'
1111
import { homedir } from 'os'
1212
import { VERSION, TSError, parse, Register, register } from './index'
1313

@@ -154,6 +154,7 @@ export function main (argv: string[]) {
154154
}
155155

156156
const cwd = dir || process.cwd()
157+
/** Unresolved. May point to a symlink, not realpath. May be missing file extension */
157158
const scriptPath = args._.length ? resolve(cwd, args._[0]) : undefined
158159
const state = new EvalState(scriptPath || join(cwd, EVAL_FILENAME))
159160

@@ -251,7 +252,28 @@ function getCwd (dir?: string, scriptMode?: boolean, scriptPath?: string) {
251252
throw new TypeError('Script mode cannot be combined with `--dir`')
252253
}
253254

254-
return dirname(scriptPath)
255+
// Use node's own resolution behavior to ensure we follow symlinks.
256+
// scriptPath may omit file extension or point to a directory with or without package.json.
257+
// This happens before we are registered, so we tell node's resolver to consider ts, tsx, and jsx files.
258+
// In extremely rare cases, is is technically possible to resolve the wrong directory,
259+
// because we do not yet know preferTsExts, jsx, nor allowJs.
260+
// See also, justification why this will not happen in real-world situations:
261+
// https://github.com/TypeStrong/ts-node/pull/1009#issuecomment-613017081
262+
const exts = ['.js', '.jsx', '.ts', '.tsx']
263+
const extsTemporarilyInstalled: string[] = []
264+
for (const ext of exts) {
265+
if (!hasOwnProperty(require.extensions, ext)) { // tslint:disable-line
266+
extsTemporarilyInstalled.push(ext)
267+
require.extensions[ext] = function() {} // tslint:disable-line
268+
}
269+
}
270+
try {
271+
return dirname(require.resolve(scriptPath))
272+
} finally {
273+
for (const ext of extsTemporarilyInstalled) {
274+
delete require.extensions[ext] // tslint:disable-line
275+
}
276+
}
255277
}
256278

257279
return dir
@@ -481,6 +503,11 @@ function isRecoverable (error: TSError) {
481503
return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code))
482504
}
483505

506+
/** Safe `hasOwnProperty` */
507+
function hasOwnProperty (object: any, property: string): boolean {
508+
return Object.prototype.hasOwnProperty.call(object, property)
509+
}
510+
484511
if (require.main === module) {
485512
main(process.argv.slice(2))
486513
}

src/index.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,14 @@ describe('ts-node', function () {
379379
expect(err).to.equal(null)
380380
expect(stdout).to.equal('.ts\n')
381381

382+
return done()
383+
})
384+
})
385+
it('should read tsconfig relative to realpath, not symlink, in scriptMode', function (done) {
386+
exec(`node ${BIN_SCRIPT_PATH} tests/main-realpath/symlink/symlink.tsx`, function (err, stdout) {
387+
expect(err).to.equal(null)
388+
expect(stdout).to.equal('')
389+
382390
return done()
383391
})
384392
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../target/target.tsx
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
this tsconfig is intentionally invalid, to confirm that ts-node does *not* attempt to parse it

tests/main-realpath/target/target.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Will throw a compiler error unless ./tsconfig.json is parsed, which enables JSX
2+
function foo() {
3+
<div></div>
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"jsx": "react"
4+
}
5+
}

0 commit comments

Comments
 (0)