@eggjs/scripts 3.1.0 → 5.0.0-beta.27

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 (49) hide show
  1. package/README.md +2 -6
  2. package/bin/dev.js +0 -0
  3. package/dist/baseCommand.d.ts +28 -0
  4. package/dist/baseCommand.js +47 -0
  5. package/dist/commands/start.d.ts +39 -0
  6. package/dist/commands/start.js +277 -0
  7. package/dist/commands/stop.d.ts +21 -0
  8. package/dist/commands/stop.js +68 -0
  9. package/dist/helper.d.ts +11 -0
  10. package/dist/helper.js +35 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +4 -0
  13. package/{src/types.ts → dist/types.d.ts} +4 -1
  14. package/dist/types.js +1 -0
  15. package/package.json +66 -86
  16. package/scripts/start-cluster.cjs +11 -4
  17. package/scripts/start-cluster.mjs +6 -2
  18. package/dist/commonjs/baseCommand.d.ts +0 -25
  19. package/dist/commonjs/baseCommand.js +0 -62
  20. package/dist/commonjs/commands/start.d.ts +0 -34
  21. package/dist/commonjs/commands/start.js +0 -350
  22. package/dist/commonjs/commands/stop.d.ts +0 -16
  23. package/dist/commonjs/commands/stop.js +0 -90
  24. package/dist/commonjs/helper.d.ts +0 -10
  25. package/dist/commonjs/helper.js +0 -61
  26. package/dist/commonjs/index.d.ts +0 -3
  27. package/dist/commonjs/index.js +0 -26
  28. package/dist/commonjs/package.json +0 -3
  29. package/dist/commonjs/types.d.ts +0 -9
  30. package/dist/commonjs/types.js +0 -3
  31. package/dist/esm/baseCommand.d.ts +0 -25
  32. package/dist/esm/baseCommand.js +0 -55
  33. package/dist/esm/commands/start.d.ts +0 -34
  34. package/dist/esm/commands/start.js +0 -344
  35. package/dist/esm/commands/stop.d.ts +0 -16
  36. package/dist/esm/commands/stop.js +0 -87
  37. package/dist/esm/helper.d.ts +0 -10
  38. package/dist/esm/helper.js +0 -51
  39. package/dist/esm/index.d.ts +0 -3
  40. package/dist/esm/index.js +0 -5
  41. package/dist/esm/package.json +0 -3
  42. package/dist/esm/types.d.ts +0 -9
  43. package/dist/esm/types.js +0 -2
  44. package/dist/package.json +0 -4
  45. package/src/baseCommand.ts +0 -68
  46. package/src/commands/start.ts +0 -384
  47. package/src/commands/stop.ts +0 -100
  48. package/src/helper.ts +0 -62
  49. package/src/index.ts +0 -8
package/README.md CHANGED
@@ -1,16 +1,12 @@
1
1
  # @eggjs/scripts
2
2
 
3
3
  [![NPM version][npm-image]][npm-url]
4
- [![Node.js CI](https://github.com/eggjs/scripts/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/scripts/actions/workflows/nodejs.yml)
5
- [![Test coverage][codecov-image]][codecov-url]
6
4
  [![npm download][download-image]][download-url]
7
5
  [![Node.js Version](https://img.shields.io/node/v/@eggjs/scripts.svg?style=flat)](https://nodejs.org/en/download/)
8
6
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
9
7
 
10
8
  [npm-image]: https://img.shields.io/npm/v/@eggjs/scripts.svg?style=flat-square
11
9
  [npm-url]: https://npmjs.org/package/@eggjs/scripts
12
- [codecov-image]: https://codecov.io/github/eggjs/scripts/coverage.svg?branch=master
13
- [codecov-url]: https://codecov.io/github/eggjs/scripts?branch=master
14
10
  [download-image]: https://img.shields.io/npm/dm/@eggjs/scripts.svg?style=flat-square
15
11
  [download-url]: https://npmjs.org/package/@eggjs/scripts
16
12
 
@@ -63,7 +59,7 @@ $ eggctl start [options] [baseDir]
63
59
  - **Options**
64
60
  - `port` - listening port, default to `process.env.PORT`, if unset, egg will use `7001` as default.
65
61
  - `title` - process title description, use for kill grep, default to `egg-server-${APP_NAME}`.
66
- - `workers` - numbers of app workers, default to `process.env.EGG_WORKERS`, if unset, egg will use `os.cpus().length` as default.
62
+ - `workers` - numbers of app workers, default to `process.env.EGG_WORKERS`, if unset, egg will use `os.cpus().length` as default.
67
63
  - `daemon` - whether run at background daemon mode, don't use it if in docker mode.
68
64
  - `framework` - specify framework that can be absolute path or npm package, default to auto detect.
69
65
  - `env` - server env, default to `process.env.EGG_SERVER_ENV`, recommended to keep empty then use framwork default env.
@@ -118,6 +114,6 @@ Please open an issue [here](https://github.com/eggjs/egg/issues?q=is%3Aissue+is%
118
114
 
119
115
  ## Contributors
120
116
 
121
- [![Contributors](https://contrib.rocks/image?repo=eggjs/scripts)](https://github.com/eggjs/scripts/graphs/contributors)
117
+ [![Contributors](https://contrib.rocks/image?repo=eggjs/egg)](https://github.com/eggjs/egg/graphs/contributors)
122
118
 
123
119
  Made with [contributors-img](https://contrib.rocks).
package/bin/dev.js CHANGED
File without changes
@@ -0,0 +1,28 @@
1
+ import { PackageEgg } from "./types.js";
2
+ import { Command, Interfaces } from "@oclif/core";
3
+
4
+ //#region src/baseCommand.d.ts
5
+ type Flags$1<T extends typeof Command> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;
6
+ type Args$1<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
7
+ declare abstract class BaseCommand<T extends typeof Command> extends Command {
8
+ static enableJsonFlag: boolean;
9
+ static baseFlags: {};
10
+ protected flags: Flags$1<T>;
11
+ protected args: Args$1<T>;
12
+ protected env: {
13
+ [key: string]: string | undefined;
14
+ TZ?: string;
15
+ };
16
+ protected pkg: Record<string, any>;
17
+ protected isESM: boolean;
18
+ protected pkgEgg: PackageEgg;
19
+ protected globalExecArgv: string[];
20
+ init(): Promise<void>;
21
+ protected initBaseInfo(baseDir: string): Promise<void>;
22
+ protected catch(err: Error & {
23
+ exitCode?: number;
24
+ }): Promise<any>;
25
+ protected finally(_: Error | undefined): Promise<any>;
26
+ }
27
+ //#endregion
28
+ export { BaseCommand };
@@ -0,0 +1,47 @@
1
+ import { debuglog } from "node:util";
2
+ import path from "node:path";
3
+ import { Command, Interfaces } from "@oclif/core";
4
+ import { readJSON } from "utility";
5
+
6
+ //#region src/baseCommand.ts
7
+ const debug = debuglog("egg/scripts/baseCommand");
8
+ var BaseCommand = class extends Command {
9
+ static enableJsonFlag = false;
10
+ static baseFlags = {};
11
+ flags;
12
+ args;
13
+ env = { ...process.env };
14
+ pkg;
15
+ isESM;
16
+ pkgEgg;
17
+ globalExecArgv = [];
18
+ async init() {
19
+ await super.init();
20
+ debug("[init] raw args: %o, NODE_ENV: %o", this.argv, this.env.NODE_ENV);
21
+ const { args, flags } = await this.parse({
22
+ flags: this.ctor.flags,
23
+ baseFlags: super.ctor.baseFlags,
24
+ enableJsonFlag: this.ctor.enableJsonFlag,
25
+ args: this.ctor.args,
26
+ strict: this.ctor.strict
27
+ });
28
+ this.flags = flags;
29
+ this.args = args;
30
+ }
31
+ async initBaseInfo(baseDir) {
32
+ const pkg = await readJSON(path.join(baseDir, "package.json"));
33
+ this.pkg = pkg;
34
+ this.pkgEgg = pkg.egg ?? {};
35
+ this.isESM = pkg.type === "module";
36
+ debug("[initBaseInfo] baseDir: %o, pkgEgg: %o, isESM: %o", baseDir, this.pkgEgg, this.isESM);
37
+ }
38
+ async catch(err) {
39
+ return super.catch(err);
40
+ }
41
+ async finally(_) {
42
+ return super.finally(_);
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ export { BaseCommand };
@@ -0,0 +1,39 @@
1
+ import { BaseCommand } from "../baseCommand.js";
2
+ import * as _oclif_core_interfaces4 from "@oclif/core/interfaces";
3
+
4
+ //#region src/commands/start.d.ts
5
+ interface FrameworkOptions {
6
+ baseDir: string;
7
+ framework?: string;
8
+ }
9
+ declare class Start<T extends typeof Start> extends BaseCommand<T> {
10
+ #private;
11
+ static description: string;
12
+ static examples: string[];
13
+ static args: {
14
+ baseDir: _oclif_core_interfaces4.Arg<string | undefined, Record<string, unknown>>;
15
+ };
16
+ static flags: {
17
+ title: _oclif_core_interfaces4.OptionFlag<string | undefined, _oclif_core_interfaces4.CustomOptions>;
18
+ framework: _oclif_core_interfaces4.OptionFlag<string | undefined, _oclif_core_interfaces4.CustomOptions>;
19
+ port: _oclif_core_interfaces4.OptionFlag<number | undefined, _oclif_core_interfaces4.CustomOptions>;
20
+ workers: _oclif_core_interfaces4.OptionFlag<number | undefined, _oclif_core_interfaces4.CustomOptions>;
21
+ env: _oclif_core_interfaces4.OptionFlag<string, _oclif_core_interfaces4.CustomOptions>;
22
+ daemon: _oclif_core_interfaces4.BooleanFlag<boolean>;
23
+ stdout: _oclif_core_interfaces4.OptionFlag<string | undefined, _oclif_core_interfaces4.CustomOptions>;
24
+ stderr: _oclif_core_interfaces4.OptionFlag<string | undefined, _oclif_core_interfaces4.CustomOptions>;
25
+ timeout: _oclif_core_interfaces4.OptionFlag<number, _oclif_core_interfaces4.CustomOptions>;
26
+ 'ignore-stderr': _oclif_core_interfaces4.BooleanFlag<boolean>;
27
+ node: _oclif_core_interfaces4.OptionFlag<string, _oclif_core_interfaces4.CustomOptions>;
28
+ require: _oclif_core_interfaces4.OptionFlag<string[] | undefined, _oclif_core_interfaces4.CustomOptions>;
29
+ sourcemap: _oclif_core_interfaces4.BooleanFlag<boolean>;
30
+ };
31
+ isReady: boolean;
32
+ protected getFrameworkPath(options: FrameworkOptions): Promise<string>;
33
+ protected getFrameworkName(frameworkPath: string): Promise<string>;
34
+ protected getServerBin(): Promise<string>;
35
+ run(): Promise<void>;
36
+ protected checkStatus(): Promise<void>;
37
+ }
38
+ //#endregion
39
+ export { FrameworkOptions, Start as default };
@@ -0,0 +1,277 @@
1
+ import { BaseCommand } from "../baseCommand.js";
2
+ import { debuglog, promisify } from "node:util";
3
+ import path from "node:path";
4
+ import { Args, Flags } from "@oclif/core";
5
+ import { exists, getDateStringParts, readJSON } from "utility";
6
+ import { scheduler } from "node:timers/promises";
7
+ import { execFile, spawn } from "node:child_process";
8
+ import { mkdir, open, rename, stat } from "node:fs/promises";
9
+ import { homedir } from "node-homedir";
10
+ import { getFrameworkPath, importResolve } from "@eggjs/utils";
11
+
12
+ //#region src/commands/start.ts
13
+ const debug = debuglog("egg/scripts/commands/start");
14
+ const execFile$1 = promisify(execFile);
15
+ var Start = class extends BaseCommand {
16
+ static description = "Start server at prod mode";
17
+ static examples = ["<%= config.bin %> <%= command.id %>"];
18
+ static args = { baseDir: Args.string({
19
+ description: "directory of application",
20
+ required: false
21
+ }) };
22
+ static flags = {
23
+ title: Flags.string({ description: "process title description, use for kill grep, default to `egg-server-${APP_NAME}`" }),
24
+ framework: Flags.string({ description: "specify framework that can be absolute path or npm package" }),
25
+ port: Flags.integer({
26
+ description: "listening port, default to `process.env.PORT`",
27
+ char: "p"
28
+ }),
29
+ workers: Flags.integer({
30
+ char: "c",
31
+ aliases: ["cluster"],
32
+ description: "numbers of app workers, default to `process.env.EGG_WORKERS` or `os.cpus().length`"
33
+ }),
34
+ env: Flags.string({
35
+ description: "server env, default to `process.env.EGG_SERVER_ENV`",
36
+ default: process.env.EGG_SERVER_ENV
37
+ }),
38
+ daemon: Flags.boolean({ description: "whether run at background daemon mode" }),
39
+ stdout: Flags.string({ description: "customize stdout file" }),
40
+ stderr: Flags.string({ description: "customize stderr file" }),
41
+ timeout: Flags.integer({
42
+ description: "the maximum timeout(ms) when app starts",
43
+ default: 300 * 1e3
44
+ }),
45
+ "ignore-stderr": Flags.boolean({ description: "whether ignore stderr when app starts" }),
46
+ node: Flags.string({
47
+ description: "customize node command path",
48
+ default: "node"
49
+ }),
50
+ require: Flags.string({
51
+ summary: "require the given module",
52
+ char: "r",
53
+ multiple: true
54
+ }),
55
+ sourcemap: Flags.boolean({
56
+ summary: "whether enable sourcemap support, will load `source-map-support` etc",
57
+ aliases: ["ts", "typescript"]
58
+ })
59
+ };
60
+ isReady = false;
61
+ #child;
62
+ async getFrameworkPath(options) {
63
+ return getFrameworkPath(options);
64
+ }
65
+ async getFrameworkName(frameworkPath) {
66
+ const pkgPath = path.join(frameworkPath, "package.json");
67
+ let name = "egg";
68
+ try {
69
+ const pkg = await readJSON(pkgPath);
70
+ if (pkg.name) name = pkg.name;
71
+ } catch {}
72
+ return name;
73
+ }
74
+ async getServerBin() {
75
+ const serverBinName = this.isESM ? "start-cluster.mjs" : "start-cluster.cjs";
76
+ return path.join(import.meta.dirname, "../../scripts", serverBinName);
77
+ }
78
+ async run() {
79
+ const { args, flags } = this;
80
+ const HOME = homedir();
81
+ const logDir = path.join(HOME, "logs");
82
+ const cwd = process.cwd();
83
+ let baseDir = args.baseDir || cwd;
84
+ if (!path.isAbsolute(baseDir)) baseDir = path.join(cwd, baseDir);
85
+ await this.initBaseInfo(baseDir);
86
+ flags.framework = await this.getFrameworkPath({
87
+ framework: flags.framework,
88
+ baseDir
89
+ });
90
+ const frameworkName = await this.getFrameworkName(flags.framework);
91
+ flags.title = flags.title || `egg-server-${this.pkg.name}`;
92
+ flags.stdout = flags.stdout || path.join(logDir, "master-stdout.log");
93
+ flags.stderr = flags.stderr || path.join(logDir, "master-stderr.log");
94
+ if (flags.workers === void 0 && process.env.EGG_WORKERS) flags.workers = Number(process.env.EGG_WORKERS);
95
+ this.env.HOME = HOME;
96
+ this.env.NODE_ENV = "production";
97
+ this.env.PATH = this.env.Path = [
98
+ path.join(baseDir, "node_modules/.bin"),
99
+ path.join(baseDir, ".node/bin"),
100
+ this.env.PATH || this.env.Path
101
+ ].filter((x) => !!x).join(path.delimiter);
102
+ this.env.ENABLE_NODE_LOG = "YES";
103
+ this.env.NODE_LOG_DIR = this.env.NODE_LOG_DIR || path.join(logDir, "alinode");
104
+ await mkdir(this.env.NODE_LOG_DIR, { recursive: true });
105
+ if (flags.env) this.env.EGG_SERVER_ENV = flags.env;
106
+ const execArgv = ["--no-deprecation", "--trace-warnings"];
107
+ if (this.pkgEgg.revert) {
108
+ const reverts = Array.isArray(this.pkgEgg.revert) ? this.pkgEgg.revert : [this.pkgEgg.revert];
109
+ for (const revert of reverts) execArgv.push(`--security-revert=${revert}`);
110
+ }
111
+ const scriptsConfig = this.pkg.eggScriptsConfig;
112
+ if (scriptsConfig?.require) {
113
+ scriptsConfig.require = Array.isArray(scriptsConfig.require) ? scriptsConfig.require : [scriptsConfig.require];
114
+ flags.require = [...scriptsConfig.require, ...flags.require ?? []];
115
+ }
116
+ if (scriptsConfig) for (const key in scriptsConfig) {
117
+ const v = scriptsConfig[key];
118
+ if (key.startsWith("node-options--")) {
119
+ const newKey = key.replace("node-options--", "");
120
+ if (v === true) execArgv.push(`--${newKey}`);
121
+ else execArgv.push(`--${newKey}=${v}`);
122
+ continue;
123
+ }
124
+ if (Reflect.get(flags, key) === void 0) Reflect.set(flags, key, v);
125
+ }
126
+ if (this.pkgEgg.typescript && flags.sourcemap === void 0) flags.sourcemap = true;
127
+ if (flags.sourcemap) {
128
+ const sourceMapSupportPkgPath = importResolve("source-map-support/package.json", { paths: [import.meta.dirname] });
129
+ const sourceMapSupport = path.join(path.dirname(sourceMapSupportPkgPath), "register.js");
130
+ if (this.isESM) execArgv.push("--import", sourceMapSupport);
131
+ else execArgv.push("--require", sourceMapSupport);
132
+ }
133
+ if (flags.port === void 0 && process.env.PORT) flags.port = parseInt(process.env.PORT);
134
+ debug("flags: %o, framework: %o, baseDir: %o, execArgv: %o", flags, frameworkName, baseDir, execArgv);
135
+ const command = flags.node;
136
+ const options = {
137
+ env: this.env,
138
+ stdio: "inherit",
139
+ detached: false,
140
+ cwd: baseDir
141
+ };
142
+ this.log("Starting %s application at %s", frameworkName, baseDir);
143
+ const ignoreKeys = [
144
+ "env",
145
+ "daemon",
146
+ "stdout",
147
+ "stderr",
148
+ "timeout",
149
+ "ignore-stderr",
150
+ "node"
151
+ ];
152
+ const clusterOptions = stringify({
153
+ ...flags,
154
+ baseDir
155
+ }, ignoreKeys);
156
+ const serverBin = await this.getServerBin();
157
+ const eggArgs = [
158
+ ...execArgv,
159
+ serverBin,
160
+ clusterOptions,
161
+ `--title=${flags.title}`
162
+ ];
163
+ const spawnScript = `${command} ${eggArgs.map((a) => `'${a}'`).join(" ")}`;
164
+ this.log("Spawn %o", spawnScript);
165
+ if (flags.daemon) {
166
+ this.log(`Save log file to ${logDir}`);
167
+ const [stdout, stderr] = await Promise.all([getRotateLog(flags.stdout), getRotateLog(flags.stderr)]);
168
+ options.stdio = [
169
+ "ignore",
170
+ stdout,
171
+ stderr,
172
+ "ipc"
173
+ ];
174
+ options.detached = true;
175
+ const child = this.#child = spawn(command, eggArgs, options);
176
+ this.isReady = false;
177
+ child.on("message", (msg) => {
178
+ if (msg && msg.action === "egg-ready") {
179
+ this.isReady = true;
180
+ this.log("%s started on %s", frameworkName, msg.data.address);
181
+ child.unref();
182
+ child.disconnect();
183
+ }
184
+ });
185
+ await this.checkStatus();
186
+ } else {
187
+ options.stdio = [
188
+ "inherit",
189
+ "inherit",
190
+ "inherit",
191
+ "ipc"
192
+ ];
193
+ const child = this.#child = spawn(command, eggArgs, options);
194
+ child.once("exit", (code) => {
195
+ if (!code) return;
196
+ this.exit(code);
197
+ });
198
+ [
199
+ "SIGINT",
200
+ "SIGQUIT",
201
+ "SIGTERM"
202
+ ].forEach((event) => {
203
+ process.once(event, () => {
204
+ debug("Kill child %s with %s", child.pid, event);
205
+ child.kill(event);
206
+ });
207
+ });
208
+ }
209
+ }
210
+ async checkStatus() {
211
+ let count = 0;
212
+ let hasError = false;
213
+ let isSuccess = true;
214
+ const timeout = this.flags.timeout / 1e3;
215
+ const stderrFile = this.flags.stderr;
216
+ while (!this.isReady) {
217
+ try {
218
+ const stats = await stat(stderrFile);
219
+ if (stats && stats.size > 0) {
220
+ hasError = true;
221
+ break;
222
+ }
223
+ } catch {}
224
+ if (count >= timeout) {
225
+ this.logToStderr("Start failed, %ds timeout", timeout);
226
+ isSuccess = false;
227
+ break;
228
+ }
229
+ await scheduler.wait(1e3);
230
+ this.log("Wait Start: %d...", ++count);
231
+ }
232
+ if (hasError) {
233
+ try {
234
+ const args = [
235
+ "-n",
236
+ "100",
237
+ stderrFile
238
+ ];
239
+ this.logToStderr("tail %s", args.join(" "));
240
+ const { stdout: headStdout } = await execFile$1("head", args);
241
+ const { stdout: tailStdout } = await execFile$1("tail", args);
242
+ this.logToStderr("Got error when startup: ");
243
+ this.logToStderr(headStdout);
244
+ this.logToStderr("...");
245
+ this.logToStderr(tailStdout);
246
+ } catch (err) {
247
+ this.logToStderr("ignore tail error: %s", err);
248
+ }
249
+ isSuccess = this.flags["ignore-stderr"];
250
+ this.logToStderr("Start got error, see %o", stderrFile);
251
+ this.logToStderr("Or use `--ignore-stderr` to ignore stderr at startup.");
252
+ }
253
+ if (!isSuccess) {
254
+ this.#child.kill("SIGTERM");
255
+ await scheduler.wait(1e3);
256
+ this.exit(1);
257
+ }
258
+ }
259
+ };
260
+ function stringify(obj, ignore) {
261
+ const result = {};
262
+ Object.keys(obj).forEach((key) => {
263
+ if (!ignore.includes(key)) result[key] = obj[key];
264
+ });
265
+ return JSON.stringify(result);
266
+ }
267
+ async function getRotateLog(logFile) {
268
+ await mkdir(path.dirname(logFile), { recursive: true });
269
+ if (await exists(logFile)) {
270
+ const [YYYY, MM, DD, HH, mm, ss] = getDateStringParts();
271
+ await rename(logFile, logFile + `.${YYYY}${MM}${DD}.${HH}${mm}${ss}`);
272
+ }
273
+ return (await open(logFile, "a")).fd;
274
+ }
275
+
276
+ //#endregion
277
+ export { Start as default };
@@ -0,0 +1,21 @@
1
+ import { BaseCommand } from "../baseCommand.js";
2
+ import { NodeProcess } from "../helper.js";
3
+ import * as _oclif_core_interfaces0 from "@oclif/core/interfaces";
4
+
5
+ //#region src/commands/stop.d.ts
6
+ declare class Stop<T extends typeof Stop> extends BaseCommand<T> {
7
+ static description: string;
8
+ static examples: string[];
9
+ static args: {
10
+ baseDir: _oclif_core_interfaces0.Arg<string | undefined, Record<string, unknown>>;
11
+ };
12
+ static flags: {
13
+ title: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
14
+ timeout: _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
15
+ };
16
+ run(): Promise<void>;
17
+ protected findNodeProcesses(filter: (item: NodeProcess) => boolean): Promise<NodeProcess[]>;
18
+ protected killProcesses(pids: number[], signal?: NodeJS.Signals): void;
19
+ }
20
+ //#endregion
21
+ export { Stop as default };
@@ -0,0 +1,68 @@
1
+ import { BaseCommand } from "../baseCommand.js";
2
+ import { findNodeProcess, isWindows, kill } from "../helper.js";
3
+ import { debuglog, format } from "node:util";
4
+ import { Args, Flags } from "@oclif/core";
5
+ import { scheduler } from "node:timers/promises";
6
+
7
+ //#region src/commands/stop.ts
8
+ const debug = debuglog("egg/scripts/commands/stop");
9
+ const osRelated = {
10
+ titleTemplate: isWindows ? "\\\"title\\\":\\\"%s\\\"" : "\"title\":\"%s\"",
11
+ appWorkerPath: /@eggjs[/\\]cluster[/\\]dist[/\\](commonjs|esm)[/\\]app_worker\.js/i,
12
+ agentWorkerPath: /@eggjs[/\\]cluster[/\\]dist[/\\](commonjs|esm)[/\\]agent_worker\.js/i
13
+ };
14
+ var Stop = class extends BaseCommand {
15
+ static description = "Stop server";
16
+ static examples = ["<%= config.bin %> <%= command.id %>"];
17
+ static args = { baseDir: Args.string({
18
+ description: "directory of application",
19
+ required: false
20
+ }) };
21
+ static flags = {
22
+ title: Flags.string({ description: "process title description, use for kill grep" }),
23
+ timeout: Flags.integer({
24
+ description: "the maximum timeout(ms) when app stop",
25
+ default: 5e3
26
+ })
27
+ };
28
+ async run() {
29
+ const { flags } = this;
30
+ this.log(`stopping egg application${flags.title ? ` with --title=${flags.title}` : ""}`);
31
+ let processList = await this.findNodeProcesses((item) => {
32
+ const cmd = item.cmd;
33
+ const matched = flags.title ? cmd.includes("start-cluster") && cmd.includes(format(osRelated.titleTemplate, flags.title)) : cmd.includes("start-cluster");
34
+ if (matched) debug("find master process: %o", item);
35
+ return matched;
36
+ });
37
+ let pids = processList.map((x) => x.pid);
38
+ if (pids.length) {
39
+ this.log("got master pid %j, list:", pids);
40
+ this.log("");
41
+ for (const item of processList) this.log("- %s: %o", item.pid, item.cmd);
42
+ this.log("");
43
+ this.killProcesses(pids);
44
+ await scheduler.wait(flags.timeout);
45
+ } else this.logToStderr("can't detect any running egg process");
46
+ processList = await this.findNodeProcesses((item) => {
47
+ const cmd = item.cmd;
48
+ const matched = flags.title ? (osRelated.appWorkerPath.test(cmd) || osRelated.agentWorkerPath.test(cmd)) && cmd.includes(format(osRelated.titleTemplate, flags.title)) : osRelated.appWorkerPath.test(cmd) || osRelated.agentWorkerPath.test(cmd);
49
+ if (matched) debug("find app/agent worker process: %o", item);
50
+ return matched;
51
+ });
52
+ pids = processList.map((x) => x.pid);
53
+ if (pids.length) {
54
+ this.log("got worker/agent pids %j that is not killed by master", pids);
55
+ this.killProcesses(pids);
56
+ }
57
+ this.log("stopped");
58
+ }
59
+ async findNodeProcesses(filter) {
60
+ return findNodeProcess(filter);
61
+ }
62
+ killProcesses(pids, signal = "SIGTERM") {
63
+ kill(pids, signal);
64
+ }
65
+ };
66
+
67
+ //#endregion
68
+ export { Stop as default };
@@ -0,0 +1,11 @@
1
+ //#region src/helper.d.ts
2
+ declare const isWindows: boolean;
3
+ interface NodeProcess {
4
+ pid: number;
5
+ cmd: string;
6
+ }
7
+ type FilterFunction = (item: NodeProcess) => boolean;
8
+ declare function findNodeProcess(filterFn?: FilterFunction): Promise<NodeProcess[]>;
9
+ declare function kill(pids: number[], signal?: string | number): void;
10
+ //#endregion
11
+ export { FilterFunction, NodeProcess, findNodeProcess, isWindows, kill };
package/dist/helper.js ADDED
@@ -0,0 +1,35 @@
1
+ import { runScript } from "runscript";
2
+
3
+ //#region src/helper.ts
4
+ const isWindows = process.platform === "win32";
5
+ const REGEX = isWindows ? /^(.*)\s+(\d+)\s*$/ : /^\s*(\d+)\s+(.*)/;
6
+ async function findNodeProcess(filterFn) {
7
+ return (await runScript(isWindows ? "wmic Path win32_process Where \"Name = 'node.exe'\" Get CommandLine,ProcessId" : "ps -wweo \"pid,args\"", { stdio: "pipe" })).stdout.toString().split("\n").reduce((arr, line) => {
8
+ if (!!line && !line.includes("/bin/sh") && line.includes("node")) {
9
+ const m = line.match(REGEX);
10
+ if (m) {
11
+ const item = isWindows ? {
12
+ pid: parseInt(m[2]),
13
+ cmd: m[1]
14
+ } : {
15
+ pid: parseInt(m[1]),
16
+ cmd: m[2]
17
+ };
18
+ if (filterFn?.(item)) arr.push(item);
19
+ }
20
+ }
21
+ return arr;
22
+ }, []);
23
+ }
24
+ function kill(pids, signal) {
25
+ pids.forEach((pid) => {
26
+ try {
27
+ process.kill(pid, signal);
28
+ } catch (err) {
29
+ if (err.code !== "ESRCH") throw err;
30
+ }
31
+ });
32
+ }
33
+
34
+ //#endregion
35
+ export { findNodeProcess, isWindows, kill };
@@ -0,0 +1,3 @@
1
+ import { BaseCommand } from "./baseCommand.js";
2
+ import Start from "./commands/start.js";
3
+ export { BaseCommand, Start, Start as StartCommand };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { BaseCommand } from "./baseCommand.js";
2
+ import Start from "./commands/start.js";
3
+
4
+ export { BaseCommand, Start, Start as StartCommand };
@@ -1,4 +1,5 @@
1
- export interface PackageEgg {
1
+ //#region src/types.d.ts
2
+ interface PackageEgg {
2
3
  framework?: boolean;
3
4
  typescript?: boolean;
4
5
  tscompiler?: string;
@@ -7,3 +8,5 @@ export interface PackageEgg {
7
8
  require?: string | string[];
8
9
  import?: string | string[];
9
10
  }
11
+ //#endregion
12
+ export { PackageEgg };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export { };