diff --git a/.gitignore b/.gitignore index f5f954c83..574e3cfc4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ coverage/ .DS_Store npm-debug.log dist/ +tsconfig.schema.json +tsconfig.schemastore-schema.json diff --git a/package-lock.json b/package-lock.json index 4f929410b..435d58f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,12 @@ "integrity": "sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==", "dev": true }, + "@types/json-schema": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", + "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "dev": true + }, "@types/mocha": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.0.0.tgz", @@ -118,6 +124,16 @@ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "dev": true, + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -177,6 +193,12 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", @@ -234,6 +256,65 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -527,6 +608,32 @@ } } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dev": true, + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -655,6 +762,12 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", @@ -788,6 +901,21 @@ } } }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -1673,6 +1801,35 @@ "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, + "typescript-json-schema": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.40.0.tgz", + "integrity": "sha512-C8D3Ca6+1x3caWOR+u45Shn3KqkRZi5M3+E8ePpEmYMqOh3xhhLdq+39pqT0Bf8+fCgAmpTFSJMT6Xwqbm0Tkw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "glob": "~7.1.4", + "json-stable-stringify": "^1.0.1", + "typescript": "^3.5.3", + "yargs": "^14.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "ua-parser-js": { "version": "0.7.17", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", @@ -1770,6 +1927,63 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, + "yargs": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz", + "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yargs-parser": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", diff --git a/package.json b/package.json index 4e3b2ae05..cbfa54dea 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,16 @@ "files": [ "dist/", "register/", - "LICENSE" + "LICENSE", + "tsconfig.schema.json", + "tsconfig.schemastore-schema.json" ], "scripts": { "lint": "tslint \"src/**/*.ts\" --project tsconfig.json", - "build": "rimraf dist && tsc", + "clean": "rimraf dist && rimraf tsconfig.schema.json && rimraf tsconfig.schemastore-schema.json", + "build": "npm run clean && npm run build:tsc && npm run build:configSchema", + "build:tsc": "tsc", + "build:configSchema": "typescript-json-schema --topRef --refs --validationKeywords allOf --out tsconfig.schema.json tsconfig.json TsConfigSchema && node --require ./register ./scripts/create-merged-schema", "test-spec": "mocha dist/**/*.spec.js -R spec --bail", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- \"dist/**/*.spec.js\" -R spec --bail", "test": "npm run build && npm run lint && npm run test-cov", @@ -55,6 +60,7 @@ "@types/react": "^16.0.2", "@types/semver": "^6.0.0", "@types/source-map-support": "^0.5.0", + "axios": "^0.19.0", "chai": "^4.0.1", "istanbul": "^0.4.0", "mocha": "^6.1.4", @@ -65,7 +71,8 @@ "semver": "^6.1.0", "tslint": "^5.11.0", "tslint-config-standard": "^9.0.0", - "typescript": "^3.7.2" + "typescript": "^3.7.2", + "typescript-json-schema": "0.40.0" }, "peerDependencies": { "typescript": ">=2.7" diff --git a/scripts/create-merged-schema.ts b/scripts/create-merged-schema.ts new file mode 100755 index 000000000..b516944b8 --- /dev/null +++ b/scripts/create-merged-schema.ts @@ -0,0 +1,58 @@ +#!/usr/bin/env ts-node +/* + * Create a complete JSON schema for tsconfig.json + * by merging the schemastore schema with our ts-node additions. + * This merged schema can be submitted in a pull request to + * SchemaStore. + */ + +import axios from 'axios'; +import * as Path from 'path'; +import * as fs from 'fs'; + +async function main() { + /** schemastore definition */ + const schemastoreSchema = (await axios.get( + 'https://schemastore.azurewebsites.net/schemas/json/tsconfig.json', + { responseType: "json" } + )).data; + + /** ts-node schema auto-generated from ts-node source code */ + const typescriptNodeSchema = require('../tsconfig.schema.json'); + + /** Patch ts-node stuff into the schemastore definition. */ + const mergedSchema = { + ...schemastoreSchema, + definitions: { + ...schemastoreSchema.definitions, + tsNodeDefinition: { + properties: { + 'ts-node': { + ...typescriptNodeSchema.definitions.TsConfigOptions, + description: typescriptNodeSchema.definitions.TsConfigSchema.properties['ts-node'].description, + properties: { + ...typescriptNodeSchema.definitions.TsConfigOptions.properties, + compilerOptions: { + ...typescriptNodeSchema.definitions.TsConfigOptions.properties.compilerOptions, + allOf: [{ + $ref: '#/definitions/compilerOptionsDefinition/properties/compilerOptions' + }] + } + } + } + } + }, + }, + allOf: [ + ...schemastoreSchema.allOf.slice(0, 4), + { "$ref": "#/definitions/tsNodeDefinition" }, + ...schemastoreSchema.allOf.slice(4), + ] + }; + fs.writeFileSync( + Path.resolve(__dirname, '../tsconfig.schemastore-schema.json'), + JSON.stringify(mergedSchema, null, 2) + ); +} + +main(); diff --git a/src/bin.ts b/src/bin.ts index 6a78a2e69..654c9535e 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -9,7 +9,7 @@ import { diffLines } from 'diff' import { Script } from 'vm' import { readFileSync, statSync } from 'fs' import { homedir } from 'os' -import { register, VERSION, DEFAULTS, TSError, parse, Register } from './index' +import { registerInternal, VERSION, DEFAULTS, TSError, parse, Register } from './index' /** * Eval filename for REPL/debug. @@ -149,42 +149,45 @@ export function main (argv: string[]) { const state = new EvalState(scriptPath || join(cwd, EVAL_FILENAME)) // Register the TypeScript compiler instance. - const service = register({ - dir: getCwd(dir, scriptMode, scriptPath), - emit, - files, - pretty, - transpileOnly, - ignore, - preferTsExts, - logError, - project, - skipProject, - skipIgnore, - compiler, - ignoreDiagnostics, - compilerOptions, - readFile: code !== undefined - ? (path: string) => { - if (path === state.path) return state.input - - try { - return readFileSync(path, 'utf8') - } catch (err) {/* Ignore. */} - } - : undefined, - fileExists: code !== undefined - ? (path: string) => { - if (path === state.path) return true - - try { - const stats = statSync(path) - return stats.isFile() || stats.isFIFO() - } catch (err) { - return false + const service = registerInternal({ + explicitOpts: { + dir: getCwd(dir, scriptMode, scriptPath), + emit, + files, + pretty, + transpileOnly, + ignore, + preferTsExts, + logError, + project, + skipProject, + skipIgnore, + compiler, + ignoreDiagnostics, + compilerOptions, + readFile: code !== undefined + ? (path: string) => { + if (path === state.path) return state.input + + try { + return readFileSync(path, 'utf8') + } catch (err) {/* Ignore. */} } - } - : undefined + : undefined, + fileExists: code !== undefined + ? (path: string) => { + if (path === state.path) return true + + try { + const stats = statSync(path) + return stats.isFile() || stats.isFIFO() + } catch (err) { + return false + } + } + : undefined + }, + defaultOpts: {} }) // Output project information. diff --git a/src/index.ts b/src/index.ts index e12653371..5196addc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,20 +72,79 @@ export const VERSION = require('../package.json').version * Options for creating a new TypeScript compiler instance. */ export interface CreateOptions { + /** + * Specify working directory for config resolution + * @default process.cwd() + */ dir?: string + /** + * Emit output files into `.ts-node` directory + * @default false + */ emit?: boolean | null + /** + * Scope compiler to files within `cwd` + * @default false + */ scope?: boolean | null + /** + * Use pretty diagnostic formatter + * @default false + */ pretty?: boolean | null + /** + * Use TypeScript's faster `transpileModule` + * @default false + */ transpileOnly?: boolean | null + /** + * Logs TypeScript errors to stderr instead of throwing exceptions + * @default false + */ logError?: boolean | null + /** + * Load files from `tsconfig.json` on startup + * @default false + */ files?: boolean | null + /** + * Specify a custom TypeScript compiler + * @default "typescript" + */ compiler?: string + /** + * Override the path patterns to skip compilation + * @default /node_modules/ + * @docsDefault "/node_modules/" + */ ignore?: string[] + /** + * Path to TypeScript JSON project file + */ project?: string + /** + * Skip project config resolution and loading + * @default false + */ skipProject?: boolean | null + /** + * Skip ignore check + * @default false + */ skipIgnore?: boolean | null + /** + * Re-order file extensions so that TypeScript imports are preferred + * @default false + */ preferTsExts?: boolean | null + /** + * JSON object to merge with compiler options + * @allOf [{"$ref": "https://schemastore.azurewebsites.net/schemas/json/tsconfig.json#definitions/compilerOptionsDefinition/properties/compilerOptions"}] + */ compilerOptions?: object + /** + * Ignore TypeScript warnings by diagnostic code + */ ignoreDiagnostics?: Array readFile?: (path: string) => string | undefined fileExists?: (path: string) => boolean @@ -99,6 +158,34 @@ export interface RegisterOptions extends CreateOptions { preferTsExts?: boolean | null } +/* + * This interface exists solely for generating a JSON schema for tsconfig.json. + * We do *not* extend the compiler's tsconfig interface. Instead we handle that + * on a schema level, via "allOf", so we pull in the same schema that VSCode + * already uses. + */ +/** + * tsconfig schema which includes "ts-node" options. + * @allOf [{"$ref": "https://schemastore.azurewebsites.net/schemas/json/tsconfig.json"}] + */ +export interface TsConfigSchema { + /** + * ts-node options. See also: https://github.com/TypeStrong/ts-node#configuration-options + * + * ts-node offers TypeScript execution and REPL for node.js, with source map support. + */ + 'ts-node': TsConfigOptions; +} +export interface TsConfigOptions + extends Omit {} + /** * Track the project information. */ @@ -117,7 +204,8 @@ export interface TypeInfo { } /** - * Default register options. + * Default register options, including values specified via environment + * variables. */ export const DEFAULTS: RegisterOptions = { dir: process.env.TS_NODE_DIR, @@ -219,9 +307,27 @@ function cachedLookup (fn: (arg: string) => T): (arg: string) => T { * Register TypeScript compiler instance onto node.js */ export function register (opts: RegisterOptions = {}): Register { - const options = { ...DEFAULTS, ...opts } + return registerInternal({ + defaultOpts: DEFAULTS, + explicitOpts: opts + }) +} + +/** + * Implementation of `register()` which allows passing explicit options and + * default options separately, to allow more advanced config merging behavior. + * @internal + */ +export function registerInternal (args: { + defaultOpts: RegisterOptions, + explicitOpts: RegisterOptions +}): Register { + const {defaultOpts, explicitOpts} = args const originalJsHandler = require.extensions['.js'] // tslint:disable-line - const service = create(options) + const {register: service, options} = createInternal({ + defaultOptions: defaultOpts, + explicitOptions: explicitOpts + }) const extensions = ['.ts'] // Enable additional extensions when JSX or `allowJs` is enabled. @@ -242,6 +348,17 @@ export function register (opts: RegisterOptions = {}): Register { * Create TypeScript compiler instance. */ export function create (options: CreateOptions = {}): Register { + return createInternal({explicitOptions: options, defaultOptions: {}}).register; +} + +function createInternal(args: { + /** Explicitly set options, via --flags or passed to the API */ + explicitOptions: CreateOptions + /** Default options, including those pulled from environment variables */ + defaultOptions: CreateOptions +}): {register: Register, options: RegisterOptions} { + const {explicitOptions, defaultOptions} = args + let options: RegisterOptions = {...defaultOptions, ...explicitOptions} const ignoreDiagnostics = [ 6059, // "'rootDir' is expected to contain all source files." 18002, // "The 'files' list in config file is empty." @@ -249,18 +366,38 @@ export function create (options: CreateOptions = {}): Register { ...(options.ignoreDiagnostics || []) ].map(Number) - const ignore = options.skipIgnore ? [] : (options.ignore || ['/node_modules/']).map(str => new RegExp(str)) - // Require the TypeScript compiler and configuration. - const cwd = options.dir ? resolve(options.dir) : process.cwd() - const isScoped = options.scope ? (fileName: string) => relative(cwd, fileName).charAt(0) !== '.' : () => true + + /** + * Compute options that must be computed before *and* after loading tsconfig + * They are required to successfully parse tsconfig, but might be changed by + * ts-node options specified in the config file. + */ + function recomputedOptions() { + const cwd = options.dir ? resolve(options.dir) : process.cwd() + const isScoped = options.scope ? (fileName: string) => relative(cwd, fileName).charAt(0) !== '.' : () => true + const compiler = require.resolve(options.compiler || 'typescript', { paths: [cwd, __dirname] }) + const ts: typeof _ts = require(compiler) + const readFile = options.readFile || ts.sys.readFile + const fileExists = options.fileExists || ts.sys.fileExists + return {cwd, isScoped, compiler, ts, readFile, fileExists} + } + + // compute enough options to read the config file + let {cwd, isScoped, compiler, ts, fileExists, readFile} = recomputedOptions() + + // Read config file + const {config, options: optionsFromTsconfig} = readConfig(cwd, ts, fileExists, readFile, options) + + // Merge default options, tsconfig options, and explicit --flag options + options = {...defaultOptions, ...optionsFromTsconfig, ...explicitOptions} + + // Re-compute based on options from tsconfig + ;({cwd, isScoped, compiler, ts, readFile, fileExists} = recomputedOptions()) + + const ignore = options.skipIgnore ? [] : (options.ignore || ['/node_modules/']).map(str => new RegExp(str)) const transpileOnly = options.transpileOnly === true - const compiler = require.resolve(options.compiler || 'typescript', { paths: [cwd, __dirname] }) - const ts: typeof _ts = require(compiler) const transformers = options.transformers || undefined - const readFile = options.readFile || ts.sys.readFile - const fileExists = options.fileExists || ts.sys.fileExists - const config = readConfig(cwd, ts, fileExists, readFile, options) const configDiagnosticList = filterDiagnostics(config.errors, ignoreDiagnostics) const outputCache = new Map() @@ -512,7 +649,10 @@ export function create (options: CreateOptions = {}): Register { const enabled = (enabled?: boolean) => enabled === undefined ? active : (active = !!enabled) const ignored = (fileName: string) => !active || !isScoped(fileName) || shouldIgnore(fileName, ignore) - return { ts, config, compile, getTypeInfo, ignored, enabled } + return { + register: { ts, config, compile, getTypeInfo, ignored, enabled }, + options + } } /** @@ -608,7 +748,8 @@ function fixConfig (ts: TSCommon, config: _ts.ParsedCommandLine) { } /** - * Load TypeScript configuration. + * Load TypeScript configuration. Returns both a parsed typescript config and + * any ts-node options specified in the config file. */ function readConfig ( cwd: string, @@ -616,7 +757,12 @@ function readConfig ( fileExists: (path: string) => boolean, readFile: (path: string) => string | undefined, options: CreateOptions -): _ts.ParsedCommandLine { +): { + /** Parsed TypeScript configuration */ + config: _ts.ParsedCommandLine + /** ts-node options pulled from tsconfig */ + options?: TsConfigOptions +} { let config: any = { compilerOptions: {} } let basePath = normalizeSlashes(cwd) let configFileName: string | undefined = undefined @@ -632,7 +778,7 @@ function readConfig ( // Return diagnostics. if (result.error) { - return { errors: [result.error], fileNames: [], options: {} } + return {config: { errors: [result.error], fileNames: [], options: {} }} } config = result.config @@ -647,9 +793,16 @@ function readConfig ( } // Override default configuration options `ts-node` requires. - config.compilerOptions = Object.assign({}, config.compilerOptions, options.compilerOptions, TS_NODE_COMPILER_OPTIONS) - - return fixConfig(ts, ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName)) + config.compilerOptions = Object.assign( + {}, + config.compilerOptions, + config['ts-node'].compilerOptions, + options.compilerOptions, + TS_NODE_COMPILER_OPTIONS + ) + + const fixedConfig = fixConfig(ts, ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName)) + return {config: fixedConfig, options: config['ts-node']} } /** diff --git a/tsconfig.json b/tsconfig.json index 486a70bae..dff9dd69a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,6 @@ { + "$schema": "./tsconfig.schemastore-schema.json", + "ts-node": {}, "compilerOptions": { "target": "es2015", "lib": ["es2015"],