diff --git a/README.md b/README.md index 5bf8a17..11e04b3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ deploy tool for egg project. -**Note: Windows is not supported** +**Note: Windows is partially supported, see [#22](https://github.com/eggjs/egg-scripts/pull/22)** ## Install diff --git a/lib/cmd/stop.js b/lib/cmd/stop.js index 09585c8..2380dd2 100644 --- a/lib/cmd/stop.js +++ b/lib/cmd/stop.js @@ -3,6 +3,12 @@ const path = require('path'); const sleep = require('mz-modules/sleep'); const Command = require('../command'); +const isWin = process.platform === 'win32'; +const osRelated = { + titlePrefix: isWin ? '\\"title\\":\\"' : '"title":"', + appWorkerPath: isWin ? 'egg-cluster\\lib\\app_worker.js' : 'egg-cluster/lib/app_worker.js', + agentWorkerPath: isWin ? 'egg-cluster\\lib\\agent_worker.js' : 'egg-cluster/lib/agent_worker.js', +}; class StopCommand extends Command { @@ -23,12 +29,6 @@ class StopCommand extends Command { } * run(context) { - /* istanbul ignore next */ - if (process.platform === 'win32') { - this.logger.warn('Windows is not supported, try to kill master process which command contains `start-cluster` or `--type=egg-server` yourself, good luck.'); - process.exit(0); - } - const { argv } = context; this.logger.info(`stopping egg application ${argv.title ? `with --title=${argv.title}` : ''}`); @@ -37,7 +37,7 @@ class StopCommand extends Command { let processList = yield this.helper.findNodeProcess(item => { const cmd = item.cmd; return argv.title ? - cmd.includes('start-cluster') && cmd.includes(`"title":"${argv.title}"`) : + cmd.includes('start-cluster') && cmd.includes(`${osRelated.titlePrefix}${argv.title}`) : cmd.includes('start-cluster'); }); let pids = processList.map(x => x.pid); @@ -57,8 +57,8 @@ class StopCommand extends Command { processList = yield this.helper.findNodeProcess(item => { const cmd = item.cmd; return argv.title ? - (cmd.includes('egg-cluster/lib/app_worker.js') || cmd.includes('egg-cluster/lib/agent_worker.js')) && cmd.includes(`"title":"${argv.title}"`) : - (cmd.includes('egg-cluster/lib/app_worker.js') || cmd.includes('egg-cluster/lib/agent_worker.js')); + (cmd.includes(osRelated.appWorkerPath) || cmd.includes(osRelated.agentWorkerPath)) && cmd.includes(`${osRelated.titlePrefix}${argv.title}`) : + (cmd.includes(osRelated.appWorkerPath) || cmd.includes(osRelated.agentWorkerPath)); }); pids = processList.map(x => x.pid); diff --git a/lib/helper.js b/lib/helper.js index 881d869..e7fd4e7 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -1,10 +1,13 @@ 'use strict'; const runScript = require('runscript'); -const REGEX = /^\s*(\d+)\s+(.*)/; +const isWin = process.platform === 'win32'; +const REGEX = isWin ? /^(.*)\s+(\d+)\s*$/ : /^\s*(\d+)\s+(.*)/; exports.findNodeProcess = function* (filterFn) { - const command = 'ps -eo "pid,command"'; + const command = isWin ? + 'wmic Path win32_process Where "Name = \'node.exe\'" Get CommandLine,ProcessId' : + 'ps -eo "pid,command"'; const stdio = yield runScript(command, { stdio: 'pipe' }); const processList = stdio.stdout.toString().split('\n') .reduce((arr, line) => { @@ -12,7 +15,7 @@ exports.findNodeProcess = function* (filterFn) { const m = line.match(REGEX); /* istanbul ignore else */ if (m) { - const item = { pid: m[1], cmd: m[2] }; + const item = isWin ? { pid: m[2], cmd: m[1] } : { pid: m[1], cmd: m[2] }; if (!filterFn || filterFn(item)) { arr.push(item); } diff --git a/test/fixtures/ts-pkg/package.json b/test/fixtures/ts-pkg/package.json index 818511f..c2c929d 100644 --- a/test/fixtures/ts-pkg/package.json +++ b/test/fixtures/ts-pkg/package.json @@ -8,6 +8,7 @@ "typescript": true }, "scripts": { - "build": "node ../../../node_modules/.bin/tsc" + "build": "node ../../../node_modules/.bin/tsc", + "windows-build": "call ../../../node_modules/.bin/tsc.cmd" } } diff --git a/test/fixtures/ts/package.json b/test/fixtures/ts/package.json index 5c422dd..183b153 100644 --- a/test/fixtures/ts/package.json +++ b/test/fixtures/ts/package.json @@ -5,6 +5,7 @@ "egg": "^1.0.0" }, "scripts": { - "build": "node ../../../node_modules/.bin/tsc" + "build": "node ../../../node_modules/.bin/tsc", + "windows-build": "call ../../../node_modules/.bin/tsc.cmd" } } diff --git a/test/start.test.js b/test/start.test.js index 42bc54a..9c9a49d 100644 --- a/test/start.test.js +++ b/test/start.test.js @@ -10,6 +10,7 @@ const coffee = require('coffee'); const httpclient = require('urllib'); const mm = require('mm'); const utils = require('./utils'); +const isWin = process.platform === 'win32'; describe('test/start.test.js', () => { const eggBin = require.resolve('../bin/egg-scripts.js'); @@ -271,7 +272,8 @@ describe('test/start.test.js', () => { let result = yield httpclient.request('http://127.0.0.1:7001/env'); assert(result.data.toString() === 'pre, true'); result = yield httpclient.request('http://127.0.0.1:7001/path'); - assert(result.data.toString().match(new RegExp(`^${fixturePath}/node_modules/.bin${path.delimiter}`))); + const appBinPath = path.join(fixturePath, 'node_modules/.bin'); + assert(result.data.toString().startsWith(`${appBinPath}${path.delimiter}`)); }); }); @@ -402,7 +404,7 @@ describe('test/start.test.js', () => { describe('start with daemon', () => { let cwd; beforeEach(function* () { - yield utils.cleanup(cwd); + if (cwd) yield utils.cleanup(cwd); yield rimraf(logDir); yield mkdirp(logDir); yield fs.writeFile(path.join(logDir, 'master-stdout.log'), 'just for test'); @@ -410,7 +412,7 @@ describe('test/start.test.js', () => { }); afterEach(function* () { yield coffee.fork(eggBin, [ 'stop', cwd ]) - // .debug() + // .debug() .end(); yield utils.cleanup(cwd); }); @@ -418,7 +420,7 @@ describe('test/start.test.js', () => { it('should start custom-framework', function* () { cwd = fixturePath; yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=2', '--port=7002', cwd ]) - // .debug() + // .debug() .expect('stdout', /Starting custom-framework application/) .expect('stdout', /custom-framework started on http:\/\/127\.0\.0\.1:7002/) .expect('code', 0) @@ -442,7 +444,7 @@ describe('test/start.test.js', () => { it('should start default egg', function* () { cwd = path.join(__dirname, 'fixtures/egg-app'); yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=2', cwd ]) - // .debug() + // .debug() .expect('stdout', /Starting egg application/) .expect('stdout', /egg started on http:\/\/127\.0\.0\.1:7001/) .expect('code', 0) @@ -455,7 +457,7 @@ describe('test/start.test.js', () => { after(function* () { yield coffee.fork(eggBin, [ 'stop', cwd ]) - // .debug() + // .debug() .end(); yield utils.cleanup(cwd); }); @@ -463,7 +465,7 @@ describe('test/start.test.js', () => { it('should status check success, exit with 0', function* () { mm(process.env, 'WAIT_TIME', 5000); yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1' ], { cwd }) - // .debug() + // .debug() .expect('stdout', /Wait Start: 5.../) .expect('stdout', /custom-framework started/) .expect('code', 0) @@ -474,12 +476,14 @@ describe('test/start.test.js', () => { mm(process.env, 'WAIT_TIME', 5000); mm(process.env, 'ERROR', 'error message'); - const stderr = path.join(homePath, 'logs/master-stderr.log'); + let stderr = path.join(homePath, 'logs/master-stderr.log'); + if (isWin) stderr = stderr.replace(/\\/g, '\\\\'); - yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1', '--ignore-stderr' ], { cwd }) - // .debug() - .expect('stderr', /nodejs.Error: error message/) - .expect('stderr', new RegExp(`Start got error, see ${stderr}`)) + const app = coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1', '--ignore-stderr' ], { cwd }); + // app.debug(); + // TODO: find a windows replacement for tail command + if (!isWin) app.expect('stderr', /nodejs.Error: error message/); + yield app.expect('stderr', new RegExp(`Start got error, see ${stderr}`)) .expect('code', 0) .end(); }); @@ -488,12 +492,14 @@ describe('test/start.test.js', () => { mm(process.env, 'WAIT_TIME', 5000); mm(process.env, 'ERROR', 'error message'); - const stderr = path.join(homePath, 'logs/master-stderr.log'); + let stderr = path.join(homePath, 'logs/master-stderr.log'); + if (isWin) stderr = stderr.replace(/\\/g, '\\\\'); - yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1' ], { cwd }) - // .debug() - .expect('stderr', /nodejs.Error: error message/) - .expect('stderr', new RegExp(`Start got error, see ${stderr}`)) + const app = coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1' ], { cwd }); + // app.debug() + // TODO: find a windows replacement for tail command + if (!isWin) app.expect('stderr', /nodejs.Error: error message/); + yield app.expect('stderr', new RegExp(`Start got error, see ${stderr}`)) .expect('code', 1) .end(); }); @@ -502,7 +508,7 @@ describe('test/start.test.js', () => { mm(process.env, 'WAIT_TIME', 10000); yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1', '--timeout=5000' ], { cwd }) - // .debug() + // .debug() .expect('stdout', /Wait Start: 1.../) .expect('stderr', /Start failed, 5s timeout/) .expect('code', 1) diff --git a/test/stop.test.js b/test/stop.test.js index 1ea012e..525a10c 100644 --- a/test/stop.test.js +++ b/test/stop.test.js @@ -10,6 +10,7 @@ const coffee = require('coffee'); const httpclient = require('urllib'); const mm = require('mm'); const utils = require('./utils'); +const isWin = process.platform === 'win32'; describe('test/stop.test.js', () => { const eggBin = require.resolve('../bin/egg-scripts.js'); @@ -60,10 +61,14 @@ describe('test/stop.test.js', () => { // make sure is kill not auto exist assert(!app.stdout.includes('exist by env')); - assert(app.stdout.includes('[master] receive signal SIGTERM, closing')); - assert(app.stdout.includes('[master] exit with code:0')); - assert(app.stdout.includes('[app_worker] exit with code:0')); - // assert(app.stdout.includes('[agent_worker] exit with code:0')); + // no way to handle the SIGTERM signal in windows ? + if (!isWin) { + assert(app.stdout.includes('[master] receive signal SIGTERM, closing')); + assert(app.stdout.includes('[master] exit with code:0')); + assert(app.stdout.includes('[app_worker] exit with code:0')); + // assert(app.stdout.includes('[agent_worker] exit with code:0')); + } + assert(killer.stdout.includes('[egg-scripts] stopping egg application')); assert(killer.stdout.match(/got master pid \["\d+\"\]/i)); }); @@ -98,9 +103,12 @@ describe('test/stop.test.js', () => { // master log const stdout = yield fs.readFile(path.join(logDir, 'master-stdout.log'), 'utf-8'); - assert(stdout.includes('[master] receive signal SIGTERM, closing')); - assert(stdout.includes('[master] exit with code:0')); - assert(stdout.includes('[app_worker] exit with code:0')); + // no way to handle the SIGTERM signal in windows ? + if (!isWin) { + assert(stdout.includes('[master] receive signal SIGTERM, closing')); + assert(stdout.includes('[master] exit with code:0')); + assert(stdout.includes('[app_worker] exit with code:0')); + } yield coffee.fork(eggBin, [ 'stop', fixturePath ]) .debug() @@ -162,10 +170,14 @@ describe('test/stop.test.js', () => { // make sure is kill not auto exist assert(!app.stdout.includes('exist by env')); - assert(app.stdout.includes('[master] receive signal SIGTERM, closing')); - assert(app.stdout.includes('[master] exit with code:0')); - assert(app.stdout.includes('[app_worker] exit with code:0')); - // assert(app.stdout.includes('[agent_worker] exit with code:0')); + // no way to handle the SIGTERM signal in windows ? + if (!isWin) { + assert(app.stdout.includes('[master] receive signal SIGTERM, closing')); + assert(app.stdout.includes('[master] exit with code:0')); + assert(app.stdout.includes('[app_worker] exit with code:0')); + // assert(app.stdout.includes('[agent_worker] exit with code:0')); + } + assert(killer.stdout.includes('[egg-scripts] stopping egg application with --title=example')); assert(killer.stdout.match(/got master pid \["\d+\"\]/i)); }); @@ -214,17 +226,26 @@ describe('test/stop.test.js', () => { // make sure is kill not auto exist assert(!app.stdout.includes('exist by env')); - assert(app.stdout.includes('[master] receive signal SIGTERM, closing')); - assert(app.stdout.includes('[master] exit with code:0')); - assert(app.stdout.includes('[app_worker] exit with code:0')); - // assert(app.stdout.includes('[agent_worker] exit with code:0')); + + // no way to handle the SIGTERM signal in windows ? + if (!isWin) { + assert(app.stdout.includes('[master] receive signal SIGTERM, closing')); + assert(app.stdout.includes('[master] exit with code:0')); + assert(app.stdout.includes('[app_worker] exit with code:0')); + // assert(app.stdout.includes('[agent_worker] exit with code:0')); + } + assert(killer.stdout.includes('[egg-scripts] stopping egg application')); assert(killer.stdout.match(/got master pid \["\d+\","\d+\"\]/i)); assert(!app2.stdout.includes('exist by env')); - assert(app2.stdout.includes('[master] receive signal SIGTERM, closing')); - assert(app2.stdout.includes('[master] exit with code:0')); - assert(app2.stdout.includes('[app_worker] exit with code:0')); + + // no way to handle the SIGTERM signal in windows ? + if (!isWin) { + assert(app2.stdout.includes('[master] receive signal SIGTERM, closing')); + assert(app2.stdout.includes('[master] exit with code:0')); + assert(app2.stdout.includes('[app_worker] exit with code:0')); + } }); }); @@ -232,13 +253,23 @@ describe('test/stop.test.js', () => { const baseDir = path.join(__dirname, 'fixtures/tmp'); beforeEach(function* () { - yield fs.symlink(fixturePath, baseDir); + // if we can't create a symlink, skip the test + try { + yield fs.symlink(fixturePath, baseDir, 'dir'); + } catch (err) { + // may get Error: EPERM: operation not permitted on windows + console.log(`test skiped, can't create symlink: ${err}`); + this.skip(); + } + + // *unix get the real path of symlink, but windows wouldn't + const appPathInRegexp = isWin ? baseDir.replace(/\\/g, '\\\\') : fixturePath; yield utils.cleanup(fixturePath); yield rimraf(logDir); yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=2' ], { cwd: baseDir }) .debug() - .expect('stdout', new RegExp(`Starting custom-framework application at ${fixturePath}`)) + .expect('stdout', new RegExp(`Starting custom-framework application at ${appPathInRegexp}`)) .expect('code', 0) .end(); diff --git a/test/ts.test.js b/test/ts.test.js index f2cf913..cee5bd8 100644 --- a/test/ts.test.js +++ b/test/ts.test.js @@ -10,6 +10,7 @@ const coffee = require('coffee'); const httpclient = require('urllib'); const mm = require('mm'); const utils = require('./utils'); +const isWin = process.platform === 'win32'; describe('test/ts.test.js', () => { const eggBin = require.resolve('../bin/egg-scripts.js'); @@ -28,7 +29,7 @@ describe('test/ts.test.js', () => { beforeEach(function* () { fixturePath = path.join(__dirname, 'fixtures/ts'); yield utils.cleanup(fixturePath); - const result = cp.spawnSync('npm', [ 'run', 'build' ], { cwd: fixturePath }); + const result = cp.spawnSync('npm', [ 'run', isWin ? 'windows-build' : 'build' ], { cwd: fixturePath, shell: isWin }); assert(!result.stderr.toString()); }); @@ -48,7 +49,7 @@ describe('test/ts.test.js', () => { assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); // console.log(result.data); - assert(result.data.stack.includes('app/controller/home.ts:6:13')); + assert(result.data.stack.includes(path.normalize('app/controller/home.ts:6:13'))); }); it('--typescript', function* () { @@ -62,7 +63,7 @@ describe('test/ts.test.js', () => { assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); // console.log(result.data); - assert(result.data.stack.includes('app/controller/home.ts:6:13')); + assert(result.data.stack.includes(path.normalize('app/controller/home.ts:6:13'))); }); it('--sourcemap', function* () { @@ -76,7 +77,7 @@ describe('test/ts.test.js', () => { assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); // console.log(result.data); - assert(result.data.stack.includes('app/controller/home.ts:6:13')); + assert(result.data.stack.includes(path.normalize('app/controller/home.ts:6:13'))); }); }); @@ -85,7 +86,7 @@ describe('test/ts.test.js', () => { beforeEach(function* () { fixturePath = path.join(__dirname, 'fixtures/ts-pkg'); yield utils.cleanup(fixturePath); - const result = cp.spawnSync('npm', [ 'run', 'build' ], { cwd: fixturePath }); + const result = cp.spawnSync('npm', [ 'run', isWin ? 'windows-build' : 'build' ], { cwd: fixturePath, shell: isWin }); assert(!result.stderr.toString()); }); @@ -105,7 +106,7 @@ describe('test/ts.test.js', () => { assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); // console.log(result.data); - assert(result.data.stack.includes('app/controller/home.ts:6:13')); + assert(result.data.stack.includes(path.normalize('app/controller/home.ts:6:13'))); }); }); }); diff --git a/test/utils.js b/test/utils.js index 3e97e7a..7e21f75 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,9 +2,14 @@ const helper = require('../lib/helper'); const sleep = require('mz-modules/sleep'); +const isWin = process.platform === 'win32'; exports.cleanup = function* (baseDir) { - const processList = yield helper.findNodeProcess(x => x.cmd.includes(`"baseDir":"${baseDir}"`)); + const processList = yield helper.findNodeProcess(x => { + const dir = isWin ? baseDir.replace(/\\/g, '\\\\') : baseDir; + const prefix = isWin ? '\\"baseDir\\":\\"' : '"baseDir":"'; + return x.cmd.includes(`${prefix}${dir}`); + }); if (processList.length) { console.log(`cleanup: ${processList.length} to kill`);