From 17b3a55254983f1e3170fd6348f0ee216788ae97 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sat, 5 Jun 2021 21:56:12 -0400 Subject: [PATCH] Un-deprecate scope and scopedir; add to CLI, tsconfig.json, and env vars (#1367) * re-add scope and scopeDir to CLI; add env vars * fix tests and bug found by tests * Fix typo --- src/bin.ts | 55 ++++++++++------- src/configuration.ts | 21 +++++-- src/index.ts | 8 +-- src/test/index.spec.ts | 84 ++++++++++++++------------ tests/scope/c/config/index.ts | 1 + tests/scope/c/config/scopedir/index.ts | 1 + tests/scope/c/config/tsconfig.json | 6 ++ tests/scope/c/index.js | 26 ++++++++ tests/scope/c/scopedir/index.ts | 1 + website/docs/options.md | 2 + 10 files changed, 135 insertions(+), 70 deletions(-) create mode 100644 tests/scope/c/config/index.ts create mode 100644 tests/scope/c/config/scopedir/index.ts create mode 100644 tests/scope/c/config/tsconfig.json create mode 100644 tests/scope/c/index.js create mode 100644 tests/scope/c/scopedir/index.ts diff --git a/src/bin.ts b/src/bin.ts index 934fb66a9..9eef1b67c 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -51,6 +51,8 @@ export function main( '--prefer-ts-exts': Boolean, '--log-error': Boolean, '--emit': Boolean, + '--scope': Boolean, + '--scope-dir': String, // Aliases. '-e': '--eval', @@ -69,6 +71,7 @@ export function main( '-O': '--compiler-options', '--dir': '--cwd', '--showConfig': '--show-config', + '--scopeDir': '--scope-dir', }, { argv, @@ -107,6 +110,8 @@ export function main( '--prefer-ts-exts': preferTsExts, '--log-error': logError, '--emit': emit, + '--scope': scope = undefined, + '--scope-dir': scopeDir = undefined, } = args; if (help) { @@ -115,32 +120,34 @@ export function main( Options: - -e, --eval [code] Evaluate code - -p, --print Print result of \`--eval\` - -r, --require [path] Require a node module before execution - -i, --interactive Opens the REPL even if stdin does not appear to be a terminal - - -h, --help Print CLI usage - -v, --version Print module version information - --cwd-mode Use current directory instead of for config resolution - --show-config Print resolved configuration and exit - - -T, --transpile-only Use TypeScript's faster \`transpileModule\` or a third-party transpiler - -H, --compiler-host Use TypeScript's compiler host API - -I, --ignore [pattern] Override the path patterns to skip compilation - -P, --project [path] Path to TypeScript JSON project file - -C, --compiler [name] Specify a custom TypeScript compiler - --transpiler [name] Specify a third-party, non-typechecking transpiler + -e, --eval [code] Evaluate code + -p, --print Print result of \`--eval\` + -r, --require [path] Require a node module before execution + -i, --interactive Opens the REPL even if stdin does not appear to be a terminal + + -h, --help Print CLI usage + -v, --version Print module version information + --cwd-mode Use current directory instead of for config resolution + --show-config Print resolved configuration and exit + + -T, --transpile-only Use TypeScript's faster \`transpileModule\` or a third-party transpiler + -H, --compiler-host Use TypeScript's compiler host API + -I, --ignore [pattern] Override the path patterns to skip compilation + -P, --project [path] Path to TypeScript JSON project file + -C, --compiler [name] Specify a custom TypeScript compiler + --transpiler [name] Specify a third-party, non-typechecking transpiler -D, --ignore-diagnostics [code] Ignore TypeScript warnings by diagnostic code -O, --compiler-options [opts] JSON object to merge with compiler options - --cwd Behave as if invoked within this working directory. - --files Load \`files\`, \`include\` and \`exclude\` from \`tsconfig.json\` on startup - --pretty Use pretty diagnostic formatter (usually enabled by default) - --skip-project Skip reading \`tsconfig.json\` - --skip-ignore Skip \`--ignore\` checks - --prefer-ts-exts Prefer importing TypeScript files over JavaScript files - --log-error Logs TypeScript errors to stderr instead of throwing exceptions + --cwd Behave as if invoked within this working directory. + --files Load \`files\`, \`include\` and \`exclude\` from \`tsconfig.json\` on startup + --pretty Use pretty diagnostic formatter (usually enabled by default) + --skip-project Skip reading \`tsconfig.json\` + --skip-ignore Skip \`--ignore\` checks + --scope Scope compiler to files within \`scopeDir\`. Anything outside this directory is ignored. + --scope-dir Directory for \`--scope\` + --prefer-ts-exts Prefer importing TypeScript files over JavaScript files + --log-error Logs TypeScript errors to stderr instead of throwing exceptions `); process.exit(0); @@ -183,6 +190,8 @@ export function main( readFile: code !== undefined ? evalAwarePartialHost.readFile : undefined, fileExists: code !== undefined ? evalAwarePartialHost.fileExists : undefined, + scope, + scopeDir, }); // Bind REPL service to ts-node compiler service (chicken-and-egg problem) diff --git a/src/configuration.ts b/src/configuration.ts index 986de4d54..1b69d4550 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -107,7 +107,7 @@ export function readConfig( // Fix ts-node options that come from tsconfig.json const tsNodeOptionsFromTsconfig: TsConfigOptions = Object.assign( {}, - filterRecognizedTsConfigTsNodeOptions(config['ts-node']) + filterRecognizedTsConfigTsNodeOptions(config['ts-node']).recognized ); // Remove resolution of "files". @@ -160,6 +160,8 @@ export function readConfig( ) ); + // Some options are relative to the config file, so must be converted to absolute paths here + if (tsNodeOptionsFromTsconfig.require) { // Modules are found relative to the tsconfig file, not the `dir` option const tsconfigRelativeRequire = createRequire(configFilePath!); @@ -169,6 +171,12 @@ export function readConfig( } ); } + if (tsNodeOptionsFromTsconfig.scopeDir) { + tsNodeOptionsFromTsconfig.scopeDir = resolve( + basePath, + tsNodeOptionsFromTsconfig.scopeDir + ); + } return { configFilePath, config: fixedConfig, tsNodeOptionsFromTsconfig }; } @@ -179,8 +187,8 @@ export function readConfig( */ function filterRecognizedTsConfigTsNodeOptions( jsonObject: any -): TsConfigOptions { - if (jsonObject == null) return jsonObject; +): { recognized: TsConfigOptions; unrecognized: any } { + if (jsonObject == null) return { recognized: jsonObject, unrecognized: {} }; const { compiler, compilerHost, @@ -197,6 +205,9 @@ function filterRecognizedTsConfigTsNodeOptions( transpileOnly, typeCheck, transpiler, + scope, + scopeDir, + ...unrecognized } = jsonObject as TsConfigOptions; const filteredTsConfigOptions = { compiler, @@ -214,9 +225,11 @@ function filterRecognizedTsConfigTsNodeOptions( transpileOnly, typeCheck, transpiler, + scope, + scopeDir, }; // Use the typechecker to make sure this implementation has the correct set of properties const catchExtraneousProps: keyof TsConfigOptions = (null as any) as keyof typeof filteredTsConfigOptions; const catchMissingProps: keyof typeof filteredTsConfigOptions = (null as any) as keyof TsConfigOptions; - return filteredTsConfigOptions; + return { recognized: filteredTsConfigOptions, unrecognized }; } diff --git a/src/index.ts b/src/index.ts index 9aea226d4..a3605d1ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -73,8 +73,8 @@ export interface ProcessEnv { /** @deprecated */ TS_NODE_DIR?: string; TS_NODE_EMIT?: string; - /** @deprecated */ TS_NODE_SCOPE?: string; + TS_NODE_SCOPE_DIR?: string; TS_NODE_FILES?: string; TS_NODE_PRETTY?: string; TS_NODE_COMPILER?: string; @@ -296,8 +296,6 @@ export interface TsConfigOptions | 'dir' | 'cwd' | 'projectSearchDir' - | 'scope' - | 'scopeDir' | 'experimentalEsmLoader' > {} @@ -318,6 +316,7 @@ export const DEFAULTS: RegisterOptions = { cwd: env.TS_NODE_CWD ?? env.TS_NODE_DIR, emit: yn(env.TS_NODE_EMIT), scope: yn(env.TS_NODE_SCOPE), + scopeDir: env.TS_NODE_SCOPE_DIR, files: yn(env.TS_NODE_FILES), pretty: yn(env.TS_NODE_PRETTY), compiler: env.TS_NODE_COMPILER, @@ -1292,7 +1291,8 @@ function registerExtension( m._compile = function (code: string, fileName: string) { debug('module._compile', fileName); - return _compile.call(this, service.compile(code, fileName), fileName); + const result = service.compile(code, fileName); + return _compile.call(this, result, fileName); }; return old(m, filename); diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index b58789f62..8e14bda9a 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -923,6 +923,14 @@ test.suite('ts-node', (test) => { expect(stderr).to.contain('Error: --show-config requires'); }); } + + test('should support compiler scope specified via tsconfig.json', async (t) => { + const { err, stderr, stdout } = await exec( + `${cmd} --project ./scope/c/config/tsconfig.json ./scope/c/index.js` + ); + expect(err).to.equal(null); + expect(stdout).to.equal(`value\nFailures: 0\n`); + }); }); test.suite('register', (_test) => { @@ -977,53 +985,51 @@ test.suite('ts-node', (test) => { expect(() => require(moduleTestPath)).to.not.throw(); }); - if (semver.gte(ts.version, '2.7.0')) { - test('should support compiler scopes', ({ - context: { registered, moduleTestPath }, - }) => { - const calls: string[] = []; + test('should support compiler scopes', ({ + context: { registered, moduleTestPath }, + }) => { + const calls: string[] = []; - registered.enabled(false); + registered.enabled(false); - const compilers = [ - register({ - projectSearchDir: join(TEST_DIR, 'scope/a'), - scopeDir: join(TEST_DIR, 'scope/a'), - scope: true, - }), - register({ - projectSearchDir: join(TEST_DIR, 'scope/a'), - scopeDir: join(TEST_DIR, 'scope/b'), - scope: true, - }), - ]; + const compilers = [ + register({ + projectSearchDir: join(TEST_DIR, 'scope/a'), + scopeDir: join(TEST_DIR, 'scope/a'), + scope: true, + }), + register({ + projectSearchDir: join(TEST_DIR, 'scope/a'), + scopeDir: join(TEST_DIR, 'scope/b'), + scope: true, + }), + ]; - compilers.forEach((c) => { - const old = c.compile; - c.compile = (code, fileName, lineOffset) => { - calls.push(fileName); + compilers.forEach((c) => { + const old = c.compile; + c.compile = (code, fileName, lineOffset) => { + calls.push(fileName); - return old(code, fileName, lineOffset); - }; - }); + return old(code, fileName, lineOffset); + }; + }); - try { - expect(require('../../tests/scope/a').ext).to.equal('.ts'); - expect(require('../../tests/scope/b').ext).to.equal('.ts'); - } finally { - compilers.forEach((c) => c.enabled(false)); - } + try { + expect(require('../../tests/scope/a').ext).to.equal('.ts'); + expect(require('../../tests/scope/b').ext).to.equal('.ts'); + } finally { + compilers.forEach((c) => c.enabled(false)); + } - expect(calls).to.deep.equal([ - join(TEST_DIR, 'scope/a/index.ts'), - join(TEST_DIR, 'scope/b/index.ts'), - ]); + expect(calls).to.deep.equal([ + join(TEST_DIR, 'scope/a/index.ts'), + join(TEST_DIR, 'scope/b/index.ts'), + ]); - delete require.cache[moduleTestPath]; + delete require.cache[moduleTestPath]; - expect(() => require(moduleTestPath)).to.throw(); - }); - } + expect(() => require(moduleTestPath)).to.throw(); + }); test('should compile through js and ts', () => { const m = require('../../tests/complex'); diff --git a/tests/scope/c/config/index.ts b/tests/scope/c/config/index.ts new file mode 100644 index 000000000..7a4ee038d --- /dev/null +++ b/tests/scope/c/config/index.ts @@ -0,0 +1 @@ +export const a: string = 'value'; diff --git a/tests/scope/c/config/scopedir/index.ts b/tests/scope/c/config/scopedir/index.ts new file mode 100644 index 000000000..7a4ee038d --- /dev/null +++ b/tests/scope/c/config/scopedir/index.ts @@ -0,0 +1 @@ +export const a: string = 'value'; diff --git a/tests/scope/c/config/tsconfig.json b/tests/scope/c/config/tsconfig.json new file mode 100644 index 000000000..f4adf0744 --- /dev/null +++ b/tests/scope/c/config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "ts-node": { + "scope": true, + "scopeDir": "./scopedir" + } +} diff --git a/tests/scope/c/index.js b/tests/scope/c/index.js new file mode 100644 index 000000000..a84053dec --- /dev/null +++ b/tests/scope/c/index.js @@ -0,0 +1,26 @@ +let failures = 0; +try { + // This should fail with an error because it is outside scopedir + require('./scopedir/index'); + failures++; +} catch (e) { + // good +} + +try { + // This should fail with an error because it is outside scopedir + require('./config/index'); + failures++; +} catch (e) { + // good +} + +try { + // this should succeed + console.log(require('./config/scopedir/index').a); +} catch (e) { + // bad + failures++; +} + +console.log(`Failures: ${failures}`); diff --git a/tests/scope/c/scopedir/index.ts b/tests/scope/c/scopedir/index.ts new file mode 100644 index 000000000..7a4ee038d --- /dev/null +++ b/tests/scope/c/scopedir/index.ts @@ -0,0 +1 @@ +export const a: string = 'value'; diff --git a/website/docs/options.md b/website/docs/options.md index 93d7c42ce..7548ac47a 100644 --- a/website/docs/options.md +++ b/website/docs/options.md @@ -49,6 +49,8 @@ _Environment variables, where available, are in `ALL_CAPS`_ - `-r, --require [path]` Require a node module before execution - `--cwd` Behave as if invoked in this working directory
*Default:* `process.cwd()`
*Environment:* `TS_NODE_CWD` - `--emit` Emit output files into `.ts-node` directory
*Default:* `false`
*Environment:* `TS_NODE_EMIT` +- `--scope` Scope compiler to files within `scopeDir`. Anything outside this directory is ignored.
*Default: `false`
*Environment:* `TS_NODE_SCOPE` +- `--scopeDir` Directory within which compiler is limited when `scope` is enabled.
*Default:* First of: `tsconfig.json` "rootDir" if specified, directory containing `tsconfig.json`, or cwd if no `tsconfig.json` is loaded.
*Environment:* `TS_NODE_SCOPE_DIR` - `TS_NODE_HISTORY` Path to history file for REPL
*Default:* `~/.ts_node_repl_history`
## API