@eggjs/bin 7.0.0-beta.4 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +5 -10
  2. package/bin/dev.js +6 -4
  3. package/bin/run.js +2 -2
  4. package/dist/commonjs/baseCommand.d.ts +49 -0
  5. package/dist/commonjs/baseCommand.js +370 -0
  6. package/dist/commonjs/commands/cov.d.ts +22 -0
  7. package/dist/commonjs/commands/cov.js +97 -0
  8. package/dist/commonjs/commands/dev.d.ts +21 -0
  9. package/dist/commonjs/commands/dev.js +95 -0
  10. package/dist/commonjs/commands/test.d.ts +23 -0
  11. package/dist/commonjs/commands/test.js +204 -0
  12. package/dist/commonjs/index.d.ts +7 -0
  13. package/dist/commonjs/index.js +30 -0
  14. package/dist/commonjs/package.json +3 -0
  15. package/dist/commonjs/types.d.ts +9 -0
  16. package/dist/commonjs/types.js +3 -0
  17. package/dist/commonjs/utils.d.ts +4 -0
  18. package/dist/commonjs/utils.js +45 -0
  19. package/dist/esm/baseCommand.d.ts +33 -6
  20. package/dist/esm/baseCommand.js +246 -40
  21. package/dist/esm/commands/cov.d.ts +17 -8
  22. package/dist/esm/commands/cov.js +83 -17
  23. package/dist/esm/commands/dev.d.ts +15 -7
  24. package/dist/esm/commands/dev.js +83 -16
  25. package/dist/esm/commands/test.d.ts +11 -5
  26. package/dist/esm/commands/test.js +89 -51
  27. package/dist/esm/index.d.ts +7 -5
  28. package/dist/esm/index.js +8 -6
  29. package/dist/esm/types.d.ts +9 -0
  30. package/dist/esm/types.js +2 -0
  31. package/dist/esm/utils.d.ts +0 -1
  32. package/dist/esm/utils.js +1 -11
  33. package/dist/package.json +1 -1
  34. package/dist/scripts/start-cluster.cjs +15 -0
  35. package/package.json +12 -28
  36. package/scripts/start-cluster.cjs +15 -0
  37. package/src/baseCommand.ts +254 -43
  38. package/src/commands/cov.ts +87 -17
  39. package/src/commands/dev.ts +84 -15
  40. package/src/commands/test.ts +92 -51
  41. package/src/index.ts +9 -5
  42. package/src/types.ts +9 -0
  43. package/src/utils.ts +0 -10
  44. package/dist/esm/bin/cli.d.ts +0 -2
  45. package/dist/esm/bin/cli.js +0 -34
  46. package/dist/esm/cmd/base.d.ts +0 -12
  47. package/dist/esm/cmd/base.js +0 -135
  48. package/dist/esm/cmd/cov.d.ts +0 -8
  49. package/dist/esm/cmd/cov.js +0 -103
  50. package/dist/esm/cmd/debug.d.ts +0 -5
  51. package/dist/esm/cmd/debug.js +0 -28
  52. package/dist/esm/cmd/dev.d.ts +0 -17
  53. package/dist/esm/cmd/dev.js +0 -118
  54. package/dist/esm/cmd/test.d.ts +0 -15
  55. package/dist/esm/cmd/test.js +0 -237
  56. package/dist/esm/commands/debug.d.ts +0 -13
  57. package/dist/esm/commands/debug.js +0 -25
  58. package/dist/esm/config/framework.d.ts +0 -4
  59. package/dist/esm/config/framework.js +0 -4
  60. package/dist/esm/config/plugin.d.ts +0 -11
  61. package/dist/esm/config/plugin.js +0 -11
  62. package/dist/esm/hooks/init/options.d.ts +0 -3
  63. package/dist/esm/hooks/init/options.js +0 -5
  64. package/dist/esm/middleware/global_options.d.ts +0 -5
  65. package/dist/esm/middleware/global_options.js +0 -182
  66. package/dist/esm/middleware/handle_error.d.ts +0 -5
  67. package/dist/esm/middleware/handle_error.js +0 -47
  68. package/dist/esm/middleware/inspect.d.ts +0 -5
  69. package/dist/esm/middleware/inspect.js +0 -69
  70. package/src/bin/cli.ts +0 -37
  71. package/src/cmd/base.ts +0 -133
  72. package/src/cmd/cov.ts +0 -89
  73. package/src/cmd/debug.ts +0 -14
  74. package/src/cmd/dev.ts +0 -102
  75. package/src/cmd/test.ts +0 -219
  76. package/src/commands/debug.ts +0 -30
  77. package/src/config/framework.ts +0 -3
  78. package/src/config/plugin.ts +0 -10
  79. package/src/hooks/init/options.ts +0 -7
  80. package/src/middleware/global_options.ts +0 -169
  81. package/src/middleware/handle_error.ts +0 -30
  82. package/src/middleware/inspect.ts +0 -54
@@ -1,14 +1,15 @@
1
1
  import { debuglog } from 'node:util';
2
- // import path from 'node:path';
3
2
  import { pathToFileURL } from 'node:url';
3
+ import path from 'node:path';
4
4
  import { fork, ForkOptions, ChildProcess } from 'node:child_process';
5
5
  import { Command, Flags, Interfaces } from '@oclif/core';
6
6
  import { importResolve } from '@eggjs/utils';
7
+ import { runScript } from 'runscript';
7
8
  import {
8
- addNodeOptionsToEnv,
9
9
  getSourceDirname,
10
- // readPackageJSON, hasTsConfig, getSourceFilename,
10
+ readPackageJSON, hasTsConfig,
11
11
  } from './utils.js';
12
+ import { PackageEgg } from './types.js';
12
13
 
13
14
  const debug = debuglog('@eggjs/bin/baseCommand');
14
15
 
@@ -40,7 +41,7 @@ function graceful(proc: ChildProcess) {
40
41
  }
41
42
  }
42
43
 
43
- class ForkError extends Error {
44
+ export class ForkError extends Error {
44
45
  code: number | null;
45
46
  constructor(message: string, code: number | null) {
46
47
  super(message);
@@ -48,8 +49,12 @@ class ForkError extends Error {
48
49
  }
49
50
  }
50
51
 
51
- export type Flags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>;
52
- export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
52
+ export interface ForkNodeOptions extends ForkOptions {
53
+ dryRun?: boolean;
54
+ }
55
+
56
+ type Flags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>;
57
+ type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
53
58
 
54
59
  export abstract class BaseCommand<T extends typeof Command> extends Command {
55
60
  // add the --json flag
@@ -63,7 +68,7 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
63
68
  // options: ['debug', 'warn', 'error', 'info', 'trace'] as const,
64
69
  // summary: 'Specify level for logging.',
65
70
  // })(),
66
- dryRun: Flags.boolean({
71
+ 'dry-run': Flags.boolean({
67
72
  default: false,
68
73
  helpGroup: 'GLOBAL',
69
74
  summary: 'whether show full command script only',
@@ -75,21 +80,66 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
75
80
  char: 'r',
76
81
  multiple: true,
77
82
  }),
83
+ import: Flags.string({
84
+ helpGroup: 'GLOBAL',
85
+ summary: 'import the given module, only work on ESM',
86
+ multiple: true,
87
+ }),
78
88
  base: Flags.string({
79
89
  helpGroup: 'GLOBAL',
80
- summary: 'directory of application, default to `process.cwd()`',
90
+ summary: 'directory of application',
81
91
  aliases: [ 'baseDir' ],
82
92
  default: process.cwd(),
83
93
  }),
94
+ tscompiler: Flags.string({
95
+ helpGroup: 'GLOBAL',
96
+ summary: 'TypeScript compiler, like ts-node/register',
97
+ aliases: [ 'tsc' ],
98
+ }),
99
+ // flag with no value (--typescript)
100
+ typescript: Flags.boolean({
101
+ helpGroup: 'GLOBAL',
102
+ description: '[default: true] use TypeScript to run the test',
103
+ allowNo: true,
104
+ }),
105
+ ts: Flags.string({
106
+ helpGroup: 'GLOBAL',
107
+ description: 'shortcut for --typescript, e.g.: --ts=false',
108
+ options: [ 'true', 'false' ],
109
+ }),
110
+ javascript: Flags.boolean({
111
+ helpGroup: 'GLOBAL',
112
+ description: 'use JavaScript to run the test',
113
+ aliases: [ 'js' ],
114
+ }),
115
+ declarations: Flags.boolean({
116
+ helpGroup: 'GLOBAL',
117
+ description: 'whether create typings, will add `--require egg-ts-helper/register`',
118
+ aliases: [ 'dts' ],
119
+ }),
120
+ // https://nodejs.org/dist/latest-v18.x/docs/api/cli.html#--inspect-brkhostport
121
+ inspect: Flags.boolean({
122
+ helpGroup: 'GLOBAL',
123
+ description: 'Activate inspector',
124
+ }),
125
+ 'inspect-brk': Flags.boolean({
126
+ helpGroup: 'GLOBAL',
127
+ description: 'Activate inspector and break at start of user script',
128
+ }),
84
129
  };
85
130
 
86
131
  protected flags!: Flags<T>;
87
132
  protected args!: Args<T>;
88
133
 
89
- protected env = process.env;
134
+ protected env = { ...process.env };
135
+ protected pkg: Record<string, any>;
136
+ protected isESM: boolean;
137
+ protected pkgEgg: PackageEgg;
138
+ protected globalExecArgv: string[] = [];
90
139
 
91
140
  public async init(): Promise<void> {
92
141
  await super.init();
142
+ debug('[init] raw args: %o, NODE_ENV: %o', this.argv, this.env.NODE_ENV);
93
143
  const { args, flags } = await this.parse({
94
144
  flags: this.ctor.flags,
95
145
  baseFlags: (super.ctor as typeof BaseCommand).baseFlags,
@@ -100,18 +150,151 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
100
150
  this.flags = flags as Flags<T>;
101
151
  this.args = args as Args<T>;
102
152
 
103
- // use ts-node/esm loader on esm
104
- let esmLoader = importResolve('ts-node/esm', {
105
- paths: [ getSourceDirname() ],
106
- });
107
- // ES Module loading with absolute path fails on windows
108
- // https://github.com/nodejs/node/issues/31710#issuecomment-583916239
109
- // https://nodejs.org/api/url.html#url_url_pathtofileurl_path
110
- // Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'
111
- esmLoader = pathToFileURL(esmLoader).href;
112
- // wait for https://github.com/nodejs/node/issues/40940
113
- addNodeOptionsToEnv('--no-warnings', this.env);
114
- addNodeOptionsToEnv(`--loader ${esmLoader}`, this.env);
153
+ await this.#afterInit();
154
+ }
155
+
156
+ async #afterInit() {
157
+ const { args, flags } = this;
158
+ debug('before: args: %o, flags: %o', args, flags);
159
+ if (!path.isAbsolute(flags.base)) {
160
+ flags.base = path.join(process.cwd(), flags.base);
161
+ }
162
+ const pkg = await readPackageJSON(flags.base);
163
+ this.pkg = pkg;
164
+ this.pkgEgg = pkg.egg ?? {};
165
+ flags.tscompiler = flags.tscompiler ?? this.env.TS_COMPILER ?? this.pkgEgg.tscompiler;
166
+
167
+ let typescript: boolean = flags.typescript;
168
+ // keep compatible with old ts flag: `--ts=true` or `--ts=false`
169
+ if (flags.ts === 'true') {
170
+ typescript = true;
171
+ } else if (flags.ts === 'false') {
172
+ typescript = false;
173
+ }
174
+
175
+ if (typescript === undefined) {
176
+ // try to ready EGG_TYPESCRIPT env first, only accept 'true' or 'false' string
177
+ if (this.env.EGG_TYPESCRIPT === 'false') {
178
+ typescript = false;
179
+ debug('detect typescript=%o from EGG_TYPESCRIPT=%o', false, this.env.EGG_TYPESCRIPT);
180
+ } else if (this.env.EGG_TYPESCRIPT === 'true') {
181
+ typescript = true;
182
+ debug('detect typescript=%o from EGG_TYPESCRIPT=%o', true, this.env.EGG_TYPESCRIPT);
183
+ } else if (typeof this.pkgEgg.typescript === 'boolean') {
184
+ // read `egg.typescript` from package.json if not pass argv
185
+ typescript = this.pkgEgg.typescript;
186
+ debug('detect typescript=%o from pkg.egg.typescript=%o', typescript, this.pkgEgg.typescript);
187
+ } else if (pkg.dependencies?.typescript) {
188
+ // auto detect pkg.dependencies.typescript or pkg.devDependencies.typescript
189
+ typescript = true;
190
+ debug('detect typescript=%o from pkg.dependencies.typescript=%o', true, pkg.dependencies.typescript);
191
+ } else if (pkg.devDependencies?.typescript) {
192
+ typescript = true;
193
+ debug('detect typescript=%o from pkg.devDependencies.typescript=%o', true, pkg.devDependencies.typescript);
194
+ } else if (await hasTsConfig(flags.base)) {
195
+ // tsconfig.json exists
196
+ typescript = true;
197
+ debug('detect typescript=%o cause tsconfig.json exists', true);
198
+ } else if (flags.tscompiler) {
199
+ typescript = true;
200
+ debug('detect typescript=%o from --tscompiler=%o', true, flags.tscompiler);
201
+ }
202
+ }
203
+ flags.typescript = typescript;
204
+
205
+ this.isESM = pkg.type === 'module';
206
+ if (typescript) {
207
+ const findPaths: string[] = [ getSourceDirname() ];
208
+ if (flags.tscompiler) {
209
+ // try app baseDir first on custom tscompiler
210
+ // then try to find tscompiler in @eggjs/bin/node_modules
211
+ findPaths.unshift(flags.base);
212
+ }
213
+ flags.tscompiler = flags.tscompiler ?? 'ts-node/register';
214
+ const tsNodeRegister = importResolve(flags.tscompiler, {
215
+ paths: findPaths,
216
+ });
217
+ flags.tscompiler = tsNodeRegister;
218
+ // should require tsNodeRegister on current process, let it can require *.ts files
219
+ // e.g.: dev command will execute egg loader to find configs and plugins
220
+ // await importModule(tsNodeRegister);
221
+ // let child process auto require ts-node too
222
+ this.addNodeOptions(this.formatImportModule(tsNodeRegister));
223
+ // tell egg loader to load ts file
224
+ // see https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L443
225
+ this.env.EGG_TYPESCRIPT = 'true';
226
+ // set current process.env.EGG_TYPESCRIPT too
227
+ process.env.EGG_TYPESCRIPT = 'true';
228
+ // load files from tsconfig on startup
229
+ this.env.TS_NODE_FILES = process.env.TS_NODE_FILES ?? 'true';
230
+ // keep same logic with egg-core, test cmd load files need it
231
+ // see https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L49
232
+ // addNodeOptionsToEnv(`--require ${importResolve('tsconfig-paths/register', {
233
+ // paths: [ getSourceDirname() ],
234
+ // })}`, ctx.env);
235
+ }
236
+ if (this.isESM) {
237
+ // use ts-node/esm loader on esm
238
+ let esmLoader = importResolve('ts-node/esm', {
239
+ paths: [ getSourceDirname() ],
240
+ });
241
+ // ES Module loading with absolute path fails on windows
242
+ // https://github.com/nodejs/node/issues/31710#issuecomment-583916239
243
+ // https://nodejs.org/api/url.html#url_url_pathtofileurl_path
244
+ // Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'
245
+ esmLoader = pathToFileURL(esmLoader).href;
246
+ // wait for https://github.com/nodejs/node/issues/40940
247
+ this.addNodeOptions('--no-warnings');
248
+ this.addNodeOptions(`--loader ${esmLoader}`);
249
+ }
250
+
251
+ if (flags.declarations === undefined) {
252
+ if (typeof this.pkgEgg.declarations === 'boolean') {
253
+ // read `egg.declarations` from package.json if not pass argv
254
+ flags.declarations = this.pkgEgg.declarations;
255
+ debug('detect declarations from pkg.egg.declarations=%o', this.pkgEgg.declarations);
256
+ }
257
+ }
258
+ if (flags.declarations) {
259
+ const etsBin = importResolve('egg-ts-helper/dist/bin', {
260
+ paths: [
261
+ flags.base,
262
+ getSourceDirname(),
263
+ ],
264
+ });
265
+ debug('run ets first: %o', etsBin);
266
+ await runScript(`node ${etsBin}`);
267
+ }
268
+
269
+ if (this.pkgEgg.revert) {
270
+ const reverts = Array.isArray(this.pkgEgg.revert) ? this.pkgEgg.revert : [ this.pkgEgg.revert ];
271
+ for (const revert of reverts) {
272
+ this.globalExecArgv.push(`--security-revert=${revert}`);
273
+ }
274
+ }
275
+
276
+ let hasInspectOption = false;
277
+ if (flags.inspect) {
278
+ this.addNodeOptions('--inspect');
279
+ hasInspectOption = true;
280
+ }
281
+ if (flags['inspect-brk']) {
282
+ this.addNodeOptions('--inspect-brk');
283
+ hasInspectOption = true;
284
+ }
285
+ if (hasInspectOption) {
286
+ Reflect.set(flags, 'timeout', 0);
287
+ debug('set timeout = 0 when inspect enable');
288
+ } else if (this.env.JB_DEBUG_FILE) {
289
+ // others like WebStorm 2019 will pass NODE_OPTIONS, and @eggjs/bin itself will be debug, so could detect `process.env.JB_DEBUG_FILE`.
290
+ Reflect.set(flags, 'timeout', 0);
291
+ debug('set timeout = false when process.env.JB_DEBUG_FILE=%o', this.env.JB_DEBUG_FILE);
292
+ }
293
+
294
+ debug('baseDir: %o, isESM: %o', flags.base, this.isESM);
295
+ debug('set NODE_OPTIONS: %o', this.env.NODE_OPTIONS);
296
+ debug('after: args: %o, flags: %o', args, flags);
297
+ debug('enter real command: %o', this.id);
115
298
  }
116
299
 
117
300
  protected async catch(err: Error & {exitCode?: number}): Promise<any> {
@@ -126,42 +309,70 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
126
309
  }
127
310
 
128
311
  protected async formatRequires(): Promise<string[]> {
129
- const requires = this.args.require ?? [];
130
- // const eggRequire = this.args.pkgEgg.require;
131
- // if (Array.isArray(eggRequire)) {
132
- // for (const r of eggRequire) {
133
- // requires.push(r);
134
- // }
135
- // } else if (typeof eggRequire === 'string' && eggRequire) {
136
- // requires.push(eggRequire);
137
- // }
138
- return requires;
312
+ const requires = this.flags.require ?? [];
313
+ const imports = this.flags.import ?? [];
314
+ let eggRequires = this.pkgEgg.require as string[] ?? [];
315
+ if (typeof eggRequires === 'string') {
316
+ eggRequires = [ eggRequires ];
317
+ }
318
+ let eggImports = this.pkgEgg.import as string[] ?? [];
319
+ if (typeof eggImports === 'string') {
320
+ eggImports = [ eggImports ];
321
+ }
322
+ return [
323
+ ...requires,
324
+ ...imports,
325
+ ...eggRequires,
326
+ ...eggImports,
327
+ ];
139
328
  }
140
329
 
141
- protected async forkNode(modulePath: string, forkArgs: string[], options: ForkOptions = {}) {
142
- const { args } = this;
143
- if (args.dryRun) {
144
- console.log('dry run: $ %o', `${process.execPath} ${modulePath} ${args.join(' ')}`);
145
- return;
330
+ protected formatImportModule(modulePath: string) {
331
+ if (this.isESM) {
332
+ return `--import ${pathToFileURL(modulePath).href}`;
146
333
  }
334
+ return `--require ${modulePath}`;
335
+ }
336
+
337
+ protected addNodeOptions(options: string) {
338
+ if (this.env.NODE_OPTIONS) {
339
+ if (!this.env.NODE_OPTIONS.includes(options)) {
340
+ this.env.NODE_OPTIONS = `${this.env.NODE_OPTIONS} ${options}`;
341
+ }
342
+ } else {
343
+ this.env.NODE_OPTIONS = options;
344
+ }
345
+ }
346
+
347
+ protected async forkNode(modulePath: string, forkArgs: string[], options: ForkNodeOptions = {}) {
348
+ const env = {
349
+ ...this.env,
350
+ ...options.env,
351
+ };
147
352
  const forkExecArgv = [
148
- // ...this.ctx.args.execArgv || [],
353
+ ...this.globalExecArgv,
149
354
  ...options.execArgv || [],
150
355
  ];
356
+ const NODE_OPTIONS = env.NODE_OPTIONS ? `NODE_OPTIONS='${env.NODE_OPTIONS}' ` : '';
357
+ const forkExecArgvString = forkExecArgv.length ? ' ' + forkExecArgv.join(' ') + ' ' : ' ';
358
+ const forkArgsString = forkArgs.map(a => `'${a}'`).join(' ');
359
+ const fullCommand = `${NODE_OPTIONS}${process.execPath}${forkExecArgvString}${modulePath} ${forkArgsString}`;
360
+ if (options.dryRun) {
361
+ console.log('dry run: $ %s', fullCommand);
362
+ return;
363
+ }
151
364
 
152
365
  options = {
153
366
  stdio: 'inherit',
154
- env: this.env,
155
- cwd: args.base,
367
+ env,
368
+ cwd: this.flags.base,
156
369
  ...options,
157
370
  execArgv: forkExecArgv,
158
371
  };
159
372
  const proc = fork(modulePath, forkArgs, options);
160
- debug('Run fork pid: %o\n\n$ %s%s %s %s\n\n',
373
+ debug('Run fork pid: %o\n\n$ %s\n\n',
161
374
  proc.pid,
162
- options.env?.NODE_OPTIONS ? `NODE_OPTIONS='${options.env.NODE_OPTIONS}' ` : '',
163
- process.execPath,
164
- modulePath, forkArgs.map(a => `'${a}'`).join(' '));
375
+ fullCommand);
165
376
  graceful(proc);
166
377
 
167
378
  return new Promise<void>((resolve, reject) => {
@@ -1,30 +1,100 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { Flags } from '@oclif/core';
4
+ import Test from './test.js';
5
+ import { importResolve } from '@eggjs/utils';
6
+ import { ForkNodeOptions } from '../baseCommand.js';
2
7
 
3
- export default class Cov extends Command {
4
- static override args = {
5
- file: Args.string({ description: 'file to read' }),
6
- };
7
-
8
- static override description = 'describe the command here';
8
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
9
+ // @ts-ignore
10
+ export default class Cov<T extends typeof Cov> extends Test<T> {
11
+ static override description = 'Run the test with coverage';
9
12
 
10
13
  static override examples = [
11
14
  '<%= config.bin %> <%= command.id %>',
15
+ '<%= config.bin %> <%= command.id %> test/index.test.ts',
12
16
  ];
13
17
 
14
18
  static override flags = {
15
- // flag with no value (-f, --force)
16
- force: Flags.boolean({ char: 'f' }),
17
- // flag with a value (-n, --name=VALUE)
18
- name: Flags.string({ char: 'n', description: 'name to print' }),
19
+ ...Test.flags,
20
+ // will use on egg-mock https://github.com/eggjs/egg-mock/blob/84a64bd19d0569ec94664c898fb1b28367b95d60/index.js#L7
21
+ prerequire: Flags.boolean({
22
+ description: 'prerequire files for coverage instrument',
23
+ }),
24
+ exclude: Flags.string({
25
+ description: 'coverage ignore, one or more files patterns`',
26
+ multiple: true,
27
+ char: 'x',
28
+ }),
29
+ c8: Flags.string({
30
+ description: 'c8 instruments passthrough`',
31
+ default: '--temp-directory node_modules/.c8_output -r text-summary -r json-summary -r json -r lcov -r cobertura',
32
+ }),
19
33
  };
20
34
 
21
- public async run(): Promise<void> {
22
- const { args, flags } = await this.parse(Cov);
35
+ protected get defaultExcludes() {
36
+ return [
37
+ 'example/',
38
+ 'examples/',
39
+ 'mocks**/',
40
+ 'docs/',
41
+ // https://github.com/JaKXz/test-exclude/blob/620a7be412d4fc2070d50f0f63e3228314066fc9/index.js#L73
42
+ 'test/**',
43
+ 'test{,-*}.js',
44
+ '**/*.test.js',
45
+ '**/__tests__/**',
46
+ '**/node_modules/**',
47
+ 'typings',
48
+ '**/*.d.ts',
49
+ ];
50
+ }
51
+
52
+ protected override async forkNode(modulePath: string, forkArgs: string[], options: ForkNodeOptions = {}) {
53
+ const { flags } = this;
54
+ if (flags.prerequire) {
55
+ this.env.EGG_BIN_PREREQUIRE = 'true';
56
+ }
23
57
 
24
- const name = flags.name ?? 'world';
25
- this.log(`hello ${name} from /Users/fengmk2/git/github.com/eggjs/bin/src/commands/cov.ts`);
26
- if (args.file && flags.force) {
27
- this.log(`you input --force and --file: ${args.file}`);
58
+ // add c8 args
59
+ // https://github.com/eggjs/egg/issues/3930
60
+ const c8Args = [
61
+ // '--show-process-tree',
62
+ ...flags.c8.split(' ').filter(a => a.trim()),
63
+ ];
64
+ if (flags.typescript) {
65
+ this.env.SPAWN_WRAP_SHIM_ROOT = path.join(flags.base, 'node_modules');
66
+ c8Args.push('--extension');
67
+ c8Args.push('.ts');
28
68
  }
69
+
70
+ const excludes = new Set([
71
+ ...process.env.COV_EXCLUDES?.split(',') ?? [],
72
+ ...this.defaultExcludes,
73
+ ...Array.from(flags.exclude ?? []),
74
+ ]);
75
+ for (const exclude of excludes) {
76
+ c8Args.push('-x');
77
+ c8Args.push(exclude);
78
+ }
79
+ const c8File = importResolve('c8/bin/c8.js');
80
+ const outputDir = path.join(flags.base, 'node_modules/.c8_output');
81
+ await fs.rm(outputDir, { force: true, recursive: true });
82
+ const coverageDir = path.join(flags.base, 'coverage');
83
+ await fs.rm(coverageDir, { force: true, recursive: true });
84
+
85
+ const execArgv = [
86
+ ...this.globalExecArgv,
87
+ ...options.execArgv || [],
88
+ ];
89
+ this.globalExecArgv = [];
90
+
91
+ // $ c8 node mocha
92
+ await super.forkNode(c8File, [
93
+ ...c8Args,
94
+ process.execPath,
95
+ ...execArgv,
96
+ modulePath,
97
+ ...forkArgs,
98
+ ]);
29
99
  }
30
100
  }
@@ -1,30 +1,99 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import { debuglog } from 'node:util';
2
+ import { Flags } from '@oclif/core';
3
+ import { getConfig, getFrameworkPath } from '@eggjs/utils';
4
+ import { detect } from 'detect-port';
5
+ import { getSourceFilename } from '../utils.js';
6
+ import { BaseCommand } from '../baseCommand.js';
2
7
 
3
- export default class Dev extends Command {
4
- static override args = {
5
- file: Args.string({ description: 'file to read' }),
6
- };
8
+ const debug = debuglog('@eggjs/bin/commands/dev');
7
9
 
8
- static override description = 'describe the command here';
10
+ export default class Dev<T extends typeof Dev> extends BaseCommand<T> {
11
+ static override description = 'Start server at local dev mode';
9
12
 
10
13
  static override examples = [
11
14
  '<%= config.bin %> <%= command.id %>',
12
15
  ];
13
16
 
14
17
  static override flags = {
15
- // flag with no value (-f, --force)
16
- force: Flags.boolean({ char: 'f' }),
17
- // flag with a value (-n, --name=VALUE)
18
- name: Flags.string({ char: 'n', description: 'name to print' }),
18
+ port: Flags.integer({
19
+ description: 'listening port, default to 7001',
20
+ char: 'p',
21
+ }),
22
+ workers: Flags.integer({
23
+ char: 'c',
24
+ aliases: [ 'cluster' ],
25
+ description: 'numbers of app workers',
26
+ default: 1,
27
+ }),
28
+ framework: Flags.string({
29
+ description: 'specify framework that can be absolute path or npm package, default is "egg"',
30
+ }),
31
+ sticky: Flags.boolean({
32
+ description: 'start a sticky cluster server',
33
+ }),
19
34
  };
20
35
 
21
36
  public async run(): Promise<void> {
22
- const { args, flags } = await this.parse(Dev);
37
+ debug('NODE_ENV: %o', this.env);
38
+ this.env.NODE_ENV = this.env.NODE_ENV ?? 'development';
39
+ this.env.EGG_MASTER_CLOSE_TIMEOUT = '1000';
40
+ const ext = this.isESM ? 'mjs' : 'cjs';
41
+ const serverBin = getSourceFilename(`../scripts/start-cluster.${ext}`);
42
+ const eggStartOptions = await this.formatEggStartOptions();
43
+ const args = [ JSON.stringify(eggStartOptions) ];
44
+ const requires = await this.formatRequires();
45
+ const execArgv: string[] = [];
46
+ for (const r of requires) {
47
+ const imports = this.formatImportModule(r).split(' ');
48
+ execArgv.push(...imports);
49
+ }
50
+ await this.forkNode(serverBin, args, { execArgv });
51
+ }
23
52
 
24
- const name = flags.name ?? 'world';
25
- this.log(`hello ${name} from /Users/fengmk2/git/github.com/eggjs/bin/src/commands/dev.ts`);
26
- if (args.file && flags.force) {
27
- this.log(`you input --force and --file: ${args.file}`);
53
+ protected async formatEggStartOptions() {
54
+ const { flags } = this;
55
+ flags.framework = getFrameworkPath({
56
+ framework: flags.framework,
57
+ baseDir: flags.base,
58
+ });
59
+
60
+ if (!flags.port) {
61
+ let configuredPort: number | undefined;
62
+ try {
63
+ const configuration = await getConfig({
64
+ framework: flags.framework,
65
+ baseDir: flags.base,
66
+ env: 'local',
67
+ });
68
+ configuredPort = configuration?.cluster?.listen?.port;
69
+ } catch (err) {
70
+ /** skip when failing to read the configuration */
71
+ debug('getConfig error: %s, framework: %o, baseDir: %o, env: local',
72
+ err, flags.framework, flags.base);
73
+ }
74
+ if (configuredPort) {
75
+ flags.port = configuredPort;
76
+ debug(`use port ${flags.port} from configuration file`);
77
+ } else {
78
+ const defaultPort = parseInt(process.env.EGG_BIN_DEFAULT_PORT ?? '7001');
79
+ debug('detect available port');
80
+ flags.port = await detect(defaultPort);
81
+ if (flags.port !== defaultPort) {
82
+ console.warn('[@eggjs/bin] server port %o is unavailable, now using port %o',
83
+ defaultPort, flags.port);
84
+ }
85
+ debug(`use available port ${flags.port}`);
86
+ }
28
87
  }
88
+
89
+ return {
90
+ baseDir: flags.base,
91
+ workers: flags.workers,
92
+ port: flags.port,
93
+ framework: flags.framework,
94
+ typescript: flags.typescript,
95
+ tscompiler: flags.tscompiler,
96
+ sticky: flags.sticky,
97
+ };
29
98
  }
30
99
  }