Skip to content

Commit

Permalink
fix: should support windows and Node.js 14 (#223)
Browse files Browse the repository at this point in the history
use fork instead of runscript
  • Loading branch information
fengmk2 authored Feb 17, 2023
1 parent 715a4d4 commit 8f1b709
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 69 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ jobs:
name: Node.js
uses: artusjs/github-actions/.github/workflows/node-test.yml@v1
with:
os: 'ubuntu-latest, macos-latest'
version: '16, 18'
os: 'ubuntu-latest, macos-latest, windows-latest'
version: '14, 16, 18'
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@
"@types/node": "^18.11.19",
"assert-file": "^1.0.0",
"coffee": "^5.4.0",
"cpy": "^8.1.2",
"egg": "^3.9.1",
"egg-mock": "^5.10.2",
"esbuild": "^0.17.7",
"esbuild-register": "^3.4.2",
"eslint": "^8.16.0",
"eslint-config-egg": "^12.0.0",
"git-contributor": "2",
"npminstall": "^7.5.0",
"typescript": "^4.9.5"
},
"repository": {
Expand All @@ -74,8 +76,7 @@
"test-local-with-esbuild": "node -r esbuild-register src/bin/cli.ts test",
"test-tsc": "npm run clean && npm run tsc && node dist/bin/cli.js && node dist/bin/cli.js test --base test/fixtures/example-ts && node dist/bin/cli.js dev --base test/fixtures/example-ts",
"cov": "c8 -r lcov -r text-summary -x 'test/**' npm run test-local -- --timeout 120000",
"ci-test-only": "npm run test-local -- test/cmd/cov.test.ts",
"ci": "npm run lint && npm run cov && npm run test-tsc",
"ci": "npm run lint && npm run test-local && npm run test-tsc",
"prepublishOnly": "npm run clean && npm run tsc",
"tsc": "tsc",
"clean": "rm -rf dist"
Expand Down
76 changes: 60 additions & 16 deletions src/cmd/base.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
import { debuglog } from 'node:util';
import { fork, ForkOptions, ChildProcess } from 'node:child_process';
import {
DefineCommand,
Options, Option, Command,
CommandContext,
Inject,
Utils,
} from '@artus-cli/artus-cli';
import runscript from 'runscript';

const debug = debuglog('egg-bin:base');

// only hook once and only when ever start any child.
const childs = new Set<ChildProcess>();
let hadHook = false;
function gracefull(proc: ChildProcess) {
// save child ref
childs.add(proc);

// only hook once
/* c8 ignore else */
if (!hadHook) {
hadHook = true;
let signal: NodeJS.Signals;
[ 'SIGINT', 'SIGQUIT', 'SIGTERM' ].forEach(event => {
process.once(event, () => {
signal = event as NodeJS.Signals;
process.exit(0);
});
});

process.once('exit', (code: number) => {
for (const child of childs) {
debug('process exit code: %o, kill child %o with %o', code, child.pid, signal);
child.kill(signal);
}
});
}
}

class ForkError extends Error {
code: number | null;
constructor(message: string, code: number | null) {
super(message);
this.code = code;
}
}

@DefineCommand()
export abstract class BaseCommand extends Command {
@Option({
Expand Down Expand Up @@ -59,26 +95,34 @@ export abstract class BaseCommand extends Command {
return requires;
}

protected async runNodeCmd(nodeCmd: string, nodeRequires?: string[]) {
const parts = [
'node',
];
if (nodeRequires) {
for (const r of nodeRequires) {
parts.push('--require');
parts.push(`'${r}'`);
}
}
parts.push(nodeCmd);
const cmd = parts.join(' ');
debug('runscript: %o', cmd);
protected async forkNode(modulePath: string, args: string[], options: ForkOptions = {}) {
if (this.dryRun) {
console.log('dry run: $ %o', cmd);
console.log('dry run: $ %o', `${process.execPath} ${modulePath} ${args.join(' ')}`);
return;
}
await runscript(cmd, {

options = {
stdio: 'inherit',
env: this.ctx.env,
cwd: this.base,
...options,
};
const proc = fork(modulePath, args, options);
debug('Run fork pid: %o, `%s %s %s`',
proc.pid, process.execPath, modulePath, args.join(' '));
gracefull(proc);

return new Promise<void>((resolve, reject) => {
proc.once('exit', code => {
debug('fork pid: %o exit code %o', proc.pid, code);
childs.delete(proc);
if (code !== 0) {
const err = new ForkError(modulePath + ' ' + args.join(' ') + ' exit with code ' + code, code);
reject(err);
} else {
resolve();
}
});
});
}
}
15 changes: 8 additions & 7 deletions src/cmd/cov.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class CovCommand extends TestCommand {
];
}

protected async runNodeCmd(nodeCmd: string) {
protected async forkNode(modulePath: string, args: string[]) {
if (this.prerequire) {
this.ctx.env.EGG_BIN_PREREQUIRE = 'true';
}
Expand All @@ -56,11 +56,12 @@ export class CovCommand extends TestCommand {
// https://github.com/eggjs/egg/issues/3930
const c8Args = [
// '--show-process-tree',
this.c8,
...this.c8.split(' ').filter(a => a.trim()),
];
if (this.args.typescript) {
this.ctx.env.SPAWN_WRAP_SHIM_ROOT = path.join(this.base, 'node_modules');
c8Args.push('--extension .ts');
c8Args.push('--extension');
c8Args.push('.ts');
}

const excludes = new Set([
Expand All @@ -69,15 +70,15 @@ export class CovCommand extends TestCommand {
...this.x,
]);
for (const exclude of excludes) {
c8Args.push(`-x '${exclude}'`);
c8Args.push('-x');
c8Args.push(`'${exclude}'`);
}

const c8File = require.resolve('c8/bin/c8.js');
const outputDir = path.join(this.base, 'node_modules/.c8_output');
await fs.rm(outputDir, { force: true, recursive: true });
const coverageDir = path.join(this.base, 'coverage');
await fs.rm(coverageDir, { force: true, recursive: true });
nodeCmd = `${c8File} ${c8Args.join(' ')} ${nodeCmd}`;
await super.runNodeCmd(nodeCmd);

await super.forkNode(c8File, [ ...c8Args, modulePath, ...args ]);
}
}
14 changes: 9 additions & 5 deletions src/cmd/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,18 @@ export class DevCommand extends BaseCommand {
this.ctx.env.NODE_ENV = this.ctx.env.NODE_ENV ?? 'development';
this.ctx.env.EGG_MASTER_CLOSE_TIMEOUT = '1000';
const serverBin = path.join(__dirname, '../../scripts/start-cluster.js');
const args = await this.formatEggStartArgs();
const serverCmd = `${serverBin} '${JSON.stringify(args)}'`;
const eggStartOptions = await this.formatEggStartOptions();
const args = [ JSON.stringify(eggStartOptions) ];
const requires = await this.formatRequires();
debug('%o, requires: %o', serverCmd, requires);
await this.runNodeCmd(serverCmd, requires);
const execArgv: string[] = [];
for (const r of requires) {
execArgv.push('--require');
execArgv.push(r);
}
await this.forkNode(serverBin, args, { execArgv });
}

protected async formatEggStartArgs() {
protected async formatEggStartOptions() {
if (!this.port) {
const defaultPort = process.env.EGG_BIN_DEFAULT_PORT ?? 7001;
debug('detect available port');
Expand Down
28 changes: 16 additions & 12 deletions src/cmd/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ export class TestCommand extends BaseCommand {
})
mochawesome: boolean;

@Option({
description: 'bbort ("bail") after first test failure',
alias: 'b',
type: 'boolean',
default: false,
})
bail: boolean;

@Options()
args: any;

Expand Down Expand Up @@ -107,12 +115,7 @@ export class TestCommand extends BaseCommand {

const mochaArgs = await this.formatMochaArgs();
if (!mochaArgs) return;

const mochaCmd = [
mochaFile,
...mochaArgs,
].filter(argv => argv.trim()).join(' ');
await this.runNodeCmd(mochaCmd);
await this.forkNode(mochaFile, mochaArgs);
}

protected async formatMochaArgs() {
Expand Down Expand Up @@ -185,15 +188,16 @@ export class TestCommand extends BaseCommand {
this.dryRun ? '--dry-run' : '',
// force exit
'--exit',
this.bail ? '--bail' : '',
this.grep.map(pattern => `--grep='${pattern}'`).join(' '),
this.timeout === false ? '--no-timeout' : `--timeout ${this.timeout}`,
this.timeout === false ? '--no-timeout' : `--timeout=${this.timeout}`,
this.parallel ? '--parallel' : '',
this.parallel && this.jobs ? `--jobs ${this.jobs}` : '',
reporter ? `--reporter ${reporter}` : '',
reporterOptions ? `--reporter-options ${reporterOptions}` : '',
...requires.map(r => `--require ${r}`),
this.parallel && this.jobs ? `--jobs=${this.jobs}` : '',
reporter ? `--reporter=${reporter}` : '',
reporterOptions ? `--reporter-options=${reporterOptions}` : '',
...requires.map(r => `--require=${r}`),
...files,
];
].filter(a => a.trim());
}

protected async getChangedTestFiles(dir: string, ext: string) {
Expand Down
30 changes: 30 additions & 0 deletions src/middleware/handle_error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { debuglog } from 'node:util';
import {
Inject, ApplicationLifecycle, LifecycleHook, LifecycleHookUnit, Program,
ArtusCliError,
} from '@artus-cli/artus-cli';

const debug = debuglog('egg-bin:midddleware:handle_error');

@LifecycleHookUnit()
export default class implements ApplicationLifecycle {
@Inject()
private readonly program: Program;

@LifecycleHook()
async configDidLoad() {
this.program.use(async (_, next) => {
debug('enter next');
try {
await next();
debug('after next');
} catch (err: any) {
debug('next error: %o', err);
// let artus cli to handle it
if (err instanceof ArtusCliError) throw err;
console.error(err);
process.exit(typeof err.code === 'number' ? err.code : 1);
}
});
}
}
9 changes: 4 additions & 5 deletions test/cmd/cov.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,9 @@ describe('test/cmd/cov.test.ts', () => {
// .debug()
.expect('stdout', /1\) should fail/)
.expect('stdout', /1 failing/)
.end((err, { stdout, code }) => {
assert(err);
assert(stdout.match(/AssertionError/));
assert(code === 1);
});
.expect('stderr', /exit with code 1/)
.expect('code', 1)
.end();
});

it('should run cov when no test files', () => {
Expand Down Expand Up @@ -158,6 +156,7 @@ describe('test/cmd/cov.test.ts', () => {
});

it('test parallel', () => {
if (process.platform === 'win32') return;
return coffee.fork(eggBin, [ 'cov', '--parallel', '--ts=false' ], {
cwd: path.join(fixtures, 'test-demo-app'),
env: { TESTS: 'test/**/*.test.js' },
Expand Down
5 changes: 4 additions & 1 deletion test/cmd/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ describe('test/cmd/dev.test.ts', () => {
const cwd = path.join(fixtures, 'demo-app');

it('should startCluster success', () => {
return coffee.fork(eggBin, [ 'dev' ], { cwd })
return coffee.fork(eggBin, [ 'dev' ], {
cwd,
// env: { NODE_DEBUG: 'egg-bin*' },
})
// .debug()
.expect('stdout', /"workers":1/)
.expect('stdout', /"baseDir":".*?demo-app"/)
Expand Down
13 changes: 12 additions & 1 deletion test/cmd/test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ describe('test/cmd/test.test.ts', () => {
.end();
});

it('should success with --bail', () => {
return coffee.fork(eggBin, [ 'test', '--bail' ], { cwd })
// .debug()
.expect('stdout', /should success/)
.expect('stdout', /a\.test\.js/)
.expect('stdout', /b\/b\.test\.js/)
.notExpect('stdout', /\ba\.js/)
.expect('code', 0)
.end();
});

it('should ignore node_modules and fixtures', () => {
return coffee.fork(eggBin, [ 'test' ], { cwd: path.join(fixtures, 'test-files-glob') })
// .debug()
Expand Down Expand Up @@ -149,7 +160,7 @@ describe('test/cmd/test.test.ts', () => {
})
// .debug()
.expect('stdout', /_mocha /)
.expect('stdout', / --timeout 12345 /)
.expect('stdout', / --timeout=12345 /)
.expect('stdout', / --exit /)
.expect('stdout', /foo\.test\.js/)
.expect('stdout', /--dry-run/)
Expand Down
Loading

0 comments on commit 8f1b709

Please sign in to comment.