diff --git a/lib/index.js b/lib/index.js index 057aed6..4bd7d70 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,9 +19,11 @@ const chokidar = require('chokidar'); const fs = require('fs'); function run() { - const [runCmd, tscArgs, runArgs, isCleanDirectory] = parseArgs(process.argv); + const { cmdPath, tscArgs, parentArgs, childArgs } = parseArgs(process.argv); const cwd = process.cwd(); + const isCleanDirectory = parentArgs.includes('--cleanOutDir'); + debug(`main process running, pid = ${process.pid}`); // 调试模式下 @@ -30,15 +32,18 @@ function run() { } // 添加 --listEmittedFiles 参数以便于获取编译后的文件列表 - if (!tscArgs.includes('--listEmittedFiles') && !tscArgs.includes('--version')) { + if ( + !tscArgs.includes('--listEmittedFiles') && + !tscArgs.includes('--version') + ) { tscArgs.push('--listEmittedFiles'); } debug(`cwd: ${cwd}`); - debug(`runCmd: ${runCmd}`); + debug(`cmdPath: ${cmdPath}`); debug(`tscArgs: ${tscArgs}`); - debug(`runArgs: ${runArgs}`); - debug(`isCleanDirectory: ${isCleanDirectory}`); + debug(`parentArgs: ${parentArgs}`); + debug(`childArgs: ${childArgs}`); let sourceDir = 'src'; let outDir; @@ -80,7 +85,7 @@ function run() { } const baseDir = path.resolve(cwd, outDir); - runArgs.push('--baseDir', baseDir); + childArgs.push('--baseDir', baseDir); if (isCleanDirectory) { /** @@ -189,9 +194,10 @@ function run() { onFirstWatchCompileSuccess: () => { runAfterTsc(); watchDeleteFile(); - if (runCmd) { - runChild = forkRun(runCmd, runArgs, { + if (cmdPath) { + runChild = forkRun(cmdPath, childArgs, { cwd, + parentArgs, }); runChild.onServerReady( async (serverReportOption, isFirstCallback, during) => { diff --git a/lib/process.js b/lib/process.js index 8fcfaba..f68aa03 100644 --- a/lib/process.js +++ b/lib/process.js @@ -1,5 +1,11 @@ const { fork, spawn } = require('child_process'); -const { filterFileChangedText, debug, output, colors } = require('./util'); +const { + filterFileChangedText, + debug, + output, + colors, + convertPosixToGnu, +} = require('./util'); const { CHILD_PROCESS_EXCEPTION_EXIT_CODE } = require('./constants'); const path = require('path'); const fs = require('fs'); @@ -20,7 +26,12 @@ const forkTsc = (tscArgs = [], options = {}) => { let firstStarted = false; let tscPath = isWin ? 'tsc.cmd' : 'tsc'; - const localTscPath = path.join(process.cwd(), 'node_modules', '.bin', tscPath); + const localTscPath = path.join( + process.cwd(), + 'node_modules', + '.bin', + tscPath + ); if (fs.existsSync(localTscPath)) { tscPath = localTscPath; } @@ -99,6 +110,23 @@ const forkRun = (runCmdPath, runArgs = [], options = {}) => { function innerFork(isFirstFork = false) { const startTime = Date.now(); + + // 从 options.parentArgs 中获取需要的 execArgv,parentArgs 是 POSIX 风格,需要过滤后转成 GNU 风格 + const filterExecArgv = ['--inspect', '--inspect-brk']; + + // 先将所有 parentArgs 转换为 GNU 格式 + const gnuStyleParentArgs = convertPosixToGnu(options.parentArgs); + + // 过滤出需要的参数 + const requiredExecArgv = gnuStyleParentArgs.filter(arg => + filterExecArgv.some(filterArg => arg.startsWith(filterArg)) + ); + + // 准备 execArgv + const execArgv = process.execArgv + .concat(['-r', 'source-map-support/register']) + .concat(requiredExecArgv); + runChild = fork('wrap.js', runArgs, { stdio: 'inherit', cwd: __dirname, @@ -108,7 +136,7 @@ const forkRun = (runCmdPath, runArgs = [], options = {}) => { MWTSC_DEVELOPMENT_ENVIRONMENT: 'true', ...process.env, }, - execArgv: process.execArgv.concat(['-r', 'source-map-support/register']), + execArgv: execArgv, }); debug(`fork run process, pid = ${runChild.pid}`); const onServerReady = async data => { diff --git a/lib/util.js b/lib/util.js index eb34d30..60434ce 100644 --- a/lib/util.js +++ b/lib/util.js @@ -5,39 +5,100 @@ const { run } = require('@midwayjs/glob'); const { debuglog } = require('util'); const debug = debuglog('midway:debug'); -exports.parseArgs = argv => { - // 去除头两位参数 - let pargs = argv.slice(2); - - // 是否清理目录 - let isCleanDirectory = false; - if (pargs.includes('--cleanOutDir')) { - isCleanDirectory = true; - // 移除 --cleanOutDir 命令 - pargs.splice( - pargs.findIndex(arg => arg === '--cleanOutDir'), - 1 - ); +/** + * 将 POSIX 风格的参数转换为 GNU 风格 + * @param {string[]} args POSIX 风格的参数数组 + * @return {string[]} GNU 风格的参数数组 + */ +exports.convertPosixToGnu = (args) => { + const gnuArgs = []; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg.startsWith('--') && i + 1 < args.length && !args[i + 1].startsWith('--')) { + // 如果当前参数以 -- 开头,且下一个参数不以 -- 开头,则将它们合并 + gnuArgs.push(`${arg}=${args[i + 1]}`); + i++; // 跳过下一个参数,因为已经合并了 + } else { + gnuArgs.push(arg); + } } + return gnuArgs; +}; - // 找到 --run 命令以及后面的所有参数 - const runIndex = pargs.findIndex(arg => arg === '--run'); +exports.parseArgs = argv => { + // 去除前两个参数(通常是 node 和脚本名) + const args = argv.slice(2); + + const result = { + cmdPath: undefined, + tscArgs: [], + parentArgs: [], + childArgs: [], + }; - if (runIndex === -1) { - return [undefined, pargs, [], isCleanDirectory]; - } + const notTscArgPrefixes = ['--cleanOutDir', '--inspect', '--inspect-brk']; - // 分出 --run,后一个路径,以及剩余的其他参数 - const runArgs = pargs.slice(runIndex + 1); + // 将 GNU 风格的参数转换为 POSIX 风格 + const convertToPosixStyle = args => { + const posixArgs = []; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg.includes('=') && arg.startsWith('--')) { + const [key, value] = arg.split('='); + posixArgs.push(key, value); + } else { + posixArgs.push(arg); + } + } + return posixArgs; + }; - // 简单约定,--run 后面的参数都是给子进程的 - const [runCmdPath, ...runChildArgs] = runArgs; + // 检查参数是否属于 notTscArgs + const isNotTscArg = arg => { + return notTscArgPrefixes.some(prefix => arg.startsWith(prefix)); + }; + + // 处理转换后的参数 + const processArgs = args => { + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (isNotTscArg(arg)) { + result.parentArgs.push(arg); + // 如果下一个参数不是以 -- 开头,认为它是当前参数的值 + if (i + 1 < args.length && !args[i + 1].startsWith('--')) { + result.parentArgs.push(args[i + 1]); + i++; + } + } else if ( + arg.startsWith('--') && + i + 1 < args.length && + !args[i + 1].startsWith('--') + ) { + // 处理 key value 格式 + result.tscArgs.push(arg, args[i + 1]); + i++; // 跳过下一个参数,因为它是当前参数的值 + } else { + result.tscArgs.push(arg); + } + } + }; + + const posixArgs = convertToPosixStyle(args); + const runIndex = posixArgs.indexOf('--run'); - // 从 pargs 中移除 --run 命令后面所有参数 if (runIndex !== -1) { - pargs = pargs.slice(0, runIndex); + // 提取 --run 后面的参数作为子进程参数 + result.cmdPath = posixArgs[runIndex + 1]; + result.childArgs = posixArgs.slice(runIndex + 2); + + // 处理 --run 前面的参数 + processArgs(posixArgs.slice(0, runIndex)); + } else { + // 如果没有 --run,处理所有参数 + processArgs(posixArgs); } - return [runCmdPath, pargs, runChildArgs, isCleanDirectory]; + + return result; }; exports.debounce = function (func, wait, immediate = false) { diff --git a/test/util.test.js b/test/util.test.js index 7bc6633..5dc234e 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -1,17 +1,123 @@ -const { parseArgs, deleteFolderRecursive, copyFilesRecursive, readJSONCFile, filterFileChangedText } = require('../lib/util'); +const { parseArgs, deleteFolderRecursive, copyFilesRecursive, readJSONCFile, filterFileChangedText, convertPosixToGnu } = require('../lib/util'); const fs = require('fs'); const path = require('path') describe('test/util.test.js', () => { - it('should parse', () => { - expect(parseArgs(['node', 'tsc'])).toEqual([undefined, [], [], false]); - expect(parseArgs(['node', 'tsc', '--watch'])).toEqual([undefined, ['--watch'], [], false]); - expect(parseArgs(['node', 'tsc', '--watch', '--run', 'index.js'])).toEqual(['index.js', ['--watch'], [], false]); - expect(parseArgs(['node', 'tsc', '--watch', '--run', 'index.js', '--', '--port', '3000'])).toEqual(['index.js', ['--watch'], ['--', '--port', '3000'], false]); - expect(parseArgs(['node', 'tsc', '--watch', '--project', 'tsconfig.prod.json'])).toEqual([undefined, ['--watch', '--project', 'tsconfig.prod.json'], [], false]); - expect(parseArgs(['node', 'tsc', '--watch', '--run'])).toEqual([undefined, ['--watch'], [], false]); - expect(parseArgs(['node', 'tsc', '--cleanOutDir'])).toEqual([undefined, [], [], true]); - expect(parseArgs(['node', 'tsc', '--watch', '--cleanOutDir', '--build', '--run'])).toEqual([undefined, ['--watch', '--build'], [], true]); + describe('parseArgs', () => { + it('should correctly parse basic arguments', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--project', 'tsconfig.production.json']); + expect(result).toEqual({ + cmdPath: undefined, + tscArgs: ['--watch', '--project', 'tsconfig.production.json'], + parentArgs: [], + childArgs: [] + }); + }); + + it('should correctly parse arguments with --run', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--project', 'tsconfig.production.json', '--run', './bootstrap.js', '--port', '3000']); + expect(result).toEqual({ + cmdPath: './bootstrap.js', + tscArgs: ['--watch', '--project', 'tsconfig.production.json'], + parentArgs: [], + childArgs: ['--port', '3000'] + }); + }); + + it('should correctly handle notTscArgs', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--cleanOutDir', '--inspect', '9229', '--project', 'tsconfig.json', '--run', './app.js']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch', '--project', 'tsconfig.json'], + parentArgs: ['--cleanOutDir', '--inspect', '9229'], + childArgs: [] + }); + }); + + it('should correctly handle GNU-style arguments', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--port=3000', '--inspect-brk=9230', '--run', './app.js', '--env=production']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch', '--port', '3000'], + parentArgs: ['--inspect-brk', '9230'], + childArgs: ['--env', 'production'] + }); + }); + + it('should correctly handle POSIX-style arguments', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--port', '3000', '--inspect-brk', '9230', '--run', './app.js', '--env', 'production']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch', '--port', '3000'], + parentArgs: ['--inspect-brk', '9230'], + childArgs: ['--env', 'production'] + }); + }); + + it('should correctly handle mixed-style arguments', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--port', '3000', '--outDir=./dist', '--cleanOutDir', '--inspect=9229', '--run', './app.js']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch', '--port', '3000', '--outDir', './dist'], + parentArgs: ['--cleanOutDir', '--inspect', '9229'], + childArgs: [] + }); + }); + + it('should correctly handle Node.js inspect arguments without value', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--inspect', '--run', './app.js']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch'], + parentArgs: ['--inspect'], + childArgs: [] + }); + }); + + it('should correctly handle Node.js inspect arguments with port', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--inspect=9229', '--run', './app.js']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch'], + parentArgs: ['--inspect', '9229'], + childArgs: [] + }); + }); + + it('should correctly handle Node.js inspect-brk arguments', () => { + const result = parseArgs(['node', 'mwtsc', '--watch', '--inspect-brk=0.0.0.0:9229', '--run', './app.js']); + expect(result).toEqual({ + cmdPath: './app.js', + tscArgs: ['--watch'], + parentArgs: ['--inspect-brk', '0.0.0.0:9229'], + childArgs: [] + }); + }); + }); + + describe('convertPosixToGnu', () => { + it('should convert POSIX-style arguments to GNU-style', () => { + const posixArgs = ['--watch', '--port', '3000', '--inspect', '9229', '--outDir', './dist']; + const expectedGnuArgs = ['--watch', '--port=3000', '--inspect=9229', '--outDir=./dist']; + expect(convertPosixToGnu(posixArgs)).toEqual(expectedGnuArgs); + }); + + it('should handle arguments without values correctly', () => { + const posixArgs = ['--watch', '--cleanOutDir', '--port', '3000']; + const expectedGnuArgs = ['--watch', '--cleanOutDir', '--port=3000']; + expect(convertPosixToGnu(posixArgs)).toEqual(expectedGnuArgs); + }); + + it('should not change already GNU-style arguments', () => { + const gnuArgs = ['--watch', '--port=3000', '--inspect=9229']; + expect(convertPosixToGnu(gnuArgs)).toEqual(gnuArgs); + }); + + it('should handle mixed POSIX and GNU-style arguments', () => { + const mixedArgs = ['--watch', '--port', '3000', '--inspect=9229', '--outDir', './dist']; + const expectedGnuArgs = ['--watch', '--port=3000', '--inspect=9229', '--outDir=./dist']; + expect(convertPosixToGnu(mixedArgs)).toEqual(expectedGnuArgs); + }); }); describe('deleteFolderRecursive', () => {