Skip to content

Commit

Permalink
Fix #1004: resolve config relative to realpath of entrypoint (#1009)
Browse files Browse the repository at this point in the history
* add failing test for #1004

* Fix #1004

* Tweak scriptMode entrypoint resolver to include all extensions
  • Loading branch information
cspotcode committed Apr 20, 2020
1 parent 3665824 commit a549b5a
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 2 deletions.
31 changes: 29 additions & 2 deletions src/bin.ts
Expand Up @@ -7,7 +7,7 @@ import Module = require('module')
import arg = require('arg')
import { diffLines } from 'diff'
import { Script } from 'vm'
import { readFileSync, statSync } from 'fs'
import { readFileSync, statSync, realpathSync } from 'fs'
import { homedir } from 'os'
import { VERSION, TSError, parse, Register, register } from './index'

Expand Down Expand Up @@ -154,6 +154,7 @@ export function main (argv: string[]) {
}

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

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

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

return dir
Expand Down Expand Up @@ -481,6 +503,11 @@ function isRecoverable (error: TSError) {
return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code))
}

/** Safe `hasOwnProperty` */
function hasOwnProperty (object: any, property: string): boolean {
return Object.prototype.hasOwnProperty.call(object, property)
}

if (require.main === module) {
main(process.argv.slice(2))
}
8 changes: 8 additions & 0 deletions src/index.spec.ts
Expand Up @@ -379,6 +379,14 @@ describe('ts-node', function () {
expect(err).to.equal(null)
expect(stdout).to.equal('.ts\n')

return done()
})
})
it('should read tsconfig relative to realpath, not symlink, in scriptMode', function (done) {
exec(`node ${BIN_SCRIPT_PATH} tests/main-realpath/symlink/symlink.tsx`, function (err, stdout) {
expect(err).to.equal(null)
expect(stdout).to.equal('')

return done()
})
})
Expand Down
1 change: 1 addition & 0 deletions tests/main-realpath/symlink/symlink.tsx
1 change: 1 addition & 0 deletions tests/main-realpath/symlink/tsconfig.json
@@ -0,0 +1 @@
this tsconfig is intentionally invalid, to confirm that ts-node does *not* attempt to parse it
4 changes: 4 additions & 0 deletions tests/main-realpath/target/target.tsx
@@ -0,0 +1,4 @@
// Will throw a compiler error unless ./tsconfig.json is parsed, which enables JSX
function foo() {
<div></div>
}
5 changes: 5 additions & 0 deletions tests/main-realpath/target/tsconfig.json
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"jsx": "react"
}
}

0 comments on commit a549b5a

Please sign in to comment.