|
- #!/usr/bin/env node
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- const path_1 = require("path");
- const repl_1 = require("repl");
- const util_1 = require("util");
- const Module = require("module");
- const arg = require("arg");
- const diff_1 = require("diff");
- const vm_1 = require("vm");
- const fs_1 = require("fs");
- const os_1 = require("os");
- const index_1 = require("./index");
- /**
- * Eval filename for REPL/debug.
- */
- const EVAL_FILENAME = `[eval].ts`;
- /**
- * Eval state management.
- */
- class EvalState {
- constructor(path) {
- this.path = path;
- this.input = '';
- this.output = '';
- this.version = 0;
- this.lines = 0;
- }
- }
- /**
- * Main `bin` functionality.
- */
- function main(argv) {
- const args = arg({
- // Node.js-like options.
- '--eval': String,
- '--interactive': Boolean,
- '--print': Boolean,
- '--require': [String],
- // CLI options.
- '--help': Boolean,
- '--script-mode': Boolean,
- '--version': arg.COUNT,
- // Project options.
- '--dir': String,
- '--files': Boolean,
- '--compiler': String,
- '--compiler-options': index_1.parse,
- '--project': String,
- '--ignore-diagnostics': [String],
- '--ignore': [String],
- '--transpile-only': Boolean,
- '--type-check': Boolean,
- '--compiler-host': Boolean,
- '--pretty': Boolean,
- '--skip-project': Boolean,
- '--skip-ignore': Boolean,
- '--prefer-ts-exts': Boolean,
- '--log-error': Boolean,
- '--emit': Boolean,
- // Aliases.
- '-e': '--eval',
- '-i': '--interactive',
- '-p': '--print',
- '-r': '--require',
- '-h': '--help',
- '-s': '--script-mode',
- '-v': '--version',
- '-T': '--transpile-only',
- '-H': '--compiler-host',
- '-I': '--ignore',
- '-P': '--project',
- '-C': '--compiler',
- '-D': '--ignore-diagnostics',
- '-O': '--compiler-options'
- }, {
- argv,
- stopAtPositional: true
- });
- // Only setting defaults for CLI-specific flags
- // Anything passed to `register()` can be `undefined`; `create()` will apply
- // defaults.
- const { '--dir': dir, '--help': help = false, '--script-mode': scriptMode = false, '--version': version = 0, '--require': requires = [], '--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, '--type-check': typeCheck, '--compiler-host': compilerHost, '--pretty': pretty, '--skip-project': skipProject, '--skip-ignore': skipIgnore, '--prefer-ts-exts': preferTsExts, '--log-error': logError, '--emit': emit } = args;
- if (help) {
- console.log(`
- Usage: ts-node [options] [ -e script | script.ts ] [arguments]
-
- Options:
-
- -e, --eval [code] Evaluate code
- -p, --print Print result of \`--eval\`
- -r, --require [path] Require a node module before execution
- -i, --interactive Opens the REPL even if stdin does not appear to be a terminal
-
- -h, --help Print CLI usage
- -v, --version Print module version information
- -s, --script-mode Use cwd from <script.ts> instead of current directory
-
- -T, --transpile-only Use TypeScript's faster \`transpileModule\`
- -H, --compiler-host Use TypeScript's compiler host API
- -I, --ignore [pattern] Override the path patterns to skip compilation
- -P, --project [path] Path to TypeScript JSON project file
- -C, --compiler [name] Specify a custom TypeScript compiler
- -D, --ignore-diagnostics [code] Ignore TypeScript warnings by diagnostic code
- -O, --compiler-options [opts] JSON object to merge with compiler options
-
- --dir Specify working directory for config resolution
- --scope Scope compiler to files within \`cwd\` only
- --files Load \`files\`, \`include\` and \`exclude\` from \`tsconfig.json\` on startup
- --pretty Use pretty diagnostic formatter (usually enabled by default)
- --skip-project Skip reading \`tsconfig.json\`
- --skip-ignore Skip \`--ignore\` checks
- --prefer-ts-exts Prefer importing TypeScript files over JavaScript files
- --log-error Logs TypeScript errors to stderr instead of throwing exceptions
- `);
- process.exit(0);
- }
- // Output project information.
- if (version === 1) {
- console.log(`v${index_1.VERSION}`);
- process.exit(0);
- }
- const cwd = dir || process.cwd();
- /** Unresolved. May point to a symlink, not realpath. May be missing file extension */
- const scriptPath = args._.length ? path_1.resolve(cwd, args._[0]) : undefined;
- const state = new EvalState(scriptPath || path_1.join(cwd, EVAL_FILENAME));
- // Register the TypeScript compiler instance.
- const service = index_1.register({
- dir: getCwd(dir, scriptMode, scriptPath),
- emit,
- files,
- pretty,
- transpileOnly,
- typeCheck,
- compilerHost,
- ignore,
- preferTsExts,
- logError,
- project,
- skipProject,
- skipIgnore,
- compiler,
- ignoreDiagnostics,
- compilerOptions,
- readFile: code !== undefined
- ? (path) => {
- if (path === state.path)
- return state.input;
- try {
- return fs_1.readFileSync(path, 'utf8');
- }
- catch (err) { /* Ignore. */ }
- }
- : undefined,
- fileExists: code !== undefined
- ? (path) => {
- if (path === state.path)
- return true;
- try {
- const stats = fs_1.statSync(path);
- return stats.isFile() || stats.isFIFO();
- }
- catch (err) {
- return false;
- }
- }
- : undefined
- });
- // Output project information.
- if (version >= 2) {
- console.log(`ts-node v${index_1.VERSION}`);
- console.log(`node ${process.version}`);
- console.log(`compiler v${service.ts.version}`);
- process.exit(0);
- }
- // Create a local module instance based on `cwd`.
- const module = new Module(state.path);
- module.filename = state.path;
- module.paths = Module._nodeModulePaths(cwd);
- Module._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));
- // Execute the main contents (either eval, script or piped).
- if (code !== undefined && !interactive) {
- evalAndExit(service, state, module, code, print);
- }
- else {
- if (args._.length) {
- Module.runMain();
- }
- else {
- // Piping of execution _only_ occurs when no other script is specified.
- // --interactive flag forces REPL
- if (interactive || process.stdin.isTTY) {
- startRepl(service, state, code);
- }
- else {
- let buffer = code || '';
- process.stdin.on('data', (chunk) => buffer += chunk);
- process.stdin.on('end', () => evalAndExit(service, state, module, buffer, print));
- }
- }
- }
- }
- exports.main = main;
- /**
- * Get project path from args.
- */
- function getCwd(dir, scriptMode, scriptPath) {
- // Validate `--script-mode` usage is correct.
- if (scriptMode) {
- if (!scriptPath) {
- throw new TypeError('Script mode must be used with a script name, e.g. `ts-node -s <script.ts>`');
- }
- if (dir) {
- throw new TypeError('Script mode cannot be combined with `--dir`');
- }
- // Use node's own resolution behavior to ensure we follow symlinks.
- // scriptPath may omit file extension or point to a directory with or without package.json.
- // This happens before we are registered, so we tell node's resolver to consider ts, tsx, and jsx files.
- // In extremely rare cases, is is technically possible to resolve the wrong directory,
- // because we do not yet know preferTsExts, jsx, nor allowJs.
- // See also, justification why this will not happen in real-world situations:
- // https://github.com/TypeStrong/ts-node/pull/1009#issuecomment-613017081
- const exts = ['.js', '.jsx', '.ts', '.tsx'];
- const extsTemporarilyInstalled = [];
- for (const ext of exts) {
- if (!hasOwnProperty(require.extensions, ext)) { // tslint:disable-line
- extsTemporarilyInstalled.push(ext);
- require.extensions[ext] = function () { }; // tslint:disable-line
- }
- }
- try {
- return path_1.dirname(require.resolve(scriptPath));
- }
- finally {
- for (const ext of extsTemporarilyInstalled) {
- delete require.extensions[ext]; // tslint:disable-line
- }
- }
- }
- return dir;
- }
- /**
- * Evaluate a script.
- */
- function evalAndExit(service, state, module, code, isPrinted) {
- let result;
- global.__filename = module.filename;
- global.__dirname = path_1.dirname(module.filename);
- global.exports = module.exports;
- global.module = module;
- global.require = module.require.bind(module);
- try {
- result = _eval(service, state, code);
- }
- catch (error) {
- if (error instanceof index_1.TSError) {
- console.error(error);
- process.exit(1);
- }
- throw error;
- }
- if (isPrinted) {
- console.log(typeof result === 'string' ? result : util_1.inspect(result));
- }
- }
- /**
- * Evaluate the code snippet.
- */
- function _eval(service, state, input) {
- const lines = state.lines;
- const isCompletion = !/\n$/.test(input);
- const undo = appendEval(state, input);
- let output;
- try {
- output = service.compile(state.input, state.path, -lines);
- }
- catch (err) {
- undo();
- throw err;
- }
- // Use `diff` to check for new JavaScript to execute.
- const changes = diff_1.diffLines(state.output, output);
- if (isCompletion) {
- undo();
- }
- else {
- state.output = output;
- }
- return changes.reduce((result, change) => {
- return change.added ? exec(change.value, state.path) : result;
- }, undefined);
- }
- /**
- * Execute some code.
- */
- function exec(code, filename) {
- const script = new vm_1.Script(code, { filename: filename });
- return script.runInThisContext();
- }
- /**
- * Start a CLI REPL.
- */
- function startRepl(service, state, code) {
- // Eval incoming code before the REPL starts.
- if (code) {
- try {
- _eval(service, state, `${code}\n`);
- }
- catch (err) {
- console.error(err);
- process.exit(1);
- }
- }
- const repl = repl_1.start({
- prompt: '> ',
- input: process.stdin,
- output: process.stdout,
- terminal: process.stdout.isTTY,
- eval: replEval,
- useGlobal: true
- });
- /**
- * Eval code from the REPL.
- */
- function replEval(code, _context, _filename, callback) {
- let err = null;
- let result;
- // TODO: Figure out how to handle completion here.
- if (code === '.scope') {
- callback(err);
- return;
- }
- try {
- result = _eval(service, state, code);
- }
- catch (error) {
- if (error instanceof index_1.TSError) {
- // Support recoverable compilations using >= node 6.
- if (repl_1.Recoverable && isRecoverable(error)) {
- err = new repl_1.Recoverable(error);
- }
- else {
- console.error(error);
- }
- }
- else {
- err = error;
- }
- }
- return callback(err, result);
- }
- // Bookmark the point where we should reset the REPL state.
- const resetEval = appendEval(state, '');
- function reset() {
- resetEval();
- // Hard fix for TypeScript forcing `Object.defineProperty(exports, ...)`.
- exec('exports = module.exports', state.path);
- }
- reset();
- repl.on('reset', reset);
- repl.defineCommand('type', {
- help: 'Check the type of a TypeScript identifier',
- action: function (identifier) {
- if (!identifier) {
- repl.displayPrompt();
- return;
- }
- const undo = appendEval(state, identifier);
- const { name, comment } = service.getTypeInfo(state.input, state.path, state.input.length);
- undo();
- if (name)
- repl.outputStream.write(`${name}\n`);
- if (comment)
- repl.outputStream.write(`${comment}\n`);
- repl.displayPrompt();
- }
- });
- // Set up REPL history when available natively via node.js >= 11.
- if (repl.setupHistory) {
- const historyPath = process.env.TS_NODE_HISTORY || path_1.join(os_1.homedir(), '.ts_node_repl_history');
- repl.setupHistory(historyPath, err => {
- if (!err)
- return;
- console.error(err);
- process.exit(1);
- });
- }
- }
- /**
- * Append to the eval instance and return an undo function.
- */
- function appendEval(state, input) {
- const undoInput = state.input;
- const undoVersion = state.version;
- const undoOutput = state.output;
- const undoLines = state.lines;
- // Handle ASI issues with TypeScript re-evaluation.
- if (undoInput.charAt(undoInput.length - 1) === '\n' && /^\s*[\/\[(`-]/.test(input) && !/;\s*$/.test(undoInput)) {
- state.input = `${state.input.slice(0, -1)};\n`;
- }
- state.input += input;
- state.lines += lineCount(input);
- state.version++;
- return function () {
- state.input = undoInput;
- state.output = undoOutput;
- state.version = undoVersion;
- state.lines = undoLines;
- };
- }
- /**
- * Count the number of lines.
- */
- function lineCount(value) {
- let count = 0;
- for (const char of value) {
- if (char === '\n') {
- count++;
- }
- }
- return count;
- }
- const RECOVERY_CODES = new Set([
- 1003,
- 1005,
- 1109,
- 1126,
- 1160,
- 1161,
- 2355 // "A function whose declared type is neither 'void' nor 'any' must return a value."
- ]);
- /**
- * Check if a function can recover gracefully.
- */
- function isRecoverable(error) {
- return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code));
- }
- /** Safe `hasOwnProperty` */
- function hasOwnProperty(object, property) {
- return Object.prototype.hasOwnProperty.call(object, property);
- }
- if (require.main === module) {
- main(process.argv.slice(2));
- }
- //# sourceMappingURL=bin.js.map
|