From bfac5d78e19286ec0d23479aaf26c447640c6cd2 Mon Sep 17 00:00:00 2001 From: Harry Chen Date: Sat, 4 May 2024 15:52:18 +0800 Subject: [PATCH] fix: spawn EINVAL in windows (#18) * refactor: retry to use tsc --listEmittedFiles to get changed list * fix: bind event without compile fail * fix: windows spawn EINVAL error * fix: windows spawn EINVAL error * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test * chore: add test --- .gitignore | 3 +- lib/index.js | 127 ++++++++--------- lib/process.js | 29 +++- lib/util.js | 132 ++++++++++++------ lib/wrap.js | 7 +- package.json | 7 +- test/fixtures/add_file/b.ts | 1 + test/fixtures/add_file/run.js | 2 + test/fixtures/add_file/tsconfig.json | 21 +++ test/fixtures/remove_file/run.js | 2 + test/fixtures/remove_file/src/b.ts | 1 + test/fixtures/remove_file/tsconfig.json | 23 ++++ test/index.test.js | 174 ++++++++++++++++++++---- test/util.js | 38 ++++++ test/util.test.js | 7 +- 15 files changed, 422 insertions(+), 152 deletions(-) create mode 100644 test/fixtures/add_file/b.ts create mode 100644 test/fixtures/add_file/run.js create mode 100644 test/fixtures/add_file/tsconfig.json create mode 100644 test/fixtures/remove_file/run.js create mode 100644 test/fixtures/remove_file/src/b.ts create mode 100644 test/fixtures/remove_file/tsconfig.json create mode 100644 test/util.js diff --git a/.gitignore b/.gitignore index 4cf59f3..fe2026d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ temp/ dist/ pnpm-lock.yaml yarn.lock -package-lock.json \ No newline at end of file +package-lock.json +test/fixtures/add_file/a.ts diff --git a/lib/index.js b/lib/index.js index eb93da1..276f6d8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,27 +9,30 @@ const { output, colors, debug, + getRelativeDir, + triggerMessage, } = require('./util'); const path = require('path'); const { replaceTscAliasPaths } = require('tsc-alias'); const chokidar = require('chokidar'); - -function getRelativeDir(rootDir, filePathDir) { - if (!filePathDir) { - return filePathDir; - } - return path.relative(rootDir, filePathDir); -} +const fs = require('fs'); function run() { const [runCmd, tscArgs, runArgs, isCleanDirectory] = parseArgs(process.argv); const cwd = process.cwd(); + debug(`main process running, pid = ${process.pid}`); + // 调试模式下 if (process.env.NODE_DEBUG === 'midway:debug') { tscArgs.push(['--preserveWatchOutput']); } + // 添加 --listEmittedFiles 参数以便于获取编译后的文件列表 + if (!tscArgs.includes('--listEmittedFiles')) { + tscArgs.push('--listEmittedFiles'); + } + debug(`cwd: ${cwd}`); debug(`runCmd: ${runCmd}`); debug(`tscArgs: ${tscArgs}`); @@ -40,13 +43,8 @@ function run() { let outDir; let allowJs = false; let tsconfig; - let fileDeleteWatcher; - let fileChangeWatcher; - let fileDirDeleteWatcher; let hasPaths = false; let tsconfigPath; - let isCompileSuccess = false; - const fileChangeList = []; const projectIndex = tscArgs.findIndex(arg => arg === '--project'); if (projectIndex !== -1) { @@ -98,8 +96,9 @@ function run() { debug(`hasPaths: ${hasPaths}`); let runChild; - const restart = debounce(async () => { - if (fileChangeList.length === 0) return; + const restart = debounce(async fileChangedList => { + debug(`fileChangedList: ${fileChangedList}`); + if (fileChangedList && fileChangedList.length === 0) return; async function aliasReplace() { if (hasPaths) { // 这里使用全量替换,tsc 增量编译会把老文件修改回去 @@ -107,15 +106,13 @@ function run() { configFile: tsconfigPath, outDir, }); - // 避免重复触发文件变化 - isCompileSuccess = false; } - output( - `${fileChangeList.length} ${colors.dim('Files has been changed.')}`, - true - ); - // 清空文件列表 - fileChangeList.length = 0; + if (fileChangedList) { + output( + `${fileChangedList.length} ${colors.dim('Files has been changed.')}`, + true + ); + } } await Promise.all([runChild && runChild.kill(), aliasReplace()]); @@ -138,24 +135,37 @@ function run() { ); } - function cleanOutDirAndRestart() { - // 碰到任意的删除情况,直接删除整个构建目录 - deleteFolderRecursive(path.join(cwd, outDir)); - forkTsc(tscArgs, { - cwd, - }); + function cleanOutDirAndRestart(p) { + const distPath = path.join(cwd, outDir, p); + if (!fs.existsSync(distPath)) return; + const stat = fs.statSync(distPath); + // is file + if (stat.isFile()) { + // remove file + fs.unlinkSync(distPath); + } else { + // is directory + deleteFolderRecursive(distPath); + } runAfterTsc(); restart(); } - function onFileChange(p) { - // 这里使用了标识来判断是否编译成功,理论上会有时序问题,但是本质上事件还是同步执行的,测试下来感觉没有问题 - if (!isCompileSuccess) return; - debug(`${colors.dim('File ')}${p}${colors.dim(' has been changed.')}`); - fileChangeList.push(p); - // 单个文件的 hot reload 处理 - restart(); - } + const sourceAbsoluteDir = path.join(cwd, sourceDir); + /** + * 不同平台的监听有很多差异,所以这里采用全文件监听的方案 + * 文件的添加,修改交给 tsc 监听,这里只监听删除 + */ + const fileDeleteWatcher = chokidar + .watch(sourceAbsoluteDir, { + cwd: sourceAbsoluteDir, + }) + .on('all', (event, path) => { + // windows 下无法触发 unlinkDir 事件 + if (event === 'unlink' || event === 'unlinkDir') { + cleanOutDirAndRestart(path); + } + }); // 启动执行 tsc 命令 const child = forkTsc(tscArgs, { @@ -198,50 +208,35 @@ function run() { ); } console.log(''); - - /** - * 第一次成功之后,开始监听文件变化 - * 1、监听 sourceDir 中的 ts 文件或者目录,如果被删除,则做完整的清理 - * 2、处理单个文件更新,触发单个文件 HMR 逻辑 - */ - fileDeleteWatcher = chokidar.watch('**/**.ts', { - cwd: path.join(cwd, sourceDir), - }); - - fileDeleteWatcher.on('unlink', cleanOutDirAndRestart); - - fileDirDeleteWatcher = chokidar - .watch('**/**', { - cwd: path.join(cwd, sourceDir), - }) - .on('unlinkDir', cleanOutDirAndRestart); - - fileChangeWatcher = chokidar - .watch('**/**.js', { - cwd: path.join(cwd, outDir), - }) - .on('change', onFileChange); } + triggerMessage('server-first-ready'); } else { output( `${colors.green('Node.js server')} ${colors.dim( 'restarted in' )} ${during} ms\n` ); + triggerMessage('server-ready'); } } ); } + debug('watch compile success first'); + triggerMessage('watch-compile-success-first'); }, - onWatchCompileSuccess: () => { - isCompileSuccess = true; - restart(); + onWatchCompileSuccess: fileChangedList => { + debug('watch compile success'); + triggerMessage('watch-compile-success'); + restart(fileChangedList); }, onWatchCompileFail: () => { - isCompileSuccess = false; + debug('watch compile fail'); + triggerMessage('watch-compile-fail'); restart.clear(); }, onCompileSuccess: () => { + debug('compile success'); + triggerMessage('compile-success'); runAfterTsc(); }, }); @@ -256,12 +251,6 @@ function run() { if (fileDeleteWatcher) { await fileDeleteWatcher.close(); } - if (fileChangeWatcher) { - await fileChangeWatcher.close(); - } - if (fileDirDeleteWatcher) { - await fileDirDeleteWatcher.close(); - } process.exit(0); } catch (err) { console.error(err); diff --git a/lib/process.js b/lib/process.js index 9a22070..ea5edb2 100644 --- a/lib/process.js +++ b/lib/process.js @@ -1,4 +1,5 @@ const { fork, spawn } = require('child_process'); +const { filterFileChangedText, debug } = require('./util'); // is windows const isWin = process.platform === 'win32'; @@ -17,22 +18,36 @@ const forkTsc = (tscArgs = [], options = {}) => { const child = spawn(isWin ? 'tsc.cmd' : 'tsc', tscArgs, { stdio: ['pipe', 'pipe', 'inherit'], cwd: options.cwd, + shell: isWin ? true : undefined, }); + debug(`fork tsc process, pid = ${child.pid}`); + + const totalFileChangedList = new Set(); + child.stdout.on('data', data => { data = data.toString('utf8'); - if (/TS\d{4,5}/.test(data)) { + const [text, fileChangedList] = filterFileChangedText(data); + if (fileChangedList.length) { + for (const file of fileChangedList) { + totalFileChangedList.add(file); + } + } + + if (/TS\d{4,5}/.test(text)) { // has error - console.log(data); + console.log(text); // 如果已经启动了,则传递成功消息给子进程 options.onWatchCompileFail && options.onWatchCompileFail(); + // 失败后清空 + totalFileChangedList.clear(); } else { - console.log(data); + console.log(text); /** * 为了减少 tsc 误判,最后一条输出会带有错误信息的数字提示,所以使用正则来简单判断 * 如果 tsc 改了,这里也要改 */ - if (/\s\d+\s/.test(data) && /\s0\s/.test(data)) { + if (/\s\d+\s/.test(text) && /\s0\s/.test(text)) { if (!firstStarted) { firstStarted = true; // emit start @@ -40,8 +55,11 @@ const forkTsc = (tscArgs = [], options = {}) => { options.onFirstWatchCompileSuccess(); } else { // 如果已经启动了,则传递成功消息给子进程 - options.onWatchCompileSuccess && options.onWatchCompileSuccess(); + options.onWatchCompileSuccess && + options.onWatchCompileSuccess(Array.from(totalFileChangedList)); } + // 传递后清空 + totalFileChangedList.clear(); } } }); @@ -75,6 +93,7 @@ const forkRun = (runCmdPath, runArgs = [], options = {}) => { }, execArgv: process.execArgv.concat(['-r', 'source-map-support/register']), }); + debug(`fork run process, pid = ${runChild.pid}`); const onServerReady = async data => { try { if (data.title === 'server-ready') { diff --git a/lib/util.js b/lib/util.js index 2a596d4..2e461d0 100644 --- a/lib/util.js +++ b/lib/util.js @@ -44,26 +44,29 @@ exports.debounce = function (func, wait, immediate = false) { let timeout, args, context, timestamp, result; if (null == wait) wait = 100; - function later() { - const last = Date.now() - timestamp; + function later(args) { + return () => { + const last = Date.now() - timestamp; - if (last < wait && last >= 0) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - context = args = null; + if (last < wait && last >= 0) { + timeout = setTimeout(later(args), wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } } - } + }; } - const debounced = (...args) => { + const debounced = (...argsIn) => { // eslint-disable-next-line @typescript-eslint/no-this-alias context = this; timestamp = Date.now(); + args = argsIn; const callNow = immediate && !timeout; - if (!timeout) timeout = setTimeout(later, wait); + if (!timeout) timeout = setTimeout(later(args), wait); if (callNow) { result = func.apply(context, args); context = args = null; @@ -190,31 +193,31 @@ exports.copyFilesRecursive = function (sourceDir, targetDir, allowJS) { exports.colors = function getConsoleColors() { const format = { - reset: "\x1b[0m", - bright: "\x1b[1m", - dim: "\x1b[2m", - underscore: "\x1b[4m", - blink: "\x1b[5m", - reverse: "\x1b[7m", - hidden: "\x1b[8m", - - black: "\x1b[30m", - red: "\x1b[31m", - green: "\x1b[32m", - yellow: "\x1b[33m", - blue: "\x1b[34m", - magenta: "\x1b[35m", - cyan: "\x1b[36m", - white: "\x1b[37m", - - bgBlack: "\x1b[40m", - bgRed: "\x1b[41m", - bgGreen: "\x1b[42m", - bgYellow: "\x1b[43m", - bgBlue: "\x1b[44m", - bgMagenta: "\x1b[45m", - bgCyan: "\x1b[46m", - bgWhite: "\x1b[47m" + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + underscore: '\x1b[4m', + blink: '\x1b[5m', + reverse: '\x1b[7m', + hidden: '\x1b[8m', + + black: '\x1b[30m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + + bgBlack: '\x1b[40m', + bgRed: '\x1b[41m', + bgGreen: '\x1b[42m', + bgYellow: '\x1b[43m', + bgBlue: '\x1b[44m', + bgMagenta: '\x1b[45m', + bgCyan: '\x1b[46m', + bgWhite: '\x1b[47m', }; const colors = {}; @@ -229,16 +232,59 @@ exports.output = function (msg, datePadding = false) { if (datePadding) { // 输出当前时间 HH:mm:ss const now = new Date(); - timeStr = `[${exports.colors.dim(`${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`)}]`; - console.log( - `${timeStr} ${msg}` - ); + timeStr = `[${exports.colors.dim( + `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}` + )}]`; + console.log(`${timeStr} ${msg}`); return; } console.log(msg); -} +}; exports.debug = function (msg) { debug('[mwtsc]: ' + msg); -} +}; + +exports.getRelativeDir = function (rootDir, filePathDir) { + if (!filePathDir) { + return filePathDir; + } + return path.relative(rootDir, filePathDir); +}; + +exports.filterFileChangedText = function (data) { + // return [data without changed text, changedList] + if (!data.includes('TSFILE:')) { + return [data, []]; + } + + const lines = data.split('\n'); + const fileChangedList = []; + + let newData = ''; + for (const line of lines) { + if (/TSFILE:/.test(line)) { + const match = line.match(/TSFILE:\s+(.*)/); + if (match && match[1] && !match[1].endsWith('d.ts')) { + fileChangedList.push(match[1]); + } + } else { + if (line === '' || /\n$/.test(line)) { + newData += line; + } else { + newData += line + '\n'; + } + } + } + + return [newData, fileChangedList]; +}; + +exports.triggerMessage = function (message) { + if (process.send) { + process.send(message); + } else { + process.emit('message', message); + } +}; diff --git a/lib/wrap.js b/lib/wrap.js index f565fc5..6f16dba 100644 --- a/lib/wrap.js +++ b/lib/wrap.js @@ -1,6 +1,7 @@ // 拿到执行路径,以及执行文件 const childPath = process.env.CHILD_CMD_PATH; const childCwd = process.env.CHILD_CWD; +const { join, isAbsolute } = require('path'); process.on('message', data => { if (data.title === 'server-kill') { @@ -9,4 +10,8 @@ process.on('message', data => { }); process.chdir(childCwd); -require(childPath); +if (isAbsolute(childPath)) { + require(childPath); +} else { + require(join(childCwd, childPath)); +} diff --git a/package.json b/package.json index f1cdc69..d789f4a 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,9 @@ "tsc-alias": "^1.8.8" }, "devDependencies": { - "execa": "^5.1.1", "jest": "^29.7.0", "mwts": "^1.3.0", - "typescript": "~4.9.5" + "typescript": "^5.4.5" }, "engines": { "node": ">=12.0.0" @@ -31,8 +30,8 @@ "tsc-watch" ], "scripts": { - "test": "jest --testTimeout=30000", - "cov": "jest --coverage" + "test": "jest --testTimeout=30000 --runInBand --forceExit", + "cov": "jest --testTimeout=30000 --coverage --runInBand --forceExit" }, "repository": { "type": "git", diff --git a/test/fixtures/add_file/b.ts b/test/fixtures/add_file/b.ts new file mode 100644 index 0000000..6a91933 --- /dev/null +++ b/test/fixtures/add_file/b.ts @@ -0,0 +1 @@ +console.log('b') diff --git a/test/fixtures/add_file/run.js b/test/fixtures/add_file/run.js new file mode 100644 index 0000000..860b5fb --- /dev/null +++ b/test/fixtures/add_file/run.js @@ -0,0 +1,2 @@ +console.log('abc') +process.send({ type: 'ready' }); diff --git a/test/fixtures/add_file/tsconfig.json b/test/fixtures/add_file/tsconfig.json new file mode 100644 index 0000000..b3be0de --- /dev/null +++ b/test/fixtures/add_file/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es2018", // target es2018 + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "skipLibCheck": true, + "pretty": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} diff --git a/test/fixtures/remove_file/run.js b/test/fixtures/remove_file/run.js new file mode 100644 index 0000000..860b5fb --- /dev/null +++ b/test/fixtures/remove_file/run.js @@ -0,0 +1,2 @@ +console.log('abc') +process.send({ type: 'ready' }); diff --git a/test/fixtures/remove_file/src/b.ts b/test/fixtures/remove_file/src/b.ts new file mode 100644 index 0000000..6a91933 --- /dev/null +++ b/test/fixtures/remove_file/src/b.ts @@ -0,0 +1 @@ +console.log('b') diff --git a/test/fixtures/remove_file/tsconfig.json b/test/fixtures/remove_file/tsconfig.json new file mode 100644 index 0000000..cd76a92 --- /dev/null +++ b/test/fixtures/remove_file/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "skipLibCheck": true, + "pretty": true, + "outDir": "dist", + "sourceRoot": "src", + }, + "exclude": [ + "dist", + "node_modules", + "*.js", + "*.ts" + ] +} diff --git a/test/index.test.js b/test/index.test.js index c4f35b5..f529c4d 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,60 +1,178 @@ -const execa = require('execa'); const { join, resolve } = require('path'); -const { existsSync, unlinkSync } = require('fs'); +const { unlink } = require('fs/promises'); +const { existsSync, writeFileSync, readFileSync } = require('fs'); const { forkRun } = require('../lib/process'); +const { execa, sleep } = require('./util'); const mtscPath = join(__dirname, '../bin/mwtsc.js'); describe('/test/index.js', () => { - it('should throw error when no --run parameter', async () => { + it.skip('should throw error when no --run parameter', async () => { + const cp = await execa('node', [mtscPath], {}); + await new Promise(resolve => { - process.stderr.on('data', data => { - console.log(data.toString()); + cp.on('exit', code => { + console.log('exit', code); resolve(); }); - process.stdout.on('data', data => { - console.log(data.toString()); - resolve(); - }); + setTimeout(() => { + cp.kill(); + }, 3000); + }) + }); - const cp = execa('node', [mtscPath], []); - cp.stdout.pipe(process.stdout); - cp.stderr.pipe(process.stderr); + it('should compile ts file and ignore run script without watch args', async () => { + const runPath = join(__dirname, 'fixtures/base'); + const cp = await execa('node', [mtscPath, '--run', './run.js'], { + cwd: runPath, + }); + await new Promise((resolve, reject) => { cp.on('exit', code => { - console.log('exit', code); - resolve(); + try { + expect(existsSync(join(runPath, 'dist/a.js'))).toBeTruthy(); + resolve(); + } catch (err) { + reject(err); + } }); setTimeout(() => { cp.kill(); }, 3000); - }) + }); }); - it('should compile ts file and run custom js', async () => { + it('should test ts file change and reload process', async () => { + // prepare + const runPath = join(__dirname, 'fixtures/add_file'); + const file = join(runPath, 'a.ts'); + const fileList = [ + file, + join(runPath, 'dist/a.js'), + ] + + for (const f of fileList) { + if (existsSync(f)) { + await unlink(f); + } + } + + const cp = await execa('node', [mtscPath, '--watch', '--run', './run.js'], { + cwd: runPath, + }); + + // add a new file + writeFileSync(file, 'console.log("a")'); + + await sleep(500); + + // change file + writeFileSync(file, 'console.log("b")'); + + await sleep(500); + await new Promise((resolve, reject) => { - process.stderr.on('data', data => { - console.log(data.toString()); - resolve(); + cp.on('exit', code => { + try { + expect(existsSync(join(runPath, 'dist/a.js'))).toBeTruthy(); + expect(readFileSync(join(runPath, 'dist/a.js'), 'utf-8')).toMatch(/"b"/); + resolve(); + } catch (err) { + reject(err); + } }); - process.stdout.on('data', data => { - console.log(data.toString()); - resolve(); - }); + setTimeout(() => { + cp.kill(); + }, 3000); + }); + }); + + it.skip('should test ts file and directory removed', async () => { + // prepare + const runPath = join(__dirname, 'fixtures/remove_file'); + const file = join(runPath, 'src/a.ts'); + const fileList = [ + file, + join(runPath, 'dist/a.js'), + ] + + for (const f of fileList) { + if (existsSync(f)) { + await unlink(f); + } + } + + const cp = await execa('node', [mtscPath, '--watch', '--run', './run.js'], { + cwd: runPath, + }); + + // add a new file + writeFileSync(file, 'console.log("a")'); + await sleep(2000); + + expect(existsSync(join(runPath, 'dist/a.js'))).toBeTruthy(); + + // remove file + await unlink(file); + + await sleep(2000); - const runPath = join(__dirname, 'fixtures/base'); - const cp = execa('node', [mtscPath, '--run', './run.js'], { - cwd: runPath, + // check file removed + expect(existsSync(file)).toBeFalsy(); + expect(existsSync(join(runPath, 'dist/a.js'))).toBeFalsy(); + + await new Promise((resolve, reject) => { + cp.on('exit', code => { + try { + resolve(); + } catch (err) { + reject(err); + } }); - cp.stdout.pipe(process.stdout); - cp.stderr.pipe(process.stderr); + setTimeout(() => { + cp.kill(); + }, 10000); + }); + }); + + it('should test ts file init error and reload process', async () => { + + // prepare + const runPath = join(__dirname, 'fixtures/add_file'); + const file = join(runPath, 'a.ts'); + + const fileList = [ + file, + join(runPath, 'dist/a.js'), + ] + + for (const f of fileList) { + if (existsSync(f)) { + await unlink(f); + } + } + + // add a error file + writeFileSync(file, 'console.log("a)'); + + const cp = await execa('node', [mtscPath, '--watch', '--run', './run.js'], { + cwd: runPath, + }); + + // change file + writeFileSync(file, 'console.log("b")'); + + await sleep(1000); + + await new Promise((resolve, reject) => { cp.on('exit', code => { try { expect(existsSync(join(runPath, 'dist/a.js'))).toBeTruthy(); + expect(readFileSync(join(runPath, 'dist/a.js'), 'utf-8')).toMatch(/"b"/); resolve(); } catch (err) { reject(err); diff --git a/test/util.js b/test/util.js new file mode 100644 index 0000000..697fb5c --- /dev/null +++ b/test/util.js @@ -0,0 +1,38 @@ +const cp = require('child_process'); + +function execa(cmd, args, options) { + return new Promise((resolve, reject) => { + // mock execa + const child = cp.spawn(cmd, args, Object.assign({ + cwd: __dirname, + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + }, options)); + + child.on('message', (data) => { + if (args.includes('--watch')) { + if (data === 'watch-compile-success-first' || data === 'watch-compile-fail') { + resolve(child); + } else { + console.log('got event:', data); + } + } else { + if (data === 'compile-success') { + resolve(child); + } else { + console.log('got event:', data); + } + } + }); + }); + + return child; +} + +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +exports.execa = execa; +exports.sleep = sleep; diff --git a/test/util.test.js b/test/util.test.js index 6935b34..7bc6633 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -1,4 +1,4 @@ -const { parseArgs, deleteFolderRecursive, copyFilesRecursive, readJSONCFile } = require('../lib/util'); +const { parseArgs, deleteFolderRecursive, copyFilesRecursive, readJSONCFile, filterFileChangedText } = require('../lib/util'); const fs = require('fs'); const path = require('path') @@ -90,4 +90,9 @@ describe('test/util.test.js', () => { expect(data.raw.paths).not.toBeDefined(); }); + it('should parse output TSFILE', () => { + expect(filterFileChangedText('TSFILE: /Users/harry/project/application/open-koa-v3/dist/task/hello.d.ts')).toEqual(['', []]); + expect(filterFileChangedText('TSFILE: /Users/harry/project/application/open-koa-v3/dist/task/hello.js')).toEqual(['', ['/Users/harry/project/application/open-koa-v3/dist/task/hello.js']]); + }); + });