@eggjs/scripts 3.1.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.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commonjs/baseCommand.d.ts +25 -0
- package/dist/commonjs/baseCommand.js +62 -0
- package/dist/commonjs/commands/start.d.ts +34 -0
- package/dist/commonjs/commands/start.js +350 -0
- package/dist/commonjs/commands/stop.d.ts +16 -0
- package/dist/commonjs/commands/stop.js +90 -0
- package/dist/commonjs/helper.d.ts +10 -0
- package/dist/commonjs/helper.js +61 -0
- package/dist/commonjs/index.d.ts +3 -0
- package/dist/commonjs/index.js +26 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/types.d.ts +9 -0
- package/dist/commonjs/types.js +3 -0
- package/dist/esm/baseCommand.d.ts +25 -0
- package/dist/esm/baseCommand.js +55 -0
- package/dist/esm/commands/start.d.ts +34 -0
- package/dist/esm/commands/start.js +344 -0
- package/dist/esm/commands/stop.d.ts +16 -0
- package/dist/esm/commands/stop.js +87 -0
- package/dist/esm/helper.d.ts +10 -0
- package/dist/esm/helper.js +51 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/types.d.ts +9 -0
- package/dist/esm/types.js +2 -0
- package/dist/package.json +4 -0
- package/package.json +103 -0
- package/scripts/start-cluster.cjs +15 -0
- package/scripts/start-cluster.mjs +14 -0
- package/src/baseCommand.ts +68 -0
- package/src/commands/start.ts +384 -0
- package/src/commands/stop.ts +100 -0
- package/src/helper.ts +62 -0
- package/src/index.ts +8 -0
- package/src/types.ts +9 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import Start from './commands/start.js';
|
|
2
|
+
// exports.StopCommand = require('./lib/cmd/stop');
|
|
3
|
+
export * from './baseCommand.js';
|
|
4
|
+
export { Start, Start as StartCommand, };
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE1BQU0scUJBQXFCLENBQUM7QUFFeEMsbURBQW1EO0FBRW5ELGNBQWMsa0JBQWtCLENBQUM7QUFDakMsT0FBTyxFQUNMLEtBQUssRUFBRSxLQUFLLElBQUksWUFBWSxHQUM3QixDQUFDIn0=
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eggjs/scripts",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "deploy tool for egg project",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@eggjs/utils": "^4.2.1",
|
|
10
|
+
"@oclif/core": "^4.2.0",
|
|
11
|
+
"common-bin": "^3.0.1",
|
|
12
|
+
"mz": "^2.7.0",
|
|
13
|
+
"mz-modules": "^2.1.0",
|
|
14
|
+
"node-homedir": "^2.0.0",
|
|
15
|
+
"runscript": "^2.0.1",
|
|
16
|
+
"source-map-support": "^0.5.21",
|
|
17
|
+
"utility": "^2.4.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@arethetypeswrong/cli": "^0.17.1",
|
|
21
|
+
"@eggjs/bin": "^7.0.1",
|
|
22
|
+
"@eggjs/tsconfig": "1",
|
|
23
|
+
"@types/mocha": "10",
|
|
24
|
+
"@types/node": "22",
|
|
25
|
+
"coffee": "^5.5.1",
|
|
26
|
+
"egg": "beta",
|
|
27
|
+
"eslint": "8",
|
|
28
|
+
"eslint-config-egg": "14",
|
|
29
|
+
"mm": "^4.0.1",
|
|
30
|
+
"rimraf": "6",
|
|
31
|
+
"ts-node": "^10.9.2",
|
|
32
|
+
"tshy": "3",
|
|
33
|
+
"tshy-after": "1",
|
|
34
|
+
"typescript": "5",
|
|
35
|
+
"urllib": "4"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.19.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"lint": "eslint --cache src test --ext .ts",
|
|
42
|
+
"pretest": "npm run clean && npm run lint -- --fix && npm run prepublishOnly",
|
|
43
|
+
"test": "egg-bin test",
|
|
44
|
+
"posttest": "npm run clean",
|
|
45
|
+
"preci": "npm run clean && npm run lint && npm run prepublishOnly",
|
|
46
|
+
"ci": "egg-bin test",
|
|
47
|
+
"postci": "npm run clean",
|
|
48
|
+
"clean": "rimraf dist",
|
|
49
|
+
"prepublishOnly": "tshy && tshy-after && attw --pack"
|
|
50
|
+
},
|
|
51
|
+
"bug": {
|
|
52
|
+
"url": "https://github.com/eggjs/egg/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/eggjs/scripts",
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git@github.com:eggjs/scripts.git"
|
|
58
|
+
},
|
|
59
|
+
"author": "TZ <atian25@qq.com>",
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"oclif": {
|
|
62
|
+
"bin": "eggctl",
|
|
63
|
+
"commands": "./dist/esm/commands",
|
|
64
|
+
"dirname": "eggctl",
|
|
65
|
+
"topicSeparator": " ",
|
|
66
|
+
"additionalHelpFlags": [
|
|
67
|
+
"-h"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"bin": {
|
|
71
|
+
"egg-scripts": "./bin/run.js",
|
|
72
|
+
"eggctl": "./bin/run.js"
|
|
73
|
+
},
|
|
74
|
+
"type": "module",
|
|
75
|
+
"tshy": {
|
|
76
|
+
"exports": {
|
|
77
|
+
".": "./src/index.ts",
|
|
78
|
+
"./package.json": "./package.json"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"exports": {
|
|
82
|
+
".": {
|
|
83
|
+
"import": {
|
|
84
|
+
"types": "./dist/esm/index.d.ts",
|
|
85
|
+
"default": "./dist/esm/index.js"
|
|
86
|
+
},
|
|
87
|
+
"require": {
|
|
88
|
+
"types": "./dist/commonjs/index.d.ts",
|
|
89
|
+
"default": "./dist/commonjs/index.js"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"./package.json": "./package.json"
|
|
93
|
+
},
|
|
94
|
+
"files": [
|
|
95
|
+
"bin",
|
|
96
|
+
"dist",
|
|
97
|
+
"src",
|
|
98
|
+
"scripts"
|
|
99
|
+
],
|
|
100
|
+
"types": "./dist/commonjs/index.d.ts",
|
|
101
|
+
"main": "./dist/commonjs/index.js",
|
|
102
|
+
"module": "./dist/esm/index.js"
|
|
103
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
const { debuglog } = require('node:util');
|
|
3
|
+
const { importModule } = require('@eggjs/utils');
|
|
4
|
+
|
|
5
|
+
const debug = debuglog('@eggjs/scripts/scripts/start-cluster');
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
debug('argv: %o', process.argv);
|
|
9
|
+
const options = JSON.parse(process.argv[2]);
|
|
10
|
+
debug('start cluster options: %o', options);
|
|
11
|
+
const { startCluster } = await importModule(options.framework);
|
|
12
|
+
await startCluster(options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
main();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { debuglog } from 'node:util';
|
|
2
|
+
import { importModule } from '@eggjs/utils';
|
|
3
|
+
|
|
4
|
+
const debug = debuglog('@eggjs/scripts/scripts/start-cluster');
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
debug('argv: %o', process.argv);
|
|
8
|
+
const options = JSON.parse(process.argv[2]);
|
|
9
|
+
debug('start cluster options: %o', options);
|
|
10
|
+
const { startCluster } = await importModule(options.framework);
|
|
11
|
+
await startCluster(options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
main();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { debuglog } from 'node:util';
|
|
2
|
+
import { Command, Flags, Interfaces } from '@oclif/core';
|
|
3
|
+
import { PackageEgg } from './types.js';
|
|
4
|
+
import { readJSON } from 'utility';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const debug = debuglog('@eggjs/scripts/baseCommand');
|
|
8
|
+
|
|
9
|
+
type Flags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>;
|
|
10
|
+
type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
|
|
11
|
+
|
|
12
|
+
export abstract class BaseCommand<T extends typeof Command> extends Command {
|
|
13
|
+
// add the --json flag
|
|
14
|
+
static enableJsonFlag = false;
|
|
15
|
+
|
|
16
|
+
// define flags that can be inherited by any command that extends BaseCommand
|
|
17
|
+
static baseFlags = {
|
|
18
|
+
// 'log-level': Flags.option({
|
|
19
|
+
// default: 'info',
|
|
20
|
+
// helpGroup: 'GLOBAL',
|
|
21
|
+
// options: ['debug', 'warn', 'error', 'info', 'trace'] as const,
|
|
22
|
+
// summary: 'Specify level for logging.',
|
|
23
|
+
// })(),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
protected flags!: Flags<T>;
|
|
27
|
+
protected args!: Args<T>;
|
|
28
|
+
|
|
29
|
+
protected env = { ...process.env };
|
|
30
|
+
protected pkg: Record<string, any>;
|
|
31
|
+
protected isESM: boolean;
|
|
32
|
+
protected pkgEgg: PackageEgg;
|
|
33
|
+
protected globalExecArgv: string[] = [];
|
|
34
|
+
|
|
35
|
+
public async init(): Promise<void> {
|
|
36
|
+
await super.init();
|
|
37
|
+
debug('[init] raw args: %o, NODE_ENV: %o', this.argv, this.env.NODE_ENV);
|
|
38
|
+
const { args, flags } = await this.parse({
|
|
39
|
+
flags: this.ctor.flags,
|
|
40
|
+
baseFlags: (super.ctor as typeof BaseCommand).baseFlags,
|
|
41
|
+
enableJsonFlag: this.ctor.enableJsonFlag,
|
|
42
|
+
args: this.ctor.args,
|
|
43
|
+
strict: this.ctor.strict,
|
|
44
|
+
});
|
|
45
|
+
this.flags = flags as Flags<T>;
|
|
46
|
+
this.args = args as Args<T>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected async initBaseInfo(baseDir: string) {
|
|
50
|
+
const pkg = await readJSON(path.join(baseDir, 'package.json'));
|
|
51
|
+
this.pkg = pkg;
|
|
52
|
+
this.pkgEgg = pkg.egg ?? {};
|
|
53
|
+
this.isESM = pkg.type === 'module';
|
|
54
|
+
debug('[initBaseInfo] baseDir: %o, pkgEgg: %o, isESM: %o', baseDir, this.pkgEgg, this.isESM);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected async catch(err: Error & {exitCode?: number}): Promise<any> {
|
|
58
|
+
// add any custom logic to handle errors from the command
|
|
59
|
+
// or simply return the parent class error handling
|
|
60
|
+
return super.catch(err);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected async finally(_: Error | undefined): Promise<any> {
|
|
64
|
+
// called after run and catch regardless of whether or not the command errored
|
|
65
|
+
return super.finally(_);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { debuglog, promisify } from 'node:util';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { scheduler } from 'node:timers/promises';
|
|
4
|
+
import { spawn, SpawnOptions, ChildProcess, execFile as _execFile } from 'node:child_process';
|
|
5
|
+
import { mkdir, rename, stat, open } from 'node:fs/promises';
|
|
6
|
+
import { homedir } from 'node-homedir';
|
|
7
|
+
import { Args, Flags } from '@oclif/core';
|
|
8
|
+
import { getFrameworkPath, importResolve } from '@eggjs/utils';
|
|
9
|
+
import { readJSON, exists, getDateStringParts } from 'utility';
|
|
10
|
+
import { BaseCommand } from '../baseCommand.js';
|
|
11
|
+
import { getSourceDirname } from '../helper.js';
|
|
12
|
+
|
|
13
|
+
const debug = debuglog('@eggjs/scripts/commands/start');
|
|
14
|
+
|
|
15
|
+
const execFile = promisify(_execFile);
|
|
16
|
+
|
|
17
|
+
export interface FrameworkOptions {
|
|
18
|
+
baseDir: string;
|
|
19
|
+
framework?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default class Start<T extends typeof Start> extends BaseCommand<T> {
|
|
23
|
+
static override description = 'Start server at prod mode';
|
|
24
|
+
|
|
25
|
+
static override examples = [
|
|
26
|
+
'<%= config.bin %> <%= command.id %>',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
static override args = {
|
|
30
|
+
baseDir: Args.string({
|
|
31
|
+
description: 'directory of application',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
static override flags = {
|
|
37
|
+
title: Flags.string({
|
|
38
|
+
description: 'process title description, use for kill grep, default to `egg-server-${APP_NAME}`',
|
|
39
|
+
}),
|
|
40
|
+
framework: Flags.string({
|
|
41
|
+
description: 'specify framework that can be absolute path or npm package',
|
|
42
|
+
}),
|
|
43
|
+
port: Flags.integer({
|
|
44
|
+
description: 'listening port, default to `process.env.PORT`',
|
|
45
|
+
char: 'p',
|
|
46
|
+
}),
|
|
47
|
+
workers: Flags.integer({
|
|
48
|
+
char: 'c',
|
|
49
|
+
aliases: [ 'cluster' ],
|
|
50
|
+
description: 'numbers of app workers, default to `process.env.EGG_WORKERS` or `os.cpus().length`',
|
|
51
|
+
}),
|
|
52
|
+
env: Flags.string({
|
|
53
|
+
description: 'server env, default to `process.env.EGG_SERVER_ENV`',
|
|
54
|
+
default: process.env.EGG_SERVER_ENV,
|
|
55
|
+
}),
|
|
56
|
+
daemon: Flags.boolean({
|
|
57
|
+
description: 'whether run at background daemon mode',
|
|
58
|
+
}),
|
|
59
|
+
stdout: Flags.string({
|
|
60
|
+
description: 'customize stdout file',
|
|
61
|
+
}),
|
|
62
|
+
stderr: Flags.string({
|
|
63
|
+
description: 'customize stderr file',
|
|
64
|
+
}),
|
|
65
|
+
timeout: Flags.integer({
|
|
66
|
+
description: 'the maximum timeout(ms) when app starts',
|
|
67
|
+
default: 300 * 1000,
|
|
68
|
+
}),
|
|
69
|
+
'ignore-stderr': Flags.boolean({
|
|
70
|
+
description: 'whether ignore stderr when app starts',
|
|
71
|
+
}),
|
|
72
|
+
node: Flags.string({
|
|
73
|
+
description: 'customize node command path',
|
|
74
|
+
default: 'node',
|
|
75
|
+
}),
|
|
76
|
+
require: Flags.string({
|
|
77
|
+
summary: 'require the given module',
|
|
78
|
+
char: 'r',
|
|
79
|
+
multiple: true,
|
|
80
|
+
}),
|
|
81
|
+
sourcemap: Flags.boolean({
|
|
82
|
+
summary: 'whether enable sourcemap support, will load `source-map-support` etc',
|
|
83
|
+
aliases: [ 'ts', 'typescript' ],
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
isReady = false;
|
|
88
|
+
#child: ChildProcess;
|
|
89
|
+
|
|
90
|
+
protected async getFrameworkPath(options: FrameworkOptions) {
|
|
91
|
+
return getFrameworkPath(options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
protected async getFrameworkName(frameworkPath: string) {
|
|
95
|
+
const pkgPath = path.join(frameworkPath, 'package.json');
|
|
96
|
+
let name = 'egg';
|
|
97
|
+
try {
|
|
98
|
+
const pkg = await readJSON(pkgPath);
|
|
99
|
+
if (pkg.name) {
|
|
100
|
+
name = pkg.name;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// ignore
|
|
104
|
+
}
|
|
105
|
+
return name;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected async getServerBin() {
|
|
109
|
+
const serverBinName = this.isESM ? 'start-cluster.mjs' : 'start-cluster.cjs';
|
|
110
|
+
// for src paths, `./src/commands/start.js`
|
|
111
|
+
let serverBin = path.join(getSourceDirname(), '../scripts', serverBinName);
|
|
112
|
+
if (!(await exists(serverBin))) {
|
|
113
|
+
// for dist paths, `./dist/esm/commands/start.js`
|
|
114
|
+
serverBin = path.join(getSourceDirname(), '../../scripts', serverBinName);
|
|
115
|
+
}
|
|
116
|
+
return serverBin;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public async run(): Promise<void> {
|
|
120
|
+
const { args, flags } = this;
|
|
121
|
+
// context.execArgvObj = context.execArgvObj || {};
|
|
122
|
+
// const { argv, env, cwd, execArgvObj } = context;
|
|
123
|
+
const HOME = homedir();
|
|
124
|
+
const logDir = path.join(HOME, 'logs');
|
|
125
|
+
|
|
126
|
+
// eggctl start
|
|
127
|
+
// eggctl start ./server
|
|
128
|
+
// eggctl start /opt/app
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
let baseDir = args.baseDir || cwd;
|
|
131
|
+
if (!path.isAbsolute(baseDir)) {
|
|
132
|
+
baseDir = path.join(cwd, baseDir);
|
|
133
|
+
}
|
|
134
|
+
await this.initBaseInfo(baseDir);
|
|
135
|
+
|
|
136
|
+
flags.framework = await this.getFrameworkPath({
|
|
137
|
+
framework: flags.framework,
|
|
138
|
+
baseDir,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const frameworkName = await this.getFrameworkName(flags.framework);
|
|
142
|
+
|
|
143
|
+
flags.title = flags.title || `egg-server-${this.pkg.name}`;
|
|
144
|
+
|
|
145
|
+
flags.stdout = flags.stdout || path.join(logDir, 'master-stdout.log');
|
|
146
|
+
flags.stderr = flags.stderr || path.join(logDir, 'master-stderr.log');
|
|
147
|
+
|
|
148
|
+
if (flags.workers === undefined && process.env.EGG_WORKERS) {
|
|
149
|
+
flags.workers = Number(process.env.EGG_WORKERS);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// normalize env
|
|
153
|
+
this.env.HOME = HOME;
|
|
154
|
+
this.env.NODE_ENV = 'production';
|
|
155
|
+
|
|
156
|
+
// it makes env big but more robust
|
|
157
|
+
this.env.PATH = this.env.Path = [
|
|
158
|
+
// for nodeinstall
|
|
159
|
+
path.join(baseDir, 'node_modules/.bin'),
|
|
160
|
+
// support `.node/bin`, due to npm5 will remove `node_modules/.bin`
|
|
161
|
+
path.join(baseDir, '.node/bin'),
|
|
162
|
+
// adjust env for win
|
|
163
|
+
this.env.PATH || this.env.Path,
|
|
164
|
+
].filter(x => !!x).join(path.delimiter);
|
|
165
|
+
|
|
166
|
+
// for alinode
|
|
167
|
+
this.env.ENABLE_NODE_LOG = 'YES';
|
|
168
|
+
this.env.NODE_LOG_DIR = this.env.NODE_LOG_DIR || path.join(logDir, 'alinode');
|
|
169
|
+
await mkdir(this.env.NODE_LOG_DIR, { recursive: true });
|
|
170
|
+
|
|
171
|
+
// cli argv -> process.env.EGG_SERVER_ENV -> `undefined` then egg will use `prod`
|
|
172
|
+
if (flags.env) {
|
|
173
|
+
// if undefined, should not pass key due to `spawn`, https://github.com/nodejs/node/blob/master/lib/child_process.js#L470
|
|
174
|
+
this.env.EGG_SERVER_ENV = flags.env;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// additional execArgv
|
|
178
|
+
const execArgv: string[] = [
|
|
179
|
+
'--no-deprecation',
|
|
180
|
+
'--trace-warnings',
|
|
181
|
+
];
|
|
182
|
+
if (this.pkgEgg.revert) {
|
|
183
|
+
const reverts = Array.isArray(this.pkgEgg.revert) ? this.pkgEgg.revert : [ this.pkgEgg.revert ];
|
|
184
|
+
for (const revert of reverts) {
|
|
185
|
+
execArgv.push(`--security-revert=${revert}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// pkg.eggScriptsConfig.require
|
|
190
|
+
const scriptsConfig: Record<string, any> = this.pkg.eggScriptsConfig;
|
|
191
|
+
if (scriptsConfig?.require) {
|
|
192
|
+
scriptsConfig.require = Array.isArray(scriptsConfig.require) ? scriptsConfig.require : [ scriptsConfig.require ];
|
|
193
|
+
flags.require = [ ...scriptsConfig.require, ...(flags.require ?? []) ];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// read argv from eggScriptsConfig in package.json
|
|
197
|
+
if (scriptsConfig) {
|
|
198
|
+
for (const key in scriptsConfig) {
|
|
199
|
+
const v = scriptsConfig[key];
|
|
200
|
+
if (key.startsWith('node-options--')) {
|
|
201
|
+
const newKey = key.replace('node-options--', '');
|
|
202
|
+
if (v === true) {
|
|
203
|
+
// "node-options--allow-wasi": true
|
|
204
|
+
// => --allow-wasi
|
|
205
|
+
execArgv.push(`--${newKey}`);
|
|
206
|
+
} else {
|
|
207
|
+
// "node-options--max-http-header-size": "20000"
|
|
208
|
+
// => --max-http-header-size=20000
|
|
209
|
+
execArgv.push(`--${newKey}=${v}`);
|
|
210
|
+
}
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const existsValue = Reflect.get(flags, key);
|
|
214
|
+
if (existsValue === undefined) {
|
|
215
|
+
// only set if key is not pass from command line
|
|
216
|
+
Reflect.set(flags, key, v);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// read `egg.typescript` from package.json
|
|
222
|
+
if (this.pkgEgg.typescript && flags.sourcemap === undefined) {
|
|
223
|
+
flags.sourcemap = true;
|
|
224
|
+
}
|
|
225
|
+
if (flags.sourcemap) {
|
|
226
|
+
const sourceMapSupport = importResolve('source-map-support/register.js', {
|
|
227
|
+
paths: [ getSourceDirname() ],
|
|
228
|
+
});
|
|
229
|
+
if (this.isESM) {
|
|
230
|
+
execArgv.push('--import', sourceMapSupport);
|
|
231
|
+
} else {
|
|
232
|
+
execArgv.push('--require', sourceMapSupport);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (flags.port === undefined && process.env.PORT) {
|
|
237
|
+
flags.port = parseInt(process.env.PORT);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
debug('flags: %o, framework: %o, baseDir: %o, execArgv: %o',
|
|
241
|
+
flags, frameworkName, baseDir, execArgv);
|
|
242
|
+
|
|
243
|
+
const command = flags.node;
|
|
244
|
+
const options: SpawnOptions = {
|
|
245
|
+
env: this.env,
|
|
246
|
+
stdio: 'inherit',
|
|
247
|
+
detached: false,
|
|
248
|
+
cwd: baseDir,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
this.log('Starting %s application at %s', frameworkName, baseDir);
|
|
252
|
+
|
|
253
|
+
// remove unused properties from stringify, alias had been remove by `removeAlias`
|
|
254
|
+
const ignoreKeys = [ 'env', 'daemon', 'stdout', 'stderr', 'timeout', 'ignore-stderr', 'node' ];
|
|
255
|
+
const clusterOptions = stringify({
|
|
256
|
+
...flags,
|
|
257
|
+
baseDir,
|
|
258
|
+
}, ignoreKeys);
|
|
259
|
+
// Note: `spawn` is not like `fork`, had to pass `execArgv` yourself
|
|
260
|
+
const serverBin = await this.getServerBin();
|
|
261
|
+
const eggArgs = [ ...execArgv, serverBin, clusterOptions, `--title=${flags.title}` ];
|
|
262
|
+
const spawnScript = `${command} ${eggArgs.map(a => `'${a}'`).join(' ')}`;
|
|
263
|
+
this.log('Spawn %o', spawnScript);
|
|
264
|
+
|
|
265
|
+
// whether run in the background.
|
|
266
|
+
if (flags.daemon) {
|
|
267
|
+
this.log(`Save log file to ${logDir}`);
|
|
268
|
+
const [ stdout, stderr ] = await Promise.all([
|
|
269
|
+
getRotateLog(flags.stdout),
|
|
270
|
+
getRotateLog(flags.stderr),
|
|
271
|
+
]);
|
|
272
|
+
options.stdio = [ 'ignore', stdout, stderr, 'ipc' ];
|
|
273
|
+
options.detached = true;
|
|
274
|
+
const child = this.#child = spawn(command, eggArgs, options);
|
|
275
|
+
this.isReady = false;
|
|
276
|
+
child.on('message', (msg: any) => {
|
|
277
|
+
// https://github.com/eggjs/cluster/blob/master/src/master.ts#L119
|
|
278
|
+
if (msg && msg.action === 'egg-ready') {
|
|
279
|
+
this.isReady = true;
|
|
280
|
+
this.log('%s started on %s', frameworkName, msg.data.address);
|
|
281
|
+
child.unref();
|
|
282
|
+
child.disconnect();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// check start status
|
|
287
|
+
await this.checkStatus();
|
|
288
|
+
} else {
|
|
289
|
+
options.stdio = [ 'inherit', 'inherit', 'inherit', 'ipc' ];
|
|
290
|
+
const child = this.#child = spawn(command, eggArgs, options);
|
|
291
|
+
child.once('exit', code => {
|
|
292
|
+
if (!code) return;
|
|
293
|
+
// command should exit after child process exit
|
|
294
|
+
this.exit(code);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// attach master signal to child
|
|
298
|
+
let signal;
|
|
299
|
+
const signals = [ 'SIGINT', 'SIGQUIT', 'SIGTERM' ] as NodeJS.Signals[];
|
|
300
|
+
signals.forEach(event => {
|
|
301
|
+
process.once(event, () => {
|
|
302
|
+
debug('Kill child %s with %s', child.pid, signal);
|
|
303
|
+
child.kill(event);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
protected async checkStatus() {
|
|
310
|
+
let count = 0;
|
|
311
|
+
let hasError = false;
|
|
312
|
+
let isSuccess = true;
|
|
313
|
+
const timeout = this.flags.timeout / 1000;
|
|
314
|
+
const stderrFile = this.flags.stderr!;
|
|
315
|
+
while (!this.isReady) {
|
|
316
|
+
try {
|
|
317
|
+
const stats = await stat(stderrFile);
|
|
318
|
+
if (stats && stats.size > 0) {
|
|
319
|
+
hasError = true;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
} catch (_) {
|
|
323
|
+
// nothing
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (count >= timeout) {
|
|
327
|
+
this.logToStderr('Start failed, %ds timeout', timeout);
|
|
328
|
+
isSuccess = false;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
await scheduler.wait(1000);
|
|
333
|
+
this.log('Wait Start: %d...', ++count);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (hasError) {
|
|
337
|
+
try {
|
|
338
|
+
const args = [ '-n', '100', stderrFile ];
|
|
339
|
+
this.logToStderr('tail %s', args.join(' '));
|
|
340
|
+
const { stdout: headStdout } = await execFile('head', args);
|
|
341
|
+
const { stdout: tailStdout } = await execFile('tail', args);
|
|
342
|
+
this.logToStderr('Got error when startup: ');
|
|
343
|
+
this.logToStderr(headStdout);
|
|
344
|
+
this.logToStderr('...');
|
|
345
|
+
this.logToStderr(tailStdout);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
this.logToStderr('ignore tail error: %s', err);
|
|
348
|
+
}
|
|
349
|
+
isSuccess = this.flags['ignore-stderr'];
|
|
350
|
+
this.logToStderr('Start got error, see %o', stderrFile);
|
|
351
|
+
this.logToStderr('Or use `--ignore-stderr` to ignore stderr at startup.');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!isSuccess) {
|
|
355
|
+
this.#child.kill('SIGTERM');
|
|
356
|
+
await scheduler.wait(1000);
|
|
357
|
+
this.exit(1);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function stringify(obj: Record<string, any>, ignore: string[]) {
|
|
363
|
+
const result: Record<string, any> = {};
|
|
364
|
+
Object.keys(obj).forEach(key => {
|
|
365
|
+
if (!ignore.includes(key)) {
|
|
366
|
+
result[key] = obj[key];
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return JSON.stringify(result);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function getRotateLog(logFile: string) {
|
|
373
|
+
await mkdir(path.dirname(logFile), { recursive: true });
|
|
374
|
+
|
|
375
|
+
if (await exists(logFile)) {
|
|
376
|
+
// format style: .20150602.193100
|
|
377
|
+
const [ YYYY, MM, DD, HH, mm, ss ] = getDateStringParts();
|
|
378
|
+
const timestamp = `.${YYYY}${MM}${DD}.${HH}${mm}${ss}`;
|
|
379
|
+
// Note: rename last log to next start time, not when last log file created
|
|
380
|
+
await rename(logFile, logFile + timestamp);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return (await open(logFile, 'a')).fd;
|
|
384
|
+
}
|