Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

448 linhas
15KB

  1. #!/usr/bin/env node
  2. "use strict";
  3. Object.defineProperty(exports, "__esModule", { value: true });
  4. const path_1 = require("path");
  5. const repl_1 = require("repl");
  6. const util_1 = require("util");
  7. const Module = require("module");
  8. const arg = require("arg");
  9. const diff_1 = require("diff");
  10. const vm_1 = require("vm");
  11. const fs_1 = require("fs");
  12. const os_1 = require("os");
  13. const index_1 = require("./index");
  14. /**
  15. * Eval filename for REPL/debug.
  16. */
  17. const EVAL_FILENAME = `[eval].ts`;
  18. /**
  19. * Eval state management.
  20. */
  21. class EvalState {
  22. constructor(path) {
  23. this.path = path;
  24. this.input = '';
  25. this.output = '';
  26. this.version = 0;
  27. this.lines = 0;
  28. }
  29. }
  30. /**
  31. * Main `bin` functionality.
  32. */
  33. function main(argv) {
  34. const args = arg({
  35. // Node.js-like options.
  36. '--eval': String,
  37. '--interactive': Boolean,
  38. '--print': Boolean,
  39. '--require': [String],
  40. // CLI options.
  41. '--help': Boolean,
  42. '--script-mode': Boolean,
  43. '--version': arg.COUNT,
  44. // Project options.
  45. '--dir': String,
  46. '--files': Boolean,
  47. '--compiler': String,
  48. '--compiler-options': index_1.parse,
  49. '--project': String,
  50. '--ignore-diagnostics': [String],
  51. '--ignore': [String],
  52. '--transpile-only': Boolean,
  53. '--type-check': Boolean,
  54. '--compiler-host': Boolean,
  55. '--pretty': Boolean,
  56. '--skip-project': Boolean,
  57. '--skip-ignore': Boolean,
  58. '--prefer-ts-exts': Boolean,
  59. '--log-error': Boolean,
  60. '--emit': Boolean,
  61. // Aliases.
  62. '-e': '--eval',
  63. '-i': '--interactive',
  64. '-p': '--print',
  65. '-r': '--require',
  66. '-h': '--help',
  67. '-s': '--script-mode',
  68. '-v': '--version',
  69. '-T': '--transpile-only',
  70. '-H': '--compiler-host',
  71. '-I': '--ignore',
  72. '-P': '--project',
  73. '-C': '--compiler',
  74. '-D': '--ignore-diagnostics',
  75. '-O': '--compiler-options'
  76. }, {
  77. argv,
  78. stopAtPositional: true
  79. });
  80. // Only setting defaults for CLI-specific flags
  81. // Anything passed to `register()` can be `undefined`; `create()` will apply
  82. // defaults.
  83. 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;
  84. if (help) {
  85. console.log(`
  86. Usage: ts-node [options] [ -e script | script.ts ] [arguments]
  87. Options:
  88. -e, --eval [code] Evaluate code
  89. -p, --print Print result of \`--eval\`
  90. -r, --require [path] Require a node module before execution
  91. -i, --interactive Opens the REPL even if stdin does not appear to be a terminal
  92. -h, --help Print CLI usage
  93. -v, --version Print module version information
  94. -s, --script-mode Use cwd from <script.ts> instead of current directory
  95. -T, --transpile-only Use TypeScript's faster \`transpileModule\`
  96. -H, --compiler-host Use TypeScript's compiler host API
  97. -I, --ignore [pattern] Override the path patterns to skip compilation
  98. -P, --project [path] Path to TypeScript JSON project file
  99. -C, --compiler [name] Specify a custom TypeScript compiler
  100. -D, --ignore-diagnostics [code] Ignore TypeScript warnings by diagnostic code
  101. -O, --compiler-options [opts] JSON object to merge with compiler options
  102. --dir Specify working directory for config resolution
  103. --scope Scope compiler to files within \`cwd\` only
  104. --files Load \`files\`, \`include\` and \`exclude\` from \`tsconfig.json\` on startup
  105. --pretty Use pretty diagnostic formatter (usually enabled by default)
  106. --skip-project Skip reading \`tsconfig.json\`
  107. --skip-ignore Skip \`--ignore\` checks
  108. --prefer-ts-exts Prefer importing TypeScript files over JavaScript files
  109. --log-error Logs TypeScript errors to stderr instead of throwing exceptions
  110. `);
  111. process.exit(0);
  112. }
  113. // Output project information.
  114. if (version === 1) {
  115. console.log(`v${index_1.VERSION}`);
  116. process.exit(0);
  117. }
  118. const cwd = dir || process.cwd();
  119. /** Unresolved. May point to a symlink, not realpath. May be missing file extension */
  120. const scriptPath = args._.length ? path_1.resolve(cwd, args._[0]) : undefined;
  121. const state = new EvalState(scriptPath || path_1.join(cwd, EVAL_FILENAME));
  122. // Register the TypeScript compiler instance.
  123. const service = index_1.register({
  124. dir: getCwd(dir, scriptMode, scriptPath),
  125. emit,
  126. files,
  127. pretty,
  128. transpileOnly,
  129. typeCheck,
  130. compilerHost,
  131. ignore,
  132. preferTsExts,
  133. logError,
  134. project,
  135. skipProject,
  136. skipIgnore,
  137. compiler,
  138. ignoreDiagnostics,
  139. compilerOptions,
  140. readFile: code !== undefined
  141. ? (path) => {
  142. if (path === state.path)
  143. return state.input;
  144. try {
  145. return fs_1.readFileSync(path, 'utf8');
  146. }
  147. catch (err) { /* Ignore. */ }
  148. }
  149. : undefined,
  150. fileExists: code !== undefined
  151. ? (path) => {
  152. if (path === state.path)
  153. return true;
  154. try {
  155. const stats = fs_1.statSync(path);
  156. return stats.isFile() || stats.isFIFO();
  157. }
  158. catch (err) {
  159. return false;
  160. }
  161. }
  162. : undefined
  163. });
  164. // Output project information.
  165. if (version >= 2) {
  166. console.log(`ts-node v${index_1.VERSION}`);
  167. console.log(`node ${process.version}`);
  168. console.log(`compiler v${service.ts.version}`);
  169. process.exit(0);
  170. }
  171. // Create a local module instance based on `cwd`.
  172. const module = new Module(state.path);
  173. module.filename = state.path;
  174. module.paths = Module._nodeModulePaths(cwd);
  175. Module._preloadModules(requires);
  176. // Prepend `ts-node` arguments to CLI for child processes.
  177. process.execArgv.unshift(__filename, ...process.argv.slice(2, process.argv.length - args._.length));
  178. process.argv = [process.argv[1]].concat(scriptPath || []).concat(args._.slice(1));
  179. // Execute the main contents (either eval, script or piped).
  180. if (code !== undefined && !interactive) {
  181. evalAndExit(service, state, module, code, print);
  182. }
  183. else {
  184. if (args._.length) {
  185. Module.runMain();
  186. }
  187. else {
  188. // Piping of execution _only_ occurs when no other script is specified.
  189. // --interactive flag forces REPL
  190. if (interactive || process.stdin.isTTY) {
  191. startRepl(service, state, code);
  192. }
  193. else {
  194. let buffer = code || '';
  195. process.stdin.on('data', (chunk) => buffer += chunk);
  196. process.stdin.on('end', () => evalAndExit(service, state, module, buffer, print));
  197. }
  198. }
  199. }
  200. }
  201. exports.main = main;
  202. /**
  203. * Get project path from args.
  204. */
  205. function getCwd(dir, scriptMode, scriptPath) {
  206. // Validate `--script-mode` usage is correct.
  207. if (scriptMode) {
  208. if (!scriptPath) {
  209. throw new TypeError('Script mode must be used with a script name, e.g. `ts-node -s <script.ts>`');
  210. }
  211. if (dir) {
  212. throw new TypeError('Script mode cannot be combined with `--dir`');
  213. }
  214. // Use node's own resolution behavior to ensure we follow symlinks.
  215. // scriptPath may omit file extension or point to a directory with or without package.json.
  216. // This happens before we are registered, so we tell node's resolver to consider ts, tsx, and jsx files.
  217. // In extremely rare cases, is is technically possible to resolve the wrong directory,
  218. // because we do not yet know preferTsExts, jsx, nor allowJs.
  219. // See also, justification why this will not happen in real-world situations:
  220. // https://github.com/TypeStrong/ts-node/pull/1009#issuecomment-613017081
  221. const exts = ['.js', '.jsx', '.ts', '.tsx'];
  222. const extsTemporarilyInstalled = [];
  223. for (const ext of exts) {
  224. if (!hasOwnProperty(require.extensions, ext)) { // tslint:disable-line
  225. extsTemporarilyInstalled.push(ext);
  226. require.extensions[ext] = function () { }; // tslint:disable-line
  227. }
  228. }
  229. try {
  230. return path_1.dirname(require.resolve(scriptPath));
  231. }
  232. finally {
  233. for (const ext of extsTemporarilyInstalled) {
  234. delete require.extensions[ext]; // tslint:disable-line
  235. }
  236. }
  237. }
  238. return dir;
  239. }
  240. /**
  241. * Evaluate a script.
  242. */
  243. function evalAndExit(service, state, module, code, isPrinted) {
  244. let result;
  245. global.__filename = module.filename;
  246. global.__dirname = path_1.dirname(module.filename);
  247. global.exports = module.exports;
  248. global.module = module;
  249. global.require = module.require.bind(module);
  250. try {
  251. result = _eval(service, state, code);
  252. }
  253. catch (error) {
  254. if (error instanceof index_1.TSError) {
  255. console.error(error);
  256. process.exit(1);
  257. }
  258. throw error;
  259. }
  260. if (isPrinted) {
  261. console.log(typeof result === 'string' ? result : util_1.inspect(result));
  262. }
  263. }
  264. /**
  265. * Evaluate the code snippet.
  266. */
  267. function _eval(service, state, input) {
  268. const lines = state.lines;
  269. const isCompletion = !/\n$/.test(input);
  270. const undo = appendEval(state, input);
  271. let output;
  272. try {
  273. output = service.compile(state.input, state.path, -lines);
  274. }
  275. catch (err) {
  276. undo();
  277. throw err;
  278. }
  279. // Use `diff` to check for new JavaScript to execute.
  280. const changes = diff_1.diffLines(state.output, output);
  281. if (isCompletion) {
  282. undo();
  283. }
  284. else {
  285. state.output = output;
  286. }
  287. return changes.reduce((result, change) => {
  288. return change.added ? exec(change.value, state.path) : result;
  289. }, undefined);
  290. }
  291. /**
  292. * Execute some code.
  293. */
  294. function exec(code, filename) {
  295. const script = new vm_1.Script(code, { filename: filename });
  296. return script.runInThisContext();
  297. }
  298. /**
  299. * Start a CLI REPL.
  300. */
  301. function startRepl(service, state, code) {
  302. // Eval incoming code before the REPL starts.
  303. if (code) {
  304. try {
  305. _eval(service, state, `${code}\n`);
  306. }
  307. catch (err) {
  308. console.error(err);
  309. process.exit(1);
  310. }
  311. }
  312. const repl = repl_1.start({
  313. prompt: '> ',
  314. input: process.stdin,
  315. output: process.stdout,
  316. terminal: process.stdout.isTTY,
  317. eval: replEval,
  318. useGlobal: true
  319. });
  320. /**
  321. * Eval code from the REPL.
  322. */
  323. function replEval(code, _context, _filename, callback) {
  324. let err = null;
  325. let result;
  326. // TODO: Figure out how to handle completion here.
  327. if (code === '.scope') {
  328. callback(err);
  329. return;
  330. }
  331. try {
  332. result = _eval(service, state, code);
  333. }
  334. catch (error) {
  335. if (error instanceof index_1.TSError) {
  336. // Support recoverable compilations using >= node 6.
  337. if (repl_1.Recoverable && isRecoverable(error)) {
  338. err = new repl_1.Recoverable(error);
  339. }
  340. else {
  341. console.error(error);
  342. }
  343. }
  344. else {
  345. err = error;
  346. }
  347. }
  348. return callback(err, result);
  349. }
  350. // Bookmark the point where we should reset the REPL state.
  351. const resetEval = appendEval(state, '');
  352. function reset() {
  353. resetEval();
  354. // Hard fix for TypeScript forcing `Object.defineProperty(exports, ...)`.
  355. exec('exports = module.exports', state.path);
  356. }
  357. reset();
  358. repl.on('reset', reset);
  359. repl.defineCommand('type', {
  360. help: 'Check the type of a TypeScript identifier',
  361. action: function (identifier) {
  362. if (!identifier) {
  363. repl.displayPrompt();
  364. return;
  365. }
  366. const undo = appendEval(state, identifier);
  367. const { name, comment } = service.getTypeInfo(state.input, state.path, state.input.length);
  368. undo();
  369. if (name)
  370. repl.outputStream.write(`${name}\n`);
  371. if (comment)
  372. repl.outputStream.write(`${comment}\n`);
  373. repl.displayPrompt();
  374. }
  375. });
  376. // Set up REPL history when available natively via node.js >= 11.
  377. if (repl.setupHistory) {
  378. const historyPath = process.env.TS_NODE_HISTORY || path_1.join(os_1.homedir(), '.ts_node_repl_history');
  379. repl.setupHistory(historyPath, err => {
  380. if (!err)
  381. return;
  382. console.error(err);
  383. process.exit(1);
  384. });
  385. }
  386. }
  387. /**
  388. * Append to the eval instance and return an undo function.
  389. */
  390. function appendEval(state, input) {
  391. const undoInput = state.input;
  392. const undoVersion = state.version;
  393. const undoOutput = state.output;
  394. const undoLines = state.lines;
  395. // Handle ASI issues with TypeScript re-evaluation.
  396. if (undoInput.charAt(undoInput.length - 1) === '\n' && /^\s*[\/\[(`-]/.test(input) && !/;\s*$/.test(undoInput)) {
  397. state.input = `${state.input.slice(0, -1)};\n`;
  398. }
  399. state.input += input;
  400. state.lines += lineCount(input);
  401. state.version++;
  402. return function () {
  403. state.input = undoInput;
  404. state.output = undoOutput;
  405. state.version = undoVersion;
  406. state.lines = undoLines;
  407. };
  408. }
  409. /**
  410. * Count the number of lines.
  411. */
  412. function lineCount(value) {
  413. let count = 0;
  414. for (const char of value) {
  415. if (char === '\n') {
  416. count++;
  417. }
  418. }
  419. return count;
  420. }
  421. const RECOVERY_CODES = new Set([
  422. 1003,
  423. 1005,
  424. 1109,
  425. 1126,
  426. 1160,
  427. 1161,
  428. 2355 // "A function whose declared type is neither 'void' nor 'any' must return a value."
  429. ]);
  430. /**
  431. * Check if a function can recover gracefully.
  432. */
  433. function isRecoverable(error) {
  434. return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code));
  435. }
  436. /** Safe `hasOwnProperty` */
  437. function hasOwnProperty(object, property) {
  438. return Object.prototype.hasOwnProperty.call(object, property);
  439. }
  440. if (require.main === module) {
  441. main(process.argv.slice(2));
  442. }
  443. //# sourceMappingURL=bin.js.map