From 7e301ef6a1a64d6e4d9c2d25c21d3856bd483279 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 29 Nov 2019 16:31:47 -0500 Subject: [PATCH 01/27] WIP --- .gitignore | 2 + package-lock.json | 214 ++++++++++++++++++++++++++++++++ package.json | 13 +- scripts/create-merged-schema.ts | 58 +++++++++ src/bin.ts | 75 +++++------ src/index.ts | 191 +++++++++++++++++++++++++--- tsconfig.json | 2 + 7 files changed, 497 insertions(+), 58 deletions(-) create mode 100755 scripts/create-merged-schema.ts 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..f78d01dbf 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"], From 73a76f3d8a39ac3f5c9ccbbb904d87093028a6c7 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 4 Dec 2019 18:06:33 -0500 Subject: [PATCH 02/27] WIP --- src/bin.ts | 114 ++++++++++++++++++++++++++++++++------------------ src/index.ts | 110 +++++++++++++++++++++++++++++++++++------------- tsconfig.json | 3 +- 3 files changed, 155 insertions(+), 72 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 654c9535e..362f93f1c 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 { registerInternal, VERSION, DEFAULTS, TSError, parse, Register } from './index' +import { registerInternal, VERSION, DEFAULTS, TSError, parse, Register, OptionsHelper, RegisterOptions } from './index' /** * Eval filename for REPL/debug. @@ -79,29 +79,71 @@ export function main (argv: string[]) { stopAtPositional: true }) - const { - '--dir': dir = DEFAULTS.dir, - '--help': help = false, - '--script-mode': scriptMode = false, - '--version': version = 0, - '--require': requires = [], - '--eval': code = undefined, - '--print': print = false, - '--interactive': interactive = false, - '--files': files = DEFAULTS.files, - '--compiler': compiler = DEFAULTS.compiler, - '--compiler-options': compilerOptions = DEFAULTS.compilerOptions, - '--project': project = DEFAULTS.project, - '--ignore-diagnostics': ignoreDiagnostics = DEFAULTS.ignoreDiagnostics, - '--ignore': ignore = DEFAULTS.ignore, - '--transpile-only': transpileOnly = DEFAULTS.transpileOnly, - '--pretty': pretty = DEFAULTS.pretty, - '--skip-project': skipProject = DEFAULTS.skipProject, - '--skip-ignore': skipIgnore = DEFAULTS.skipIgnore, - '--prefer-ts-exts': preferTsExts = DEFAULTS.preferTsExts, - '--log-error': logError = DEFAULTS.logError, - '--emit': emit = DEFAULTS.emit - } = args + const flagOptions = { + dir: args['--dir'], + help: args['--help'], + scriptMode: args['--script-mode'], + version: args['--version'], + requires: args['--require'], + code: args['--eval'], + print: args['--print'], + interactive: args['--interactive'], + files: args['--files'], + compiler: args['--compiler'], + compilerOptions: args['--compiler-options'], + project: args['--project'], + ignoreDiagnostics: args['--ignore-diagnostics'], + ignore: args['--ignore'], + transpileOnly: args['--transpile-only'], + pretty: args['--pretty'], + skipProject: args['--skip-project'], + skipIgnore: args['--skip-ignore'], + preferTsExts: args['--prefer-ts-exts'], + logError: args['--log-error'], + emit: args['--emit'] + } + const defaultOptions = { + ...DEFAULTS, + help: false, + scriptMode: false, + version: 0, + requires: [], + code: undefined, + print: false, + interactive: false + } + let options = (() => { + const { + dir = defaultOptions.dir, + help = defaultOptions.help, + scriptMode = defaultOptions.scriptMode, + version = defaultOptions.version, + requires = defaultOptions.requires, + code = defaultOptions.code, + print = defaultOptions.print, + interactive = defaultOptions.interactive, + files = defaultOptions.files, + compiler = defaultOptions.compiler, + compilerOptions = defaultOptions.compilerOptions, + project = defaultOptions.project, + ignoreDiagnostics = defaultOptions.ignoreDiagnostics, + ignore = defaultOptions.ignore, + transpileOnly = defaultOptions.transpileOnly, + pretty = defaultOptions.pretty, + skipProject = defaultOptions.skipProject, + skipIgnore = defaultOptions.skipIgnore, + preferTsExts = defaultOptions.preferTsExts, + logError = defaultOptions.logError, + emit = defaultOptions.emit, + } = flagOptions + return { + dir, help, scriptMode, version, requires, code, print, interactive, + files, compiler, compilerOptions, project, ignoreDiagnostics, ignore, + transpileOnly, pretty, skipProject, skipIgnore, preferTsExts, logError, + emit + } + })() + const {help, version, dir, scriptMode, code, interactive, print} = options if (help) { console.log(` @@ -149,22 +191,10 @@ export function main (argv: string[]) { const state = new EvalState(scriptPath || join(cwd, EVAL_FILENAME)) // Register the TypeScript compiler instance. - const service = registerInternal({ - explicitOpts: { + const service = registerInternal(new OptionsHelper({ + explicitOptions: { + ...flagOptions, 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 @@ -187,8 +217,10 @@ export function main (argv: string[]) { } : undefined }, - defaultOpts: {} - }) + defaultOptions + })) + + const {requires = []} = service.options // Output project information. if (version >= 2) { diff --git a/src/index.ts b/src/index.ts index f78d01dbf..306ab519b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -176,6 +176,7 @@ export interface TsConfigSchema { */ 'ts-node': TsConfigOptions } + export interface TsConfigOptions extends Omit {} + > { + requires?: Array + } + +/** + * Helper for obtaining ts-node options from multiple sources and merging them. + * @internal + */ +export class OptionsHelper { + constructor(fields: Partial) { + Object.assign(this, fields) + } + + /** + * Options specified explicitly via --flags or options object passed to ts-node's public API. + * They should override everything else. + */ + public explicitOptions: RegisterOptions = {} + + /** + * Default options that are overridden by all other sources. Env vars fall into this category. + */ + public defaultOptions: RegisterOptions = DEFAULTS + + /** Options pulled from tsconfig.json */ + public tsconfigOptions: RegisterOptions = {} + + merge(): RegisterOptions { + return defaults(this.defaultOptions, this.tsconfigOptions, this.explicitOptions); + } +} + +/** + * Like Object.assign or splatting, but never overwrites with `undefined`. + * This matches the behavior for argument and destructuring defaults. + * @internal + */ +export function defaults(...sources: Array): T { + const merged: any = {} + for(const source of sources) { + for(const [key, value] of Object.entries(source)) { + if(value !== undefined) merged[key] = value + } + } + return merged +} /** * Track the project information. @@ -282,6 +328,7 @@ export class TSError extends BaseError { export interface Register { ts: TSCommon config: _ts.ParsedCommandLine + options: RegisterOptions & TsConfigOptions enabled (enabled?: boolean): boolean ignored (fileName: string): boolean compile (code: string, fileName: string, lineOffset?: number): string @@ -307,10 +354,10 @@ function cachedLookup (fn: (arg: string) => T): (arg: string) => T { * Register TypeScript compiler instance onto node.js */ export function register (opts: RegisterOptions = {}): Register { - return registerInternal({ - defaultOpts: DEFAULTS, - explicitOpts: opts - }) + return registerInternal(new OptionsHelper({ + defaultOptions: DEFAULTS, + explicitOptions: opts + })) } /** @@ -318,16 +365,9 @@ export function register (opts: RegisterOptions = {}): Register { * default options separately, to allow more advanced config merging behavior. * @internal */ -export function registerInternal (args: { - defaultOpts: RegisterOptions, - explicitOpts: RegisterOptions -}): Register { - const { defaultOpts, explicitOpts } = args +export function registerInternal (optionsHelper: OptionsHelper): Register { const originalJsHandler = require.extensions['.js'] // tslint:disable-line - const { register: service, options } = createInternal({ - defaultOptions: defaultOpts, - explicitOptions: explicitOpts - }) + const service = createInternal(optionsHelper) const extensions = ['.ts'] // Enable additional extensions when JSX or `allowJs` is enabled. @@ -339,7 +379,7 @@ export function registerInternal (args: { process[REGISTER_INSTANCE] = service // Register the extensions. - registerExtensions(options.preferTsExts, extensions, service, originalJsHandler) + registerExtensions(service.options.preferTsExts, extensions, service, originalJsHandler) return service } @@ -348,17 +388,13 @@ export function registerInternal (args: { * Create TypeScript compiler instance. */ export function create (options: CreateOptions = {}): Register { - return createInternal({ explicitOptions: options, defaultOptions: {} }).register + return createInternal(new OptionsHelper({ + explicitOptions: options + })) } -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 } +function createInternal (optionsHelper: OptionsHelper): Register { + let options = optionsHelper.merge() const ignoreDiagnostics = [ 6059, // "'rootDir' is expected to contain all source files." 18002, // "The 'files' list in config file is empty." @@ -387,10 +423,12 @@ function createInternal (args: { let { cwd, isScoped, compiler, ts, fileExists, readFile } = recomputedOptions() // Read config file - const { config, options: optionsFromTsconfig } = readConfig(cwd, ts, fileExists, readFile, options) + const { config, options: tsconfigOptions } = readConfig(cwd, ts, fileExists, readFile, options) + if(tsconfigOptions) + optionsHelper.tsconfigOptions = tsconfigOptions // Merge default options, tsconfig options, and explicit --flag options - options = { ...defaultOptions, ...optionsFromTsconfig, ...explicitOptions } + options = optionsHelper.merge() // Re-compute based on options from tsconfig ;({ cwd, isScoped, compiler, ts, readFile, fileExists } = recomputedOptions()) @@ -650,8 +688,7 @@ function createInternal (args: { const ignored = (fileName: string) => !active || !isScoped(fileName) || shouldIgnore(fileName, ignore) return { - register: { ts, config, compile, getTypeInfo, ignored, enabled }, - options + ts, config, compile, getTypeInfo, ignored, enabled, options } } @@ -796,13 +833,26 @@ function readConfig ( config.compilerOptions = Object.assign( {}, config.compilerOptions, - config['ts-node'].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'] } + + // Fix ts-node options that come from tsconfig.json + const tsconfigOptions: TsConfigOptions = { + ...config['ts-node'] + } + if(tsconfigOptions.requires) { + // Relative paths are relative to the tsconfig's parent directory, not the `dir` option + tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { + if(path.startsWith('.')) return resolve(configFileName!, '..', path) + return path + }) + } + + return { config: fixedConfig, options: tsconfigOptions } } /** diff --git a/tsconfig.json b/tsconfig.json index dff9dd69a..5e83c83ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "declaration": true, "sourceMap": true, "inlineSources": true, - "types": ["node", "mocha"] + "types": ["node", "mocha"], + "stripInternal": true }, "include": [ "src/**/*" From 91ca89214745ed5b092f1148b1b62fe8ec6105a4 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 4 Dec 2019 19:08:24 -0500 Subject: [PATCH 03/27] WIP tests --- src/bin.ts | 6 +++--- src/index.spec.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 25 +++++++++++++------------ 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 362f93f1c..3c0683b7c 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -134,7 +134,7 @@ export function main (argv: string[]) { skipIgnore = defaultOptions.skipIgnore, preferTsExts = defaultOptions.preferTsExts, logError = defaultOptions.logError, - emit = defaultOptions.emit, + emit = defaultOptions.emit } = flagOptions return { dir, help, scriptMode, version, requires, code, print, interactive, @@ -143,7 +143,7 @@ export function main (argv: string[]) { emit } })() - const {help, version, dir, scriptMode, code, interactive, print} = options + const { help, version, dir, scriptMode, code, interactive, print } = options if (help) { console.log(` @@ -220,7 +220,7 @@ export function main (argv: string[]) { defaultOptions })) - const {requires = []} = service.options + const { requires = [] } = service.options // Output project information. if (version >= 2) { diff --git a/src/index.spec.ts b/src/index.spec.ts index c1402be7e..47a8edfb9 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -346,6 +346,52 @@ describe('ts-node', function () { }) }) } + + describe('should read ts-node options from tsconfig.json', function () { + const BIN_EXEC = `node "${join(__dirname, '../dist/bin')}" --project tests/tsconfig-options/tsconfig.json` + it('options pulled from tsconfig.json', function (done) { + exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, function (err, stdout) { + expect(err).to.equal(null) + const { options, config } = JSON.parse(stdout) + expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) + expect(config.options.sourceRoot).to.equal('tsconfig-tsnode-sourceRoot') + expect(options.pretty).to.equal(null) + expect(options.transpileOnly).to.equal(true) + expect(options.requires).to.deep.equal(['./required']) + return done() + }) + }) + it('flags override tsconfig', function (done) { + exec(`${BIN_EXEC} --transpile-only=false --compiler-options '{"sourceRoot": "flags-sourceRoot"}' tests/tsconfig-options/log-options.js`, function (err, stdout) { + expect(err).to.equal(null) + const { options, config } = JSON.parse(stdout) + expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) + expect(config.options.sourceRoot).to.equal('flags-sourceRoot') + expect(options.pretty).to.equal(null) + expect(options.transpileOnly).to.equal(false) + expect(options.requires).to.deep.equal(['./required']) + return done() + }) + }) + it('tsconfig overrides env vars', function (done) { + exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, { + env: { + ...process.env, + TS_NODE_PRETTY: 'true', + TS_NODE_TRANSPILE_ONLY: 'false' + } + }, function (err, stdout) { + expect(err).to.equal(null) + const { options, config } = JSON.parse(stdout) + expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) + expect(config.options.sourceRoot).to.equal('tsconfig-tsnode-sourceRoot') + expect(options.pretty).to.equal(true) + expect(options.transpileOnly).to.equal(true) + expect(options.requires).to.deep.equal(['./required']) + return done() + }) + }) + }) }) describe('register', function () { diff --git a/src/index.ts b/src/index.ts index 306ab519b..5af43325c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -186,15 +186,15 @@ export interface TsConfigOptions | 'project' | 'dir' > { - requires?: Array - } + requires?: Array +} /** * Helper for obtaining ts-node options from multiple sources and merging them. * @internal */ export class OptionsHelper { - constructor(fields: Partial) { + constructor (fields: Partial) { Object.assign(this, fields) } @@ -212,8 +212,8 @@ export class OptionsHelper { /** Options pulled from tsconfig.json */ public tsconfigOptions: RegisterOptions = {} - merge(): RegisterOptions { - return defaults(this.defaultOptions, this.tsconfigOptions, this.explicitOptions); + merge (): RegisterOptions { + return defaults(this.defaultOptions, this.tsconfigOptions, this.explicitOptions) } } @@ -222,11 +222,11 @@ export class OptionsHelper { * This matches the behavior for argument and destructuring defaults. * @internal */ -export function defaults(...sources: Array): T { +export function defaults (...sources: Array): T { const merged: any = {} - for(const source of sources) { - for(const [key, value] of Object.entries(source)) { - if(value !== undefined) merged[key] = value + for (const source of sources) { + for (const [key, value] of Object.entries(source)) { + if (value !== undefined) merged[key] = value } } return merged @@ -424,8 +424,9 @@ function createInternal (optionsHelper: OptionsHelper): Register { // Read config file const { config, options: tsconfigOptions } = readConfig(cwd, ts, fileExists, readFile, options) - if(tsconfigOptions) + if (tsconfigOptions) { optionsHelper.tsconfigOptions = tsconfigOptions + } // Merge default options, tsconfig options, and explicit --flag options options = optionsHelper.merge() @@ -844,10 +845,10 @@ function readConfig ( const tsconfigOptions: TsConfigOptions = { ...config['ts-node'] } - if(tsconfigOptions.requires) { + if (tsconfigOptions.requires) { // Relative paths are relative to the tsconfig's parent directory, not the `dir` option tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { - if(path.startsWith('.')) return resolve(configFileName!, '..', path) + if (path.startsWith('.')) return resolve(configFileName!, '..', path) return path }) } From e11be565e5afc43be88d16eb6c74c266a7bc9097 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 4 Dec 2019 19:15:15 -0500 Subject: [PATCH 04/27] WIP --- package.json | 1 + src/index.spec.ts | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cbfa54dea..0dbdb0792 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ ], "scripts": { "lint": "tslint \"src/**/*.ts\" --project tsconfig.json", + "lint:fix": "tslint \"src/**/*.ts\" --project tsconfig.json --fix", "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", diff --git a/src/index.spec.ts b/src/index.spec.ts index 47a8edfb9..f6bb19f2d 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -356,20 +356,22 @@ describe('ts-node', function () { expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) expect(config.options.sourceRoot).to.equal('tsconfig-tsnode-sourceRoot') expect(options.pretty).to.equal(null) + expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.requires).to.deep.equal(['./required']) + expect(options.requires).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) return done() }) }) it('flags override tsconfig', function (done) { - exec(`${BIN_EXEC} --transpile-only=false --compiler-options '{"sourceRoot": "flags-sourceRoot"}' tests/tsconfig-options/log-options.js`, function (err, stdout) { + exec(`${BIN_EXEC} --skip-ignore --compiler-options '{"sourceRoot": "flags-sourceRoot"}' tests/tsconfig-options/log-options.js`, function (err, stdout) { expect(err).to.equal(null) const { options, config } = JSON.parse(stdout) expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) expect(config.options.sourceRoot).to.equal('flags-sourceRoot') expect(options.pretty).to.equal(null) - expect(options.transpileOnly).to.equal(false) - expect(options.requires).to.deep.equal(['./required']) + expect(options.skipIgnore).to.equal(true) + expect(options.transpileOnly).to.equal(true) + expect(options.requires).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) return done() }) }) @@ -378,7 +380,7 @@ describe('ts-node', function () { env: { ...process.env, TS_NODE_PRETTY: 'true', - TS_NODE_TRANSPILE_ONLY: 'false' + TS_NODE_SKIP_IGNORE: 'true' } }, function (err, stdout) { expect(err).to.equal(null) @@ -386,8 +388,9 @@ describe('ts-node', function () { expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) expect(config.options.sourceRoot).to.equal('tsconfig-tsnode-sourceRoot') expect(options.pretty).to.equal(true) + expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.requires).to.deep.equal(['./required']) + expect(options.requires).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) return done() }) }) From 5b576261d13f815463546e78965860e54a5791a9 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 4 Dec 2019 19:19:52 -0500 Subject: [PATCH 05/27] fix --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 5af43325c..6ab9e07d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -225,7 +225,8 @@ export class OptionsHelper { export function defaults (...sources: Array): T { const merged: any = {} for (const source of sources) { - for (const [key, value] of Object.entries(source)) { + for (const key of Object.keys(source)) { + const value = (source as any)[key] if (value !== undefined) merged[key] = value } } From d14a02846fd1803765359418c4628252aeed2a97 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 4 Dec 2019 19:30:20 -0500 Subject: [PATCH 06/27] fix --- tests/tsconfig-options/log-options.js | 7 +++++++ tests/tsconfig-options/required.js | 1 + tests/tsconfig-options/tsconfig.json | 15 +++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/tsconfig-options/log-options.js create mode 100644 tests/tsconfig-options/required.js create mode 100644 tests/tsconfig-options/tsconfig.json diff --git a/tests/tsconfig-options/log-options.js b/tests/tsconfig-options/log-options.js new file mode 100644 index 000000000..dae2f96d4 --- /dev/null +++ b/tests/tsconfig-options/log-options.js @@ -0,0 +1,7 @@ +const assert = require('assert') +assert(process.required) +const register = process[Symbol.for('ts-node.register.instance')] +console.log(JSON.stringify({ + options: register.options, + config: register.config +})) diff --git a/tests/tsconfig-options/required.js b/tests/tsconfig-options/required.js new file mode 100644 index 000000000..e2c958cdf --- /dev/null +++ b/tests/tsconfig-options/required.js @@ -0,0 +1 @@ +process.required = true diff --git a/tests/tsconfig-options/tsconfig.json b/tests/tsconfig-options/tsconfig.json new file mode 100644 index 000000000..5b25d2578 --- /dev/null +++ b/tests/tsconfig-options/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "ts-node": { + "compilerOptions": { + "sourceRoot": "tsconfig-tsnode-sourceRoot" + }, + "transpileOnly": true, + "requires": ["./required"], + "skipIgnore": false + }, + "compilerOptions": { + "sourceRoot": "tsconfig-sourceRoot", + "typeRoots": ["tsconfig-typeroots"], + } +} From 973a78cf0512094a4050098a118965bc9be25c1b Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 4 Dec 2019 19:37:47 -0500 Subject: [PATCH 07/27] Fix --- src/index.spec.ts | 8 ++++---- tests/tsconfig-options/tsconfig.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index f6bb19f2d..9ca9e05c4 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -354,7 +354,7 @@ describe('ts-node', function () { expect(err).to.equal(null) const { options, config } = JSON.parse(stdout) expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) - expect(config.options.sourceRoot).to.equal('tsconfig-tsnode-sourceRoot') + expect(config.options.types).to.deep.equal(['tsconfig-tsnode-types']) expect(options.pretty).to.equal(null) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) @@ -363,11 +363,11 @@ describe('ts-node', function () { }) }) it('flags override tsconfig', function (done) { - exec(`${BIN_EXEC} --skip-ignore --compiler-options '{"sourceRoot": "flags-sourceRoot"}' tests/tsconfig-options/log-options.js`, function (err, stdout) { + exec(`${BIN_EXEC} --skip-ignore --compiler-options '{"types": ["flags-types"]}' tests/tsconfig-options/log-options.js`, function (err, stdout) { expect(err).to.equal(null) const { options, config } = JSON.parse(stdout) expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) - expect(config.options.sourceRoot).to.equal('flags-sourceRoot') + expect(config.options.types).to.deep.equal(['flags-types']) expect(options.pretty).to.equal(null) expect(options.skipIgnore).to.equal(true) expect(options.transpileOnly).to.equal(true) @@ -386,7 +386,7 @@ describe('ts-node', function () { expect(err).to.equal(null) const { options, config } = JSON.parse(stdout) expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots')]) - expect(config.options.sourceRoot).to.equal('tsconfig-tsnode-sourceRoot') + expect(config.options.types).to.deep.equal(['tsconfig-tsnode-types']) expect(options.pretty).to.equal(true) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) diff --git a/tests/tsconfig-options/tsconfig.json b/tests/tsconfig-options/tsconfig.json index 5b25d2578..901531e1d 100644 --- a/tests/tsconfig-options/tsconfig.json +++ b/tests/tsconfig-options/tsconfig.json @@ -2,14 +2,14 @@ "extends": "../tsconfig.json", "ts-node": { "compilerOptions": { - "sourceRoot": "tsconfig-tsnode-sourceRoot" + "types": ["tsconfig-tsnode-types"] }, "transpileOnly": true, "requires": ["./required"], "skipIgnore": false }, "compilerOptions": { - "sourceRoot": "tsconfig-sourceRoot", "typeRoots": ["tsconfig-typeroots"], + "types": ["tsconfig-types"] } } From 7dc0009b2977a3380329c56b26dd79cc61499aa2 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 5 Dec 2019 00:40:30 -0500 Subject: [PATCH 08/27] Addressing PR feedback. Not done yet; just pushing what I have so far --- package.json | 8 ++++---- scripts/create-merged-schema.ts | 4 ++-- src/index.ts | 32 +++++++------------------------- src/tsconfig-schema.ts | 20 ++++++++++++++++++++ tsconfig.json | 1 - 5 files changed, 33 insertions(+), 32 deletions(-) create mode 100644 src/tsconfig-schema.ts diff --git a/package.json b/package.json index 0dbdb0792..6625f89c2 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,11 @@ ], "scripts": { "lint": "tslint \"src/**/*.ts\" --project tsconfig.json", - "lint:fix": "tslint \"src/**/*.ts\" --project tsconfig.json --fix", + "lint-fix": "tslint \"src/**/*.ts\" --project tsconfig.json --fix", "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", + "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", diff --git a/scripts/create-merged-schema.ts b/scripts/create-merged-schema.ts index b516944b8..ba2c6f5ca 100755 --- a/scripts/create-merged-schema.ts +++ b/scripts/create-merged-schema.ts @@ -7,7 +7,7 @@ */ import axios from 'axios'; -import * as Path from 'path'; +import {resolve} from 'path'; import * as fs from 'fs'; async function main() { @@ -50,7 +50,7 @@ async function main() { ] }; fs.writeFileSync( - Path.resolve(__dirname, '../tsconfig.schemastore-schema.json'), + resolve(__dirname, '../tsconfig.schemastore-schema.json'), JSON.stringify(mergedSchema, null, 2) ); } diff --git a/src/index.ts b/src/index.ts index 6ab9e07d4..b8cd66ab3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -158,25 +158,6 @@ 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 relative(cwd, fileName).charAt(0) !== '.' : () => true + function loadCompiler () { 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 } + return { compiler, ts, readFile, fileExists } } // compute enough options to read the config file - let { cwd, isScoped, compiler, ts, fileExists, readFile } = recomputedOptions() + let { compiler, ts, fileExists, readFile } = loadCompiler() // Read config file const { config, options: tsconfigOptions } = readConfig(cwd, ts, fileExists, readFile, options) @@ -433,8 +414,9 @@ function createInternal (optionsHelper: OptionsHelper): Register { options = optionsHelper.merge() // Re-compute based on options from tsconfig - ;({ cwd, isScoped, compiler, ts, readFile, fileExists } = recomputedOptions()) + ;({ compiler, ts, readFile, fileExists } = loadCompiler()) + const isScoped = options.scope ? (fileName: string) => relative(cwd, fileName).charAt(0) !== '.' : () => true const ignore = options.skipIgnore ? [] : (options.ignore || ['/node_modules/']).map(str => new RegExp(str)) const transpileOnly = options.transpileOnly === true const transformers = options.transformers || undefined diff --git a/src/tsconfig-schema.ts b/src/tsconfig-schema.ts new file mode 100644 index 000000000..b569f9a5c --- /dev/null +++ b/src/tsconfig-schema.ts @@ -0,0 +1,20 @@ +import { TsConfigOptions } from "."; + +/* + * 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 +} diff --git a/tsconfig.json b/tsconfig.json index 5e83c83ac..465293630 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,5 @@ { "$schema": "./tsconfig.schemastore-schema.json", - "ts-node": {}, "compilerOptions": { "target": "es2015", "lib": ["es2015"], From 3fa74a5db958f92f24a282a576f3e8ccc40616a9 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 13 Dec 2019 22:09:28 -0500 Subject: [PATCH 09/27] Pushing fixes to view the gh diff --- package-lock.json | 6 +- package.json | 2 +- scripts/create-merged-schema.ts | 4 +- src/bin.ts | 157 +++++++++++++------------------- 4 files changed, 69 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index 435d58f50..df8b14935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2090,9 +2090,9 @@ } }, "yn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.0.0.tgz", - "integrity": "sha512-+Wo/p5VRfxUgBUGy2j/6KX2mj9AYJWOHuhMjMcbBFc3y54o9/4buK1ksBvuiK01C3kby8DH9lSmJdSxw+4G/2Q==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz", + "integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==" } } } diff --git a/package.json b/package.json index 6625f89c2..0f7b1a9cd 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,6 @@ "diff": "^4.0.1", "make-error": "^1.1.1", "source-map-support": "^0.5.6", - "yn": "^3.0.0" + "yn": "^4.0.0" } } diff --git a/scripts/create-merged-schema.ts b/scripts/create-merged-schema.ts index ba2c6f5ca..7241de5a0 100755 --- a/scripts/create-merged-schema.ts +++ b/scripts/create-merged-schema.ts @@ -8,7 +8,7 @@ import axios from 'axios'; import {resolve} from 'path'; -import * as fs from 'fs'; +import {writeFileSync} from 'fs'; async function main() { /** schemastore definition */ @@ -49,7 +49,7 @@ async function main() { ...schemastoreSchema.allOf.slice(4), ] }; - fs.writeFileSync( + writeFileSync( resolve(__dirname, '../tsconfig.schemastore-schema.json'), JSON.stringify(mergedSchema, null, 2) ); diff --git a/src/bin.ts b/src/bin.ts index 3c0683b7c..ab9caecd7 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 { registerInternal, VERSION, DEFAULTS, TSError, parse, Register, OptionsHelper, RegisterOptions } from './index' +import { registerInternal, VERSION, DEFAULTS, TSError, parse, Register, OptionsHelper, RegisterOptions, register } from './index' /** * Eval filename for REPL/debug. @@ -79,71 +79,31 @@ export function main (argv: string[]) { stopAtPositional: true }) - const flagOptions = { - dir: args['--dir'], - help: args['--help'], - scriptMode: args['--script-mode'], - version: args['--version'], - requires: args['--require'], - code: args['--eval'], - print: args['--print'], - interactive: args['--interactive'], - files: args['--files'], - compiler: args['--compiler'], - compilerOptions: args['--compiler-options'], - project: args['--project'], - ignoreDiagnostics: args['--ignore-diagnostics'], - ignore: args['--ignore'], - transpileOnly: args['--transpile-only'], - pretty: args['--pretty'], - skipProject: args['--skip-project'], - skipIgnore: args['--skip-ignore'], - preferTsExts: args['--prefer-ts-exts'], - logError: args['--log-error'], - emit: args['--emit'] - } - const defaultOptions = { - ...DEFAULTS, - help: false, - scriptMode: false, - version: 0, - requires: [], - code: undefined, - print: false, - interactive: false - } - let options = (() => { - const { - dir = defaultOptions.dir, - help = defaultOptions.help, - scriptMode = defaultOptions.scriptMode, - version = defaultOptions.version, - requires = defaultOptions.requires, - code = defaultOptions.code, - print = defaultOptions.print, - interactive = defaultOptions.interactive, - files = defaultOptions.files, - compiler = defaultOptions.compiler, - compilerOptions = defaultOptions.compilerOptions, - project = defaultOptions.project, - ignoreDiagnostics = defaultOptions.ignoreDiagnostics, - ignore = defaultOptions.ignore, - transpileOnly = defaultOptions.transpileOnly, - pretty = defaultOptions.pretty, - skipProject = defaultOptions.skipProject, - skipIgnore = defaultOptions.skipIgnore, - preferTsExts = defaultOptions.preferTsExts, - logError = defaultOptions.logError, - emit = defaultOptions.emit - } = flagOptions - return { - dir, help, scriptMode, version, requires, code, print, interactive, - files, compiler, compilerOptions, project, ignoreDiagnostics, ignore, - transpileOnly, pretty, skipProject, skipIgnore, preferTsExts, logError, - emit - } - })() - const { help, version, dir, scriptMode, code, interactive, print } = options + // Only setting defaults for CLI-specific flags + // Anything on RegisterOptions can be passed as `undefined` to `register()` + const { + '--dir': dir, + '--help': help = false, + '--script-mode': scriptMode = false, + '--version': version = 0, + '--require': argsRequire = [], + '--eval': code = undefined, + '--print': print = false, + '--interactive': interactive = false, + '--files': files, + '--compiler': compiler, + '--compiler-options': compilerOptions, + '--project': project, + '--ignore-diagnostics': ignoreDiagnostics, + '--ignore': ignore, + '--transpile-only': transpileOnly, + '--pretty': pretty, + '--skip-project': skipProject, + '--skip-ignore': skipIgnore, + '--prefer-ts-exts': preferTsExts, + '--log-error': logError, + '--emit': emit + } = args if (help) { console.log(` @@ -191,36 +151,45 @@ export function main (argv: string[]) { const state = new EvalState(scriptPath || join(cwd, EVAL_FILENAME)) // Register the TypeScript compiler instance. - const service = registerInternal(new OptionsHelper({ - explicitOptions: { - ...flagOptions, - dir: getCwd(dir, scriptMode, scriptPath), - 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 = 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 } - : undefined - }, - defaultOptions - })) + } + : undefined + }) - const { requires = [] } = service.options + const requires = argsRequire !== undefined ? argsRequire : service.options.requires || [] // Output project information. if (version >= 2) { From 90e059dfaebbd1ca101886ff2be8b80abf29b970 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 13 Dec 2019 22:33:04 -0500 Subject: [PATCH 10/27] Pushing fixes to view the gh diff --- src/bin.ts | 2 +- src/index.ts | 77 +++++++++++----------------------------------------- 2 files changed, 17 insertions(+), 62 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index ab9caecd7..6e88df75e 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 { registerInternal, VERSION, DEFAULTS, TSError, parse, Register, OptionsHelper, RegisterOptions, register } from './index' +import { VERSION, DEFAULTS, TSError, parse, Register, RegisterOptions, register } from './index' /** * Eval filename for REPL/debug. diff --git a/src/index.ts b/src/index.ts index b8cd66ab3..ffb3f17fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -170,34 +170,6 @@ export interface TsConfigOptions requires?: Array } -/** - * Helper for obtaining ts-node options from multiple sources and merging them. - * @internal - */ -export class OptionsHelper { - constructor (fields: Partial) { - Object.assign(this, fields) - } - - /** - * Options specified explicitly via --flags or options object passed to ts-node's public API. - * They should override everything else. - */ - public explicitOptions: RegisterOptions = {} - - /** - * Default options that are overridden by all other sources. Env vars fall into this category. - */ - public defaultOptions: RegisterOptions = DEFAULTS - - /** Options pulled from tsconfig.json */ - public tsconfigOptions: RegisterOptions = {} - - merge (): RegisterOptions { - return defaults(this.defaultOptions, this.tsconfigOptions, this.explicitOptions) - } -} - /** * Like Object.assign or splatting, but never overwrites with `undefined`. * This matches the behavior for argument and destructuring defaults. @@ -336,20 +308,8 @@ function cachedLookup (fn: (arg: string) => T): (arg: string) => T { * Register TypeScript compiler instance onto node.js */ export function register (opts: RegisterOptions = {}): Register { - return registerInternal(new OptionsHelper({ - defaultOptions: DEFAULTS, - explicitOptions: opts - })) -} - -/** - * Implementation of `register()` which allows passing explicit options and - * default options separately, to allow more advanced config merging behavior. - * @internal - */ -export function registerInternal (optionsHelper: OptionsHelper): Register { const originalJsHandler = require.extensions['.js'] // tslint:disable-line - const service = createInternal(optionsHelper) + const service = create(opts) const extensions = ['.ts'] // Enable additional extensions when JSX or `allowJs` is enabled. @@ -370,23 +330,13 @@ export function registerInternal (optionsHelper: OptionsHelper): Register { * Create TypeScript compiler instance. */ export function create (options: CreateOptions = {}): Register { - return createInternal(new OptionsHelper({ - explicitOptions: options - })) -} - -function createInternal (optionsHelper: OptionsHelper): Register { - let options = optionsHelper.merge() - const ignoreDiagnostics = [ - 6059, // "'rootDir' is expected to contain all source files." - 18002, // "The 'files' list in config file is empty." - 18003, // "No inputs were found in config file." - ...(options.ignoreDiagnostics || []) - ].map(Number) + const optionsWithoutDefaults = options + options = defaults(options, DEFAULTS) // Require the TypeScript compiler and configuration. const cwd = options.dir ? resolve(options.dir) : process.cwd() + const compilerBefore = options.compiler /** * Compute options that must be computed before *and* after loading tsconfig @@ -406,20 +356,25 @@ function createInternal (optionsHelper: OptionsHelper): Register { // Read config file const { config, options: tsconfigOptions } = readConfig(cwd, ts, fileExists, readFile, options) - if (tsconfigOptions) { - optionsHelper.tsconfigOptions = tsconfigOptions - } - // Merge default options, tsconfig options, and explicit --flag options - options = optionsHelper.merge() + // Merge default options, tsconfig options, and explicit options + options = defaults(optionsWithoutDefaults, tsconfigOptions || {}, DEFAULTS) - // Re-compute based on options from tsconfig - ;({ compiler, ts, readFile, fileExists } = loadCompiler()) + // If `compiler` option changed based on tsconfig, re-load the compiler + if(options.compiler !== compilerBefore) { + ({ compiler, ts, readFile, fileExists } = loadCompiler()) + } const isScoped = options.scope ? (fileName: string) => relative(cwd, fileName).charAt(0) !== '.' : () => true const ignore = options.skipIgnore ? [] : (options.ignore || ['/node_modules/']).map(str => new RegExp(str)) const transpileOnly = options.transpileOnly === true const transformers = options.transformers || undefined + const ignoreDiagnostics = [ + 6059, // "'rootDir' is expected to contain all source files." + 18002, // "The 'files' list in config file is empty." + 18003, // "No inputs were found in config file." + ...(options.ignoreDiagnostics || []) + ].map(Number) const configDiagnosticList = filterDiagnostics(config.errors, ignoreDiagnostics) const outputCache = new Map() From 7050acc970fbdd56eed76dfad512a51a0c2aea3e Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 13 Dec 2019 22:45:32 -0500 Subject: [PATCH 11/27] Fixes --- src/bin.ts | 2 +- src/index.ts | 10 +++++----- src/tsconfig-schema.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 6e88df75e..49dcec927 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -189,7 +189,7 @@ export function main (argv: string[]) { : undefined }) - const requires = argsRequire !== undefined ? argsRequire : service.options.requires || [] + const requires = argsRequire.length !== 0 ? argsRequire : service.options.requires || [] // Output project information. if (version >= 2) { diff --git a/src/index.ts b/src/index.ts index ffb3f17fa..c5e89db5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { relative, basename, extname, resolve, dirname, join } from 'path' import sourceMapSupport = require('source-map-support') -import yn from 'yn' +import * as yn from 'yn' import { BaseError } from 'make-error' import * as util from 'util' import * as _ts from 'typescript' @@ -339,9 +339,9 @@ export function create (options: CreateOptions = {}): Register { const compilerBefore = options.compiler /** - * 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. + * Load the typescript compiler. It is required to parse the tsconfig, but + * might be changed by options specified in the tsconfig, so we might need to + * do this twice if the `compiler` option changes. */ function loadCompiler () { const compiler = require.resolve(options.compiler || 'typescript', { paths: [cwd, __dirname] }) @@ -361,7 +361,7 @@ export function create (options: CreateOptions = {}): Register { options = defaults(optionsWithoutDefaults, tsconfigOptions || {}, DEFAULTS) // If `compiler` option changed based on tsconfig, re-load the compiler - if(options.compiler !== compilerBefore) { + if (options.compiler !== compilerBefore) { ({ compiler, ts, readFile, fileExists } = loadCompiler()) } diff --git a/src/tsconfig-schema.ts b/src/tsconfig-schema.ts index b569f9a5c..d25b3025b 100644 --- a/src/tsconfig-schema.ts +++ b/src/tsconfig-schema.ts @@ -1,4 +1,4 @@ -import { TsConfigOptions } from "."; +import { TsConfigOptions } from '.' /* * This interface exists solely for generating a JSON schema for tsconfig.json. From b9b2f672825a04d62f8c4ae453c24859abcf77cd Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 13 Dec 2019 22:49:44 -0500 Subject: [PATCH 12/27] comments --- scripts/create-merged-schema.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/create-merged-schema.ts b/scripts/create-merged-schema.ts index 7241de5a0..f615bb42b 100755 --- a/scripts/create-merged-schema.ts +++ b/scripts/create-merged-schema.ts @@ -44,6 +44,8 @@ async function main() { }, }, allOf: [ + // Splice into the allOf array at a spot that looks good. Does not affect + // behavior of the schema, but looks nicer if we want to submit as a PR to schemastore. ...schemastoreSchema.allOf.slice(0, 4), { "$ref": "#/definitions/tsNodeDefinition" }, ...schemastoreSchema.allOf.slice(4), From 91f5e13ad83f95a1b804e4519c112ef3654e4b44 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 13 Dec 2019 22:57:15 -0500 Subject: [PATCH 13/27] Cleanup --- src/bin.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 49dcec927..976234114 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 { VERSION, DEFAULTS, TSError, parse, Register, RegisterOptions, register } from './index' +import { VERSION, TSError, parse, Register, register } from './index' /** * Eval filename for REPL/debug. @@ -80,7 +80,8 @@ export function main (argv: string[]) { }) // Only setting defaults for CLI-specific flags - // Anything on RegisterOptions can be passed as `undefined` to `register()` + // Anything passed to `register()` can be `undefined`; `create()` will apply + // defaults. const { '--dir': dir, '--help': help = false, From a209bdfb332a2964250e4da3e6db4f6c51eff81c Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sat, 14 Dec 2019 00:18:29 -0500 Subject: [PATCH 14/27] Finalize tsconfig PR --- src/index.ts | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index c5e89db5e..2df043b5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -331,7 +331,7 @@ export function register (opts: RegisterOptions = {}): Register { */ export function create (options: CreateOptions = {}): Register { const optionsWithoutDefaults = options - options = defaults(options, DEFAULTS) + options = defaults(DEFAULTS, options) // Require the TypeScript compiler and configuration. @@ -346,25 +346,25 @@ export function create (options: CreateOptions = {}): Register { function loadCompiler () { 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 { compiler, ts, readFile, fileExists } + return { compiler, ts } } // compute enough options to read the config file - let { compiler, ts, fileExists, readFile } = loadCompiler() + let { compiler, ts } = loadCompiler() // Read config file - const { config, options: tsconfigOptions } = readConfig(cwd, ts, fileExists, readFile, options) + const { config, options: tsconfigOptions } = readConfig(cwd, ts, options) // Merge default options, tsconfig options, and explicit options - options = defaults(optionsWithoutDefaults, tsconfigOptions || {}, DEFAULTS) + options = defaults(DEFAULTS, tsconfigOptions || {}, optionsWithoutDefaults) // If `compiler` option changed based on tsconfig, re-load the compiler if (options.compiler !== compilerBefore) { - ({ compiler, ts, readFile, fileExists } = loadCompiler()) + ({ compiler, ts } = loadCompiler()) } + const readFile = options.readFile || ts.sys.readFile + const fileExists = options.fileExists || ts.sys.fileExists const isScoped = options.scope ? (fileName: string) => relative(cwd, fileName).charAt(0) !== '.' : () => true const ignore = options.skipIgnore ? [] : (options.ignore || ['/node_modules/']).map(str => new RegExp(str)) const transpileOnly = options.transpileOnly === true @@ -626,9 +626,7 @@ 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, options - } + return { ts, config, compile, getTypeInfo, ignored, enabled, options } } /** @@ -730,8 +728,6 @@ function fixConfig (ts: TSCommon, config: _ts.ParsedCommandLine) { function readConfig ( cwd: string, ts: TSCommon, - fileExists: (path: string) => boolean, - readFile: (path: string) => string | undefined, options: CreateOptions ): { /** Parsed TypeScript configuration */ @@ -743,6 +739,11 @@ function readConfig ( let basePath = normalizeSlashes(cwd) let configFileName: string | undefined = undefined + const { + fileExists = ts.sys.fileExists, + readFile = ts.sys.readFile + } = options + // Read project configuration when available. if (!options.skipProject) { configFileName = options.project @@ -762,8 +763,14 @@ function readConfig ( } } + // Fix ts-node options that come from tsconfig.json + const tsconfigOptions: TsConfigOptions = { + ...config['ts-node'] + } + // Remove resolution of "files". - if (!options.files) { + const filesOption = options.files !== undefined ? options.files : tsconfigOptions.files; + if (!filesOption) { config.files = [] config.include = [] } @@ -777,12 +784,13 @@ function readConfig ( TS_NODE_COMPILER_OPTIONS ) - const fixedConfig = fixConfig(ts, ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName)) + const fixedConfig = fixConfig(ts, ts.parseJsonConfigFileContent(config, { + fileExists, + readFile, + readDirectory: ts.sys.readDirectory, + useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, + }, basePath, undefined, configFileName)) - // Fix ts-node options that come from tsconfig.json - const tsconfigOptions: TsConfigOptions = { - ...config['ts-node'] - } if (tsconfigOptions.requires) { // Relative paths are relative to the tsconfig's parent directory, not the `dir` option tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { From 700a90fb483df86753c0da9d76bd76ca4af73e6c Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sat, 14 Dec 2019 00:27:56 -0500 Subject: [PATCH 15/27] Remove ability to specify "requires" in tsconfig --- src/bin.ts | 4 +--- src/index.ts | 12 +----------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 976234114..38523a9dd 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -87,7 +87,7 @@ export function main (argv: string[]) { '--help': help = false, '--script-mode': scriptMode = false, '--version': version = 0, - '--require': argsRequire = [], + '--require': requires = [], '--eval': code = undefined, '--print': print = false, '--interactive': interactive = false, @@ -190,8 +190,6 @@ export function main (argv: string[]) { : undefined }) - const requires = argsRequire.length !== 0 ? argsRequire : service.options.requires || [] - // Output project information. if (version >= 2) { console.log(`ts-node v${VERSION}`) diff --git a/src/index.ts b/src/index.ts index 2df043b5e..5da0ca4f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,9 +166,7 @@ export interface TsConfigOptions | 'skipProject' | 'project' | 'dir' - > { - requires?: Array -} + > {} /** * Like Object.assign or splatting, but never overwrites with `undefined`. @@ -791,14 +789,6 @@ function readConfig ( useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, }, basePath, undefined, configFileName)) - if (tsconfigOptions.requires) { - // Relative paths are relative to the tsconfig's parent directory, not the `dir` option - tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { - if (path.startsWith('.')) return resolve(configFileName!, '..', path) - return path - }) - } - return { config: fixedConfig, options: tsconfigOptions } } From 80ea06c9786ea401e6a758e2029144154510fbd0 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sat, 14 Dec 2019 00:28:05 -0500 Subject: [PATCH 16/27] Fix linter failures --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5da0ca4f7..b0346bb94 100644 --- a/src/index.ts +++ b/src/index.ts @@ -767,7 +767,7 @@ function readConfig ( } // Remove resolution of "files". - const filesOption = options.files !== undefined ? options.files : tsconfigOptions.files; + const filesOption = options.files !== undefined ? options.files : tsconfigOptions.files if (!filesOption) { config.files = [] config.include = [] @@ -786,7 +786,7 @@ function readConfig ( fileExists, readFile, readDirectory: ts.sys.readDirectory, - useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, + useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames }, basePath, undefined, configFileName)) return { config: fixedConfig, options: tsconfigOptions } From e7690b966a69255d2daa9895912cbe4aebc4e2e0 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sat, 14 Dec 2019 00:28:22 -0500 Subject: [PATCH 17/27] Revert "Remove ability to specify "requires" in tsconfig" This reverts commit 700a90fb483df86753c0da9d76bd76ca4af73e6c. --- src/bin.ts | 4 +++- src/index.ts | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 38523a9dd..976234114 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -87,7 +87,7 @@ export function main (argv: string[]) { '--help': help = false, '--script-mode': scriptMode = false, '--version': version = 0, - '--require': requires = [], + '--require': argsRequire = [], '--eval': code = undefined, '--print': print = false, '--interactive': interactive = false, @@ -190,6 +190,8 @@ export function main (argv: string[]) { : undefined }) + const requires = argsRequire.length !== 0 ? argsRequire : service.options.requires || [] + // Output project information. if (version >= 2) { console.log(`ts-node v${VERSION}`) diff --git a/src/index.ts b/src/index.ts index b0346bb94..8891351a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,7 +166,9 @@ export interface TsConfigOptions | 'skipProject' | 'project' | 'dir' - > {} + > { + requires?: Array +} /** * Like Object.assign or splatting, but never overwrites with `undefined`. @@ -789,6 +791,14 @@ function readConfig ( useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames }, basePath, undefined, configFileName)) + if (tsconfigOptions.requires) { + // Relative paths are relative to the tsconfig's parent directory, not the `dir` option + tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { + if (path.startsWith('.')) return resolve(configFileName!, '..', path) + return path + }) + } + return { config: fixedConfig, options: tsconfigOptions } } From 8a5573b70c9c982661c8c627afb30b5909bbd4be Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 11 Feb 2020 15:04:42 -0500 Subject: [PATCH 18/27] remove duplication from merge --- src/index.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9b3775986..d40a58812 100644 --- a/src/index.ts +++ b/src/index.ts @@ -190,18 +190,6 @@ export interface RegisterOptions extends CreateOptions { preferTsExts?: boolean } -export interface TsConfigOptions - extends Omit { - requires?: Array -} - /** * Like Object.assign or splatting, but never overwrites with `undefined`. * This matches the behavior for argument and destructuring defaults. @@ -228,7 +216,9 @@ export interface TsConfigOptions extends Omit {} +> { + requires?: Array +} /** * Like `Object.assign`, but ignores `undefined` properties. From 362db159efb1693ef6fc2997b8bbaa6c7322b2c0 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 11 Feb 2020 15:07:20 -0500 Subject: [PATCH 19/27] comments --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index d40a58812..b0b35a3bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -938,6 +938,8 @@ function readConfig ( if (tsconfigOptions.requires) { // Relative paths are relative to the tsconfig's parent directory, not the `dir` option tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { + // TODO this is not good enough. + // We also need to resolve node_modules relative to the tsconfig.json if (path.startsWith('.')) return resolve(dirname(configFileName!), path) return path }) From 567582e89662a693ed47526f42fb11bb82d87ae9 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 11 Feb 2020 15:09:13 -0500 Subject: [PATCH 20/27] fix --- src/index.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/index.ts b/src/index.ts index b0b35a3bb..89e461e86 100644 --- a/src/index.ts +++ b/src/index.ts @@ -190,22 +190,6 @@ export interface RegisterOptions extends CreateOptions { preferTsExts?: boolean } -/** - * Like Object.assign or splatting, but never overwrites with `undefined`. - * This matches the behavior for argument and destructuring defaults. - * @internal - */ -export function defaults (...sources: Array): T { - const merged: any = {} - for (const source of sources) { - for (const key of Object.keys(source)) { - const value = (source as any)[key] - if (value !== undefined) merged[key] = value - } - } - return merged -} - /** * Must be an interface to support `typescript-json-schema`. */ From bbca6a61ea99c5fa567659c4755f25002791164a Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 28 Jul 2020 15:12:44 -0400 Subject: [PATCH 21/27] Fix --- dist-raw/node-createrequire.js | 29 +++++++++++++++++++++++++ src/bin.ts | 2 +- src/index.ts | 39 ++++++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 dist-raw/node-createrequire.js diff --git a/dist-raw/node-createrequire.js b/dist-raw/node-createrequire.js new file mode 100644 index 000000000..649deb10d --- /dev/null +++ b/dist-raw/node-createrequire.js @@ -0,0 +1,29 @@ +// Extracted from https://github.com/nodejs/node/blob/ec2ffd6b9d255e19818b6949d2f7dc7ac70faee9/lib/internal/modules/cjs/loader.js +// then modified to suit our needs + +const path = require('path'); +const Module = require('module'); + +exports.createRequireFromPath = createRequireFromPath; + +function createRequireFromPath(filename) { + // Allow a directory to be passed as the filename + const trailingSlash = + filename.endsWith('/') || (isWindows && filename.endsWith('\\')); + + const proxyPath = trailingSlash ? + path.join(filename, 'noop.js') : + filename; + + const m = new Module(proxyPath); + m.filename = proxyPath; + + m.paths = Module._nodeModulePaths(m.path); + return makeRequireFunction(m, proxyPath); +} + +// This trick is much smaller than copy-pasting from https://github.com/nodejs/node/blob/ec2ffd6b9d255e19818b6949d2f7dc7ac70faee9/lib/internal/modules/cjs/helpers.js#L32-L101 +function makeRequireFunction(module, filename) { + module._compile('module.exports = require;', filename) + return mod.exports +} diff --git a/src/bin.ts b/src/bin.ts index 81b4a0b2c..aba28d31e 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -199,7 +199,7 @@ export function main (argv: string[]) { : undefined }) - const requires = argsRequire.length !== 0 ? argsRequire : service.options.requires || [] + const requires = argsRequire.length !== 0 ? argsRequire : service.options.require || [] // Output project information. if (version >= 2) { diff --git a/src/index.ts b/src/index.ts index 09c722173..8d2f36065 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import * as ynModule from 'yn' import { BaseError } from 'make-error' import * as util from 'util' import * as _ts from 'typescript' +import * as Module from 'module' /** * Does this version of node obey the package.json "type" field @@ -218,6 +219,19 @@ export interface RegisterOptions extends CreateOptions { preferTsExts?: boolean } +/** + * Options only available via tsconfig, not register() nor create() APIs. + */ +export interface TsConfigExclusiveOptions { + require?: Array +} + +/** + * Options returned by us after we merge programmatic options with those + * from tsconfig.json + */ +export interface ParsedOptions extends RegisterOptions, TsConfigExclusiveOptions {} + /** * Must be an interface to support `typescript-json-schema`. */ @@ -228,9 +242,7 @@ export interface TsConfigOptions extends Omit { - requires?: Array -} + >, TsConfigExclusiveOptions {} /** * Like `Object.assign`, but ignores `undefined` properties. @@ -335,7 +347,7 @@ export class TSError extends BaseError { export interface Register { ts: TSCommon config: _ts.ParsedCommandLine - options: RegisterOptions + options: ParsedOptions enabled (enabled?: boolean): boolean ignored (fileName: string): boolean compile (code: string, fileName: string, lineOffset?: number): string @@ -410,7 +422,7 @@ export function create (rawOptions: CreateOptions = {}): Register { // Read config file and merge new options between env and CLI options. const { config, options: tsconfigOptions } = readConfig(cwd, ts, rawOptions) - const options = assign({}, DEFAULTS, tsconfigOptions || {}, rawOptions) + const options = assign({}, DEFAULTS, tsconfigOptions || {}, rawOptions) // If `compiler` option changed based on tsconfig, re-load the compiler. if (options.compiler !== compilerName) { @@ -993,13 +1005,11 @@ function readConfig ( useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames }, basePath, undefined, configFileName)) - if (tsconfigOptions.requires) { + if (tsconfigOptions.require) { // Relative paths are relative to the tsconfig's parent directory, not the `dir` option - tsconfigOptions.requires = tsconfigOptions.requires.map((path: string) => { - // TODO this is not good enough. - // We also need to resolve node_modules relative to the tsconfig.json - if (path.startsWith('.')) return resolve(dirname(configFileName!), path) - return path + const tsconfigRelativeRequire = createRequire(configFileName) + tsconfigOptions.require = tsconfigOptions.require.map((path: string) => { + return tsconfigRelativeRequire.resolve(path) }) } @@ -1063,3 +1073,10 @@ function getTokenAtPosition (ts: typeof _ts, sourceFile: _ts.SourceFile, positio return current } } + +let nodeCreateRequire: (path: string) => NodeRequire +function createRequire(filename: string) { + if (!nodeCreateRequire) + nodeCreateRequire = Module.createRequire || Module.createRequireFromPath || require('../dist-raw/node-createrequire').createRequireFromPath + return nodeCreateRequire(filename) +} From 1c500d5facbddccd48b7408e55b188865bbdc186 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 28 Jul 2020 15:14:54 -0400 Subject: [PATCH 22/27] fix --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 8d2f36065..c88f8c56f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1007,7 +1007,7 @@ function readConfig ( if (tsconfigOptions.require) { // Relative paths are relative to the tsconfig's parent directory, not the `dir` option - const tsconfigRelativeRequire = createRequire(configFileName) + const tsconfigRelativeRequire = createRequire(configFileName!) tsconfigOptions.require = tsconfigOptions.require.map((path: string) => { return tsconfigRelativeRequire.resolve(path) }) From a9ff8c912587994b047a92e1fb4648ecd8e70065 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 28 Jul 2020 15:35:41 -0400 Subject: [PATCH 23/27] fix --- src/index.ts | 6 ++++-- tests/tsconfig-options/tsconfig.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index c88f8c56f..c393349e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1075,8 +1075,10 @@ function getTokenAtPosition (ts: typeof _ts, sourceFile: _ts.SourceFile, positio } let nodeCreateRequire: (path: string) => NodeRequire -function createRequire(filename: string) { - if (!nodeCreateRequire) +function createRequire (filename: string) { + if (!nodeCreateRequire) { + // tslint:disable-next-line nodeCreateRequire = Module.createRequire || Module.createRequireFromPath || require('../dist-raw/node-createrequire').createRequireFromPath + } return nodeCreateRequire(filename) } diff --git a/tests/tsconfig-options/tsconfig.json b/tests/tsconfig-options/tsconfig.json index 901531e1d..e59c275b4 100644 --- a/tests/tsconfig-options/tsconfig.json +++ b/tests/tsconfig-options/tsconfig.json @@ -5,7 +5,7 @@ "types": ["tsconfig-tsnode-types"] }, "transpileOnly": true, - "requires": ["./required"], + "require": ["./required"], "skipIgnore": false }, "compilerOptions": { From bd53d100c2bb86b7ccc5a2f35cae1baab6d73c65 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 28 Jul 2020 15:41:50 -0400 Subject: [PATCH 24/27] fix --- src/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index e297036bc..d9f98687d 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -493,7 +493,7 @@ describe('ts-node', function () { expect(options.pretty).to.equal(undefined) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.requires).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) + expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) return done() }) }) From 544d1bf4cfd857bdf2c4233a53df6b8a970a028b Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 28 Jul 2020 16:05:56 -0400 Subject: [PATCH 25/27] fix --- src/index.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index d9f98687d..afdc8103e 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -493,7 +493,7 @@ describe('ts-node', function () { expect(options.pretty).to.equal(undefined) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) + expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required.js')]) return done() }) }) @@ -507,7 +507,7 @@ describe('ts-node', function () { expect(options.pretty).to.equal(undefined) expect(options.skipIgnore).to.equal(true) expect(options.transpileOnly).to.equal(true) - expect(options.requires).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) + expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required.js')]) return done() }) }) @@ -527,7 +527,7 @@ describe('ts-node', function () { expect(options.pretty).to.equal(true) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.requires).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required')]) + expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required.js')]) return done() }) }) From 18566b4df752be225fcf4bc4f4741138565d2b85 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 29 Jul 2020 02:06:01 -0400 Subject: [PATCH 26/27] Merge tsconfig and programmatic / CLI `require` arrays; load them in `register()` instead of bin.ts --- src/bin.ts | 6 +----- src/index.ts | 37 ++++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index aba28d31e..ee3380ffc 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -176,6 +176,7 @@ export function main (argv: string[]) { compiler, ignoreDiagnostics, compilerOptions, + require: argsRequire, readFile: code !== undefined ? (path: string) => { if (path === state.path) return state.input @@ -199,8 +200,6 @@ export function main (argv: string[]) { : undefined }) - const requires = argsRequire.length !== 0 ? argsRequire : service.options.require || [] - // Output project information. if (version >= 2) { console.log(`ts-node v${VERSION}`) @@ -214,9 +213,6 @@ export function main (argv: string[]) { module.filename = state.path module.paths = (Module as any)._nodeModulePaths(cwd) - // Require specified modules before start-up. - ;(Module as any)._preloadModules(requires) - // Prepend `ts-node` arguments to CLI for child processes. process.execArgv.unshift(__filename, ...process.argv.slice(2, process.argv.length - args._.length)) process.argv = [process.argv[1]].concat(scriptPath || []).concat(args._.slice(1)) diff --git a/src/index.ts b/src/index.ts index c393349e4..24f6b04f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -196,6 +196,15 @@ export interface CreateOptions { * Ignore TypeScript warnings by diagnostic code. */ ignoreDiagnostics?: Array + /** + * Modules to require, like node's `--require` flag. + * + * If specified in tsconfig.json, the modules will be resolved relative to the tsconfig.json file. + * + * If specified programmatically, each input string should be pre-resolved to an absolute path for + * best results. + */ + require?: Array readFile?: (path: string) => string | undefined fileExists?: (path: string) => boolean transformers?: _ts.CustomTransformers | ((p: _ts.Program) => _ts.CustomTransformers) @@ -219,19 +228,6 @@ export interface RegisterOptions extends CreateOptions { preferTsExts?: boolean } -/** - * Options only available via tsconfig, not register() nor create() APIs. - */ -export interface TsConfigExclusiveOptions { - require?: Array -} - -/** - * Options returned by us after we merge programmatic options with those - * from tsconfig.json - */ -export interface ParsedOptions extends RegisterOptions, TsConfigExclusiveOptions {} - /** * Must be an interface to support `typescript-json-schema`. */ @@ -242,7 +238,7 @@ export interface TsConfigOptions extends Omit, TsConfigExclusiveOptions {} + > {} /** * Like `Object.assign`, but ignores `undefined` properties. @@ -347,7 +343,7 @@ export class TSError extends BaseError { export interface Register { ts: TSCommon config: _ts.ParsedCommandLine - options: ParsedOptions + options: RegisterOptions enabled (enabled?: boolean): boolean ignored (fileName: string): boolean compile (code: string, fileName: string, lineOffset?: number): string @@ -396,6 +392,9 @@ export function register (opts: RegisterOptions = {}): Register { // Register the extensions. registerExtensions(service.options.preferTsExts, extensions, service, originalJsHandler) + // Require specified modules before start-up. + ;(Module as any)._preloadModules(service.options.require) + return service } @@ -422,7 +421,11 @@ export function create (rawOptions: CreateOptions = {}): Register { // Read config file and merge new options between env and CLI options. const { config, options: tsconfigOptions } = readConfig(cwd, ts, rawOptions) - const options = assign({}, DEFAULTS, tsconfigOptions || {}, rawOptions) + const options = assign({}, DEFAULTS, tsconfigOptions || {}, rawOptions) + options.require = [ + ...tsconfigOptions.require || [], + ...rawOptions.require || [] + ] // If `compiler` option changed based on tsconfig, re-load the compiler. if (options.compiler !== compilerName) { @@ -1006,7 +1009,7 @@ function readConfig ( }, basePath, undefined, configFileName)) if (tsconfigOptions.require) { - // Relative paths are relative to the tsconfig's parent directory, not the `dir` option + // Modules are found relative to the tsconfig file, not the `dir` option const tsconfigRelativeRequire = createRequire(configFileName!) tsconfigOptions.require = tsconfigOptions.require.map((path: string) => { return tsconfigRelativeRequire.resolve(path) From 6fb4b8fff82e548bb639316b2ea3df55832f1fa5 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 29 Jul 2020 03:04:37 -0400 Subject: [PATCH 27/27] tests --- src/index.spec.ts | 19 +++++++++++-------- .../{log-options.js => log-options1.js} | 2 +- tests/tsconfig-options/log-options2.js | 3 +++ tests/tsconfig-options/required.js | 1 - tests/tsconfig-options/required1.js | 1 + tests/tsconfig-options/required2.js | 2 ++ tests/tsconfig-options/tsconfig.json | 2 +- 7 files changed, 19 insertions(+), 11 deletions(-) rename tests/tsconfig-options/{log-options.js => log-options1.js} (87%) create mode 100644 tests/tsconfig-options/log-options2.js delete mode 100644 tests/tsconfig-options/required.js create mode 100644 tests/tsconfig-options/required1.js create mode 100644 tests/tsconfig-options/required2.js diff --git a/src/index.spec.ts b/src/index.spec.ts index afdc8103e..fd4b0e052 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -471,7 +471,7 @@ describe('ts-node', function () { const BIN_EXEC = `"${BIN_PATH}" --project tests/tsconfig-options/tsconfig.json` it('should override compiler options from env', function (done) { - exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, { + exec(`${BIN_EXEC} tests/tsconfig-options/log-options1.js`, { env: { ...process.env, TS_NODE_COMPILER_OPTIONS: '{"typeRoots": ["env-typeroots"]}' @@ -485,7 +485,7 @@ describe('ts-node', function () { }) it('should use options from `tsconfig.json`', function (done) { - exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, function (err, stdout) { + exec(`${BIN_EXEC} tests/tsconfig-options/log-options1.js`, function (err, stdout) { expect(err).to.equal(null) const { options, config } = JSON.parse(stdout) expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots').replace(/\\/g, '/')]) @@ -493,13 +493,13 @@ describe('ts-node', function () { expect(options.pretty).to.equal(undefined) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required.js')]) + expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required1.js')]) return done() }) }) - it('should have flags override `tsconfig.json`', function (done) { - exec(`${BIN_EXEC} --skip-ignore --compiler-options "{\\"types\\":[\\"flags-types\\"]}" tests/tsconfig-options/log-options.js`, function (err, stdout) { + it('should have flags override / merge with `tsconfig.json`', function (done) { + exec(`${BIN_EXEC} --skip-ignore --compiler-options "{\\"types\\":[\\"flags-types\\"]}" --require ./tests/tsconfig-options/required2.js tests/tsconfig-options/log-options2.js`, function (err, stdout) { expect(err).to.equal(null) const { options, config } = JSON.parse(stdout) expect(config.options.typeRoots).to.deep.equal([join(__dirname, '../tests/tsconfig-options/tsconfig-typeroots').replace(/\\/g, '/')]) @@ -507,13 +507,16 @@ describe('ts-node', function () { expect(options.pretty).to.equal(undefined) expect(options.skipIgnore).to.equal(true) expect(options.transpileOnly).to.equal(true) - expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required.js')]) + expect(options.require).to.deep.equal([ + join(__dirname, '../tests/tsconfig-options/required1.js'), + './tests/tsconfig-options/required2.js' + ]) return done() }) }) it('should have `tsconfig.json` override environment', function (done) { - exec(`${BIN_EXEC} tests/tsconfig-options/log-options.js`, { + exec(`${BIN_EXEC} tests/tsconfig-options/log-options1.js`, { env: { ...process.env, TS_NODE_PRETTY: 'true', @@ -527,7 +530,7 @@ describe('ts-node', function () { expect(options.pretty).to.equal(true) expect(options.skipIgnore).to.equal(false) expect(options.transpileOnly).to.equal(true) - expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required.js')]) + expect(options.require).to.deep.equal([join(__dirname, '../tests/tsconfig-options/required1.js')]) return done() }) }) diff --git a/tests/tsconfig-options/log-options.js b/tests/tsconfig-options/log-options1.js similarity index 87% rename from tests/tsconfig-options/log-options.js rename to tests/tsconfig-options/log-options1.js index dae2f96d4..59bc33323 100644 --- a/tests/tsconfig-options/log-options.js +++ b/tests/tsconfig-options/log-options1.js @@ -1,5 +1,5 @@ const assert = require('assert') -assert(process.required) +assert(process.required1) const register = process[Symbol.for('ts-node.register.instance')] console.log(JSON.stringify({ options: register.options, diff --git a/tests/tsconfig-options/log-options2.js b/tests/tsconfig-options/log-options2.js new file mode 100644 index 000000000..30f402d16 --- /dev/null +++ b/tests/tsconfig-options/log-options2.js @@ -0,0 +1,3 @@ +const assert = require('assert') +require('./log-options1') +assert(process.required2) diff --git a/tests/tsconfig-options/required.js b/tests/tsconfig-options/required.js deleted file mode 100644 index e2c958cdf..000000000 --- a/tests/tsconfig-options/required.js +++ /dev/null @@ -1 +0,0 @@ -process.required = true diff --git a/tests/tsconfig-options/required1.js b/tests/tsconfig-options/required1.js new file mode 100644 index 000000000..8f3ac408f --- /dev/null +++ b/tests/tsconfig-options/required1.js @@ -0,0 +1 @@ +process.required1 = true diff --git a/tests/tsconfig-options/required2.js b/tests/tsconfig-options/required2.js new file mode 100644 index 000000000..69a3ba618 --- /dev/null +++ b/tests/tsconfig-options/required2.js @@ -0,0 +1,2 @@ +require('assert')(process.required1) +process.required2 = true diff --git a/tests/tsconfig-options/tsconfig.json b/tests/tsconfig-options/tsconfig.json index e59c275b4..3f248317e 100644 --- a/tests/tsconfig-options/tsconfig.json +++ b/tests/tsconfig-options/tsconfig.json @@ -5,7 +5,7 @@ "types": ["tsconfig-tsnode-types"] }, "transpileOnly": true, - "require": ["./required"], + "require": ["./required1"], "skipIgnore": false }, "compilerOptions": {