UNPKG

13.5 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.register = void 0;
7const path_1 = require("path");
8const build_utils_1 = __importDefault(require("./build-utils"));
9const { NowBuildError } = build_utils_1.default;
10/*
11 * Fork of TS-Node - https://github.com/TypeStrong/ts-node
12 * Copyright Blake Embrey
13 * MIT License
14 */
15/**
16 * Debugging.
17 */
18const shouldDebug = false;
19const debug = shouldDebug
20 ? console.log.bind(console, 'ts-node')
21 : () => undefined;
22const debugFn = shouldDebug
23 ? (key, fn) => {
24 let i = 0;
25 return (x) => {
26 debug(key, x, ++i);
27 return fn(x);
28 };
29 }
30 : (_, fn) => fn;
31/**
32 * Track the project information.
33 */
34class MemoryCache {
35 constructor(rootFileNames = []) {
36 this.fileContents = new Map();
37 this.fileVersions = new Map();
38 for (const fileName of rootFileNames)
39 this.fileVersions.set(fileName, 1);
40 }
41}
42/**
43 * Default register options.
44 */
45const DEFAULTS = {
46 files: null,
47 pretty: null,
48 compiler: undefined,
49 compilerOptions: undefined,
50 ignore: undefined,
51 project: undefined,
52 ignoreDiagnostics: undefined,
53 logError: null,
54};
55/**
56 * Default TypeScript compiler options required by `ts-node`.
57 */
58const TS_NODE_COMPILER_OPTIONS = {
59 sourceMap: true,
60 inlineSourceMap: false,
61 inlineSources: true,
62 declaration: false,
63 noEmit: false,
64 outDir: '$$ts-node$$',
65};
66/**
67 * Replace backslashes with forward slashes.
68 */
69function normalizeSlashes(value) {
70 return value.replace(/\\/g, '/');
71}
72/**
73 * Cached fs operation wrapper.
74 */
75function cachedLookup(fn) {
76 const cache = new Map();
77 return (arg) => {
78 if (!cache.has(arg)) {
79 cache.set(arg, fn(arg));
80 }
81 return cache.get(arg);
82 };
83}
84/**
85 * Register TypeScript compiler.
86 */
87function register(opts = {}) {
88 const options = Object.assign({}, DEFAULTS, opts);
89 const ignoreDiagnostics = [
90 6059,
91 18002,
92 18003,
93 ...(options.ignoreDiagnostics || []),
94 ].map(Number);
95 // Require the TypeScript compiler and configuration.
96 const cwd = options.basePath || process.cwd();
97 const nowNodeBase = path_1.resolve(__dirname, '..', '..', '..');
98 let compiler;
99 try {
100 compiler = require.resolve(options.compiler || 'typescript', {
101 paths: [options.project || cwd, nowNodeBase],
102 });
103 }
104 catch (e) {
105 compiler = require.resolve(eval('"typescript"'));
106 }
107 //eslint-disable-next-line @typescript-eslint/no-var-requires
108 const ts = require(compiler);
109 if (compiler.startsWith(nowNodeBase)) {
110 console.log('Using TypeScript ' + ts.version + ' (no local tsconfig.json)');
111 }
112 else {
113 console.log('Using TypeScript ' + ts.version + ' (local user-provided)');
114 }
115 const transformers = options.transformers || undefined;
116 const readFile = options.readFile || ts.sys.readFile;
117 const fileExists = options.fileExists || ts.sys.fileExists;
118 const formatDiagnostics = process.stdout.isTTY || options.pretty
119 ? ts.formatDiagnosticsWithColorAndContext
120 : ts.formatDiagnostics;
121 const diagnosticHost = {
122 getNewLine: () => ts.sys.newLine,
123 getCurrentDirectory: () => cwd,
124 getCanonicalFileName: path => path,
125 };
126 function createTSError(diagnostics) {
127 const message = formatDiagnostics(diagnostics, diagnosticHost);
128 return new NowBuildError({ code: 'NODE_TYPESCRIPT_ERROR', message });
129 }
130 function reportTSError(diagnostics, shouldExit) {
131 if (!diagnostics || diagnostics.length === 0) {
132 return;
133 }
134 const error = createTSError(diagnostics);
135 if (shouldExit) {
136 throw error;
137 }
138 else {
139 // Print error in red color and continue execution.
140 console.error('\x1b[31m%s\x1b[0m', error);
141 }
142 }
143 // we create a custom build per tsconfig.json instance
144 const builds = new Map();
145 function getBuild(configFileName = '') {
146 let build = builds.get(configFileName);
147 if (build)
148 return build;
149 const config = readConfig(configFileName);
150 /**
151 * Create the basic required function using transpile mode.
152 */
153 const getOutput = function (code, fileName) {
154 const result = ts.transpileModule(code, {
155 fileName,
156 transformers,
157 compilerOptions: config.options,
158 reportDiagnostics: true,
159 });
160 const diagnosticList = result.diagnostics
161 ? filterDiagnostics(result.diagnostics, ignoreDiagnostics)
162 : [];
163 reportTSError(diagnosticList, config.options.noEmitOnError);
164 return { code: result.outputText, map: result.sourceMapText };
165 };
166 // Use full language services when the fast option is disabled.
167 let getOutputTypeCheck;
168 {
169 const memoryCache = new MemoryCache(config.fileNames);
170 const cachedReadFile = cachedLookup(debugFn('readFile', readFile));
171 // Create the compiler host for type checking.
172 const serviceHost = {
173 getScriptFileNames: () => Array.from(memoryCache.fileVersions.keys()),
174 getScriptVersion: (fileName) => {
175 const version = memoryCache.fileVersions.get(fileName);
176 return version === undefined ? '' : version.toString();
177 },
178 getScriptSnapshot(fileName) {
179 let contents = memoryCache.fileContents.get(fileName);
180 // Read contents into TypeScript memory cache.
181 if (contents === undefined) {
182 contents = cachedReadFile(fileName);
183 if (contents === undefined)
184 return;
185 memoryCache.fileVersions.set(fileName, 1);
186 memoryCache.fileContents.set(fileName, contents);
187 }
188 return ts.ScriptSnapshot.fromString(contents);
189 },
190 readFile: cachedReadFile,
191 readDirectory: cachedLookup(debugFn('readDirectory', ts.sys.readDirectory)),
192 getDirectories: cachedLookup(debugFn('getDirectories', ts.sys.getDirectories)),
193 fileExists: cachedLookup(debugFn('fileExists', fileExists)),
194 directoryExists: cachedLookup(debugFn('directoryExists', ts.sys.directoryExists)),
195 getNewLine: () => ts.sys.newLine,
196 useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
197 getCurrentDirectory: () => cwd,
198 getCompilationSettings: () => config.options,
199 getDefaultLibFileName: () => ts.getDefaultLibFilePath(config.options),
200 getCustomTransformers: () => transformers,
201 };
202 const registry = ts.createDocumentRegistry(ts.sys.useCaseSensitiveFileNames, cwd);
203 const service = ts.createLanguageService(serviceHost, registry);
204 // Set the file contents into cache manually.
205 const updateMemoryCache = function (contents, fileName) {
206 const fileVersion = memoryCache.fileVersions.get(fileName) || 0;
207 // Avoid incrementing cache when nothing has changed.
208 if (memoryCache.fileContents.get(fileName) === contents)
209 return;
210 memoryCache.fileVersions.set(fileName, fileVersion + 1);
211 memoryCache.fileContents.set(fileName, contents);
212 };
213 getOutputTypeCheck = function (code, fileName) {
214 updateMemoryCache(code, fileName);
215 const output = service.getEmitOutput(fileName);
216 // Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`.
217 const diagnostics = service
218 .getSemanticDiagnostics(fileName)
219 .concat(service.getSyntacticDiagnostics(fileName));
220 const diagnosticList = filterDiagnostics(diagnostics, ignoreDiagnostics);
221 reportTSError(diagnosticList, config.options.noEmitOnError);
222 if (output.emitSkipped) {
223 throw new TypeError(`${path_1.relative(cwd, fileName)}: Emit skipped`);
224 }
225 // Throw an error when requiring `.d.ts` files.
226 if (output.outputFiles.length === 0) {
227 throw new TypeError('Unable to require `.d.ts` file.\n' +
228 'This is usually the result of a faulty configuration or import. ' +
229 'Make sure there is a `.js`, `.json` or another executable extension and ' +
230 'loader (attached before `ts-node`) available alongside ' +
231 `\`${path_1.basename(fileName)}\`.`);
232 }
233 return {
234 code: output.outputFiles[1].text,
235 map: output.outputFiles[0].text,
236 };
237 };
238 }
239 builds.set(configFileName, (build = {
240 getOutput,
241 getOutputTypeCheck,
242 }));
243 return build;
244 }
245 // determine the tsconfig.json path for a given folder
246 function detectConfig() {
247 let configFileName = undefined;
248 // Read project configuration when available.
249 configFileName = options.project
250 ? ts.findConfigFile(normalizeSlashes(options.project), fileExists)
251 : ts.findConfigFile(normalizeSlashes(cwd), fileExists);
252 if (configFileName)
253 return normalizeSlashes(configFileName);
254 }
255 /**
256 * Load TypeScript configuration.
257 */
258 function readConfig(configFileName) {
259 let config = { compilerOptions: {} };
260 const basePath = normalizeSlashes(path_1.dirname(configFileName));
261 // Read project configuration when available.
262 if (configFileName) {
263 const result = ts.readConfigFile(configFileName, readFile);
264 // Return diagnostics.
265 if (result.error) {
266 const errorResult = {
267 errors: [result.error],
268 fileNames: [],
269 options: {},
270 };
271 const configDiagnosticList = filterDiagnostics(errorResult.errors, ignoreDiagnostics);
272 // Render the configuration errors.
273 reportTSError(configDiagnosticList, true);
274 return errorResult;
275 }
276 config = result.config;
277 }
278 // Remove resolution of "files".
279 if (!options.files) {
280 config.files = [];
281 config.include = [];
282 }
283 // Override default configuration options `ts-node` requires.
284 config.compilerOptions = Object.assign({}, config.compilerOptions, options.compilerOptions, TS_NODE_COMPILER_OPTIONS);
285 const configResult = fixConfig(ts, ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName));
286 if (configFileName) {
287 const configDiagnosticList = filterDiagnostics(configResult.errors, ignoreDiagnostics);
288 // Render the configuration errors.
289 reportTSError(configDiagnosticList, configResult.options.noEmitOnError);
290 }
291 return configResult;
292 }
293 // Create a simple TypeScript compiler proxy.
294 function compile(code, fileName, skipTypeCheck) {
295 const configFileName = detectConfig();
296 const build = getBuild(configFileName);
297 const { code: value, map: sourceMap } = (skipTypeCheck
298 ? build.getOutput
299 : build.getOutputTypeCheck)(code, fileName);
300 const output = {
301 code: value,
302 map: Object.assign(JSON.parse(sourceMap), {
303 file: path_1.basename(fileName),
304 sources: [fileName],
305 }),
306 };
307 delete output.map.sourceRoot;
308 return output;
309 }
310 return compile;
311}
312exports.register = register;
313/**
314 * Do post-processing on config options to support `ts-node`.
315 */
316function fixConfig(ts, config) {
317 // Delete options that *should not* be passed through.
318 delete config.options.out;
319 delete config.options.outFile;
320 delete config.options.composite;
321 delete config.options.declarationDir;
322 delete config.options.declarationMap;
323 delete config.options.emitDeclarationOnly;
324 delete config.options.tsBuildInfoFile;
325 delete config.options.incremental;
326 // Target esnext output by default (instead of ES3).
327 // This will prevent TS from polyfill/downlevel emit.
328 if (config.options.target === undefined) {
329 config.options.target = ts.ScriptTarget.ESNext;
330 }
331 // When mixing TS with JS, its best to enable this flag.
332 // This is useful when no `tsconfig.json` is supplied.
333 if (config.options.esModuleInterop === undefined) {
334 config.options.esModuleInterop = true;
335 }
336 // Target CommonJS, always!
337 config.options.module = ts.ModuleKind.CommonJS;
338 return config;
339}
340/**
341 * Filter diagnostics.
342 */
343function filterDiagnostics(diagnostics, ignore) {
344 return diagnostics.filter(x => ignore.indexOf(x.code) === -1);
345}