@jayfong/x-server 2.10.0 → 2.11.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/lib/_cjs/cli/cli.js +34 -12
- package/lib/_cjs/cli/deploy_util.js +24 -0
- package/lib/_cjs/cli/env_util.js +97 -54
- package/lib/_cjs/cli/templates/routes.ts +15 -5
- package/lib/_cjs/core/handler.js +0 -1
- package/lib/_cjs/core/server.js +18 -1
- package/lib/cli/cli.js +34 -12
- package/lib/cli/deploy_util.d.ts +9 -0
- package/lib/cli/deploy_util.js +24 -0
- package/lib/cli/env_util.d.ts +6 -3
- package/lib/cli/env_util.js +94 -55
- package/lib/cli/templates/routes.ts +15 -5
- package/lib/core/handler.js +0 -1
- package/lib/core/server.js +18 -1
- package/package.json +2 -1
package/lib/_cjs/cli/cli.js
CHANGED
|
@@ -142,7 +142,9 @@ _yargs.default.command('dev', '开始开发', _ => _.positional('index', {
|
|
|
142
142
|
await _env_util.EnvUtil.importFile({
|
|
143
143
|
cwd: process.cwd(),
|
|
144
144
|
file: '.env'
|
|
145
|
-
});
|
|
145
|
+
}); // 之所以能成功是因为 Prisma 用的 dotenv 默认不会覆盖已经设置的环境变量
|
|
146
|
+
// https://github.com/motdotla/dotenv#override
|
|
147
|
+
|
|
146
148
|
process.env.DATABASE_URL = process.env.DATABASE_ACTION_URL || process.env.DATABASE_URL;
|
|
147
149
|
await (0, _execa.default)('tnpx', ['prisma', ...argv._.slice(1), '--schema', 'src/db/schema.prisma'], {
|
|
148
150
|
cwd: process.cwd(),
|
|
@@ -179,18 +181,38 @@ _yargs.default.command('dev', '开始开发', _ => _.positional('index', {
|
|
|
179
181
|
stdio: 'inherit'
|
|
180
182
|
});
|
|
181
183
|
}
|
|
182
|
-
}).command('deploy', '部署',
|
|
183
|
-
|
|
184
|
+
}).command('deploy [type]', '部署', _ => _.positional('type', {
|
|
185
|
+
describe: '类型',
|
|
186
|
+
type: 'string',
|
|
187
|
+
choices: ['env']
|
|
188
|
+
}), async argv => {
|
|
189
|
+
const deployEnv = await _env_util.EnvUtil.parseFileAsMap({
|
|
184
190
|
cwd: process.cwd(),
|
|
185
191
|
file: '.env.deploy'
|
|
186
192
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
|
|
194
|
+
if (argv.type === 'env') {
|
|
195
|
+
const appEnv = await _env_util.EnvUtil.parseFileAsMap({
|
|
196
|
+
cwd: process.cwd(),
|
|
197
|
+
file: '.env'
|
|
198
|
+
});
|
|
199
|
+
await _deploy_util.DeployUtil.deployEnv({
|
|
200
|
+
host: deployEnv.HOST,
|
|
201
|
+
user: deployEnv.USER,
|
|
202
|
+
key: deployEnv.KEY,
|
|
203
|
+
token: appEnv.APP_TOKEN,
|
|
204
|
+
port: appEnv.APP_PORT,
|
|
205
|
+
env: appEnv
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
await _deploy_util.DeployUtil.deploy({
|
|
209
|
+
cwd: process.cwd(),
|
|
210
|
+
host: deployEnv.HOST,
|
|
211
|
+
user: deployEnv.USER,
|
|
212
|
+
key: deployEnv.KEY,
|
|
213
|
+
dir: deployEnv.DIR,
|
|
214
|
+
cmd: deployEnv.CMD,
|
|
215
|
+
memory: deployEnv.MEMORY
|
|
216
|
+
});
|
|
217
|
+
}
|
|
196
218
|
}).parse();
|
|
@@ -57,6 +57,30 @@ class DeployUtil {
|
|
|
57
57
|
ssh.dispose();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
static async deployEnv(options) {
|
|
61
|
+
const ssh = new _nodeSsh.NodeSSH();
|
|
62
|
+
await ssh.connect({
|
|
63
|
+
host: options.host,
|
|
64
|
+
username: options.user,
|
|
65
|
+
privateKey: options.key
|
|
66
|
+
});
|
|
67
|
+
await ssh.execCommand((0, _vtils.dedent)`
|
|
68
|
+
set -ex
|
|
69
|
+
curl --header "Content-Type: application/json" \\
|
|
70
|
+
--request "POST" \\
|
|
71
|
+
--data ${JSON.stringify(JSON.stringify({
|
|
72
|
+
token: options.token,
|
|
73
|
+
type: 'updateEnv',
|
|
74
|
+
data: options.env
|
|
75
|
+
}))} \\
|
|
76
|
+
"http://localhost:${options.port}/$"
|
|
77
|
+
`, {
|
|
78
|
+
onStdout: buf => console.log(buf.toString()),
|
|
79
|
+
onStderr: buf => console.log(buf.toString())
|
|
80
|
+
});
|
|
81
|
+
ssh.dispose();
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
}
|
|
61
85
|
|
|
62
86
|
exports.DeployUtil = DeployUtil;
|
package/lib/_cjs/cli/env_util.js
CHANGED
|
@@ -11,80 +11,106 @@ var _nodePath = _interopRequireDefault(require("node:path"));
|
|
|
11
11
|
|
|
12
12
|
var _vtils = require("vtils");
|
|
13
13
|
|
|
14
|
+
var _yaml = require("yaml");
|
|
15
|
+
|
|
14
16
|
class EnvUtil {
|
|
15
17
|
static normalizeValue(value) {
|
|
16
|
-
if (value
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
if (value.includes(' || ')) {
|
|
20
|
+
const [devValue, prodValue] = value.split(/\s+\|\|\s+/);
|
|
21
|
+
return (0, _vtils.devOrProd)(() => EnvUtil.normalizeValue(devValue), () => EnvUtil.normalizeValue(prodValue));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return value === 'true' ? true : value === 'false' ? false : value.startsWith('[') && value.endsWith(']') || value.startsWith('{') && value.endsWith('}') ? JSON.parse(value) : (0, _vtils.isNumeric)(value) ? Number(value) : value;
|
|
25
|
+
} else if (value && typeof value === 'object') {
|
|
26
|
+
if (value.dev != null && value.prod != null) {
|
|
27
|
+
return (0, _vtils.devOrProd)(() => value.dev, () => value.prod);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return value;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
return value
|
|
33
|
+
return value;
|
|
22
34
|
}
|
|
23
35
|
|
|
24
|
-
static parseContent(src) {
|
|
25
|
-
const envs = [];
|
|
36
|
+
static parseContent(src, isYaml = false) {
|
|
37
|
+
const envs = [];
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
if (isYaml) {
|
|
40
|
+
const envObj = (0, _yaml.parse)(src);
|
|
41
|
+
Object.keys(envObj).forEach(key => {
|
|
42
|
+
var _src$match;
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
envs.push({
|
|
45
|
+
key: key,
|
|
46
|
+
value: this.normalizeValue(envObj[key]),
|
|
47
|
+
comment: ((_src$match = src.match(new RegExp(`#\\s*(.+?)\\s*[\\r\\n]+\\s*${(0, _vtils.escapeRegExp)(key)}:`))) == null ? void 0 : _src$match[1]) || ''
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
// https://github.com/andreialecu/dotenv/blob/feat-multiline/lib/main.js
|
|
52
|
+
const multilineLineBreaks = true; // convert Buffers before splitting into lines and processing
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
let
|
|
54
|
+
const lines = src.split(EnvUtil.NEWLINES_MATCH);
|
|
55
|
+
let lastComment = '';
|
|
34
56
|
|
|
35
|
-
|
|
57
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
58
|
+
let line = lines[idx]; // matching "KEY' and 'VAL' in 'KEY=VAL'
|
|
36
59
|
|
|
37
|
-
|
|
38
|
-
const key = keyValueArr[1]; // default undefined or missing values to empty string
|
|
60
|
+
const keyValueArr = line.match(EnvUtil.RE_INI_KEY_VAL); // matched?
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const isDoubleQuoted = val[0] === '"' && val[end] === '"';
|
|
43
|
-
const isSingleQuoted = val[0] === "'" && val[end] === "'";
|
|
44
|
-
const isMultilineDoubleQuoted = val[0] === '"' && (val.length === 1 || val[end] !== '"');
|
|
45
|
-
const isMultilineSingleQuoted = val[0] === "'" && (val.length === 1 || val[end] !== "'"); // if parsing line breaks and the value starts with a quote
|
|
62
|
+
if (keyValueArr != null) {
|
|
63
|
+
const key = keyValueArr[1]; // default undefined or missing values to empty string
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
let val = keyValueArr[2] || '';
|
|
66
|
+
let end = val.length - 1;
|
|
67
|
+
const isDoubleQuoted = val[0] === '"' && val[end] === '"';
|
|
68
|
+
const isSingleQuoted = val[0] === "'" && val[end] === "'";
|
|
69
|
+
const isMultilineDoubleQuoted = val[0] === '"' && (val.length === 1 || val[end] !== '"');
|
|
70
|
+
const isMultilineSingleQuoted = val[0] === "'" && (val.length === 1 || val[end] !== "'"); // if parsing line breaks and the value starts with a quote
|
|
50
71
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
if (multilineLineBreaks && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
|
|
73
|
+
const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
|
|
74
|
+
val = val.substring(1);
|
|
54
75
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
while (idx++ < lines.length - 1) {
|
|
77
|
+
line = lines[idx];
|
|
78
|
+
end = line.length - 1;
|
|
79
|
+
|
|
80
|
+
if (line[end] === quoteChar) {
|
|
81
|
+
val += EnvUtil.NEWLINE + line.substring(0, end);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
val += EnvUtil.NEWLINE + line;
|
|
58
86
|
}
|
|
59
87
|
|
|
60
|
-
val
|
|
61
|
-
}
|
|
88
|
+
val = (0, _vtils.dedent)(val); // if single or double quoted, remove quotes
|
|
89
|
+
} else if (isSingleQuoted || isDoubleQuoted) {
|
|
90
|
+
val = val.substring(1, end); // if double quoted, expand newlines
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
92
|
+
if (isDoubleQuoted) {
|
|
93
|
+
val = val.replace(EnvUtil.RE_NEWLINES, EnvUtil.NEWLINE);
|
|
94
|
+
}
|
|
66
95
|
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
val = (0, _vtils.dedent)(val);
|
|
97
|
+
} else {
|
|
98
|
+
// remove surrounding whitespace
|
|
99
|
+
val = (0, _vtils.dedent)(val).trim();
|
|
69
100
|
}
|
|
70
101
|
|
|
71
|
-
|
|
102
|
+
envs.push({
|
|
103
|
+
key: key,
|
|
104
|
+
value: EnvUtil.normalizeValue(val),
|
|
105
|
+
comment: lastComment
|
|
106
|
+
});
|
|
107
|
+
lastComment = '';
|
|
72
108
|
} else {
|
|
73
|
-
|
|
74
|
-
val = (0, _vtils.dedent)(val).trim();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
envs.push({
|
|
78
|
-
key: key,
|
|
79
|
-
value: EnvUtil.normalizeValue(val),
|
|
80
|
-
comment: lastComment
|
|
81
|
-
});
|
|
82
|
-
lastComment = '';
|
|
83
|
-
} else {
|
|
84
|
-
const commentArr = line.match(EnvUtil.COMMENT_MATCH);
|
|
109
|
+
const commentArr = line.match(EnvUtil.COMMENT_MATCH);
|
|
85
110
|
|
|
86
|
-
|
|
87
|
-
|
|
111
|
+
if (commentArr) {
|
|
112
|
+
lastComment = commentArr[1];
|
|
113
|
+
}
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
}
|
|
@@ -95,15 +121,32 @@ class EnvUtil {
|
|
|
95
121
|
static async parseFile(options) {
|
|
96
122
|
const envFile = _nodePath.default.join(options.cwd, options.file);
|
|
97
123
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
const envYmlFile = _nodePath.default.join(options.cwd, `${options.file}.yml`);
|
|
125
|
+
|
|
126
|
+
const envYamlFile = _nodePath.default.join(options.cwd, `${options.file}.yaml`);
|
|
127
|
+
|
|
128
|
+
const file = (await _fsExtra.default.pathExists(envYmlFile)) ? envYmlFile : (await _fsExtra.default.pathExists(envYamlFile)) ? envYamlFile : (await _fsExtra.default.pathExists(envFile)) ? envFile : '';
|
|
129
|
+
|
|
130
|
+
if (file) {
|
|
131
|
+
const envContent = await _fsExtra.default.readFile(file, 'utf-8');
|
|
132
|
+
const envs = EnvUtil.parseContent(envContent, /\.ya?ml$/i.test(file));
|
|
101
133
|
return envs;
|
|
102
134
|
}
|
|
103
135
|
|
|
104
136
|
return [];
|
|
105
137
|
}
|
|
106
138
|
|
|
139
|
+
static async parseFileAsMap(options) {
|
|
140
|
+
const envs = await EnvUtil.parseFile(options);
|
|
141
|
+
const envsObj = {};
|
|
142
|
+
|
|
143
|
+
for (const env of envs) {
|
|
144
|
+
envsObj[env.key] = env.value;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return envsObj;
|
|
148
|
+
}
|
|
149
|
+
|
|
107
150
|
static async importFile(options) {
|
|
108
151
|
const envs = await EnvUtil.parseFile(options);
|
|
109
152
|
const envsObj = {};
|
|
@@ -26,16 +26,26 @@ export type HandlerMap = UnionToIntersection<
|
|
|
26
26
|
}[keyof RouteMap]
|
|
27
27
|
>
|
|
28
28
|
export type HandlerPath = keyof HandlerMap
|
|
29
|
+
export type HandlerMetaMap = {
|
|
30
|
+
[K in HandlerPath]: HandlerMap[K] extends Handler<infer X, infer Y, infer Z>
|
|
31
|
+
? {
|
|
32
|
+
payload: X
|
|
33
|
+
result: Y
|
|
34
|
+
method: Z
|
|
35
|
+
}
|
|
36
|
+
: {}
|
|
37
|
+
}
|
|
29
38
|
export type HandlerPayloadMap = {
|
|
30
|
-
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
[K in HandlerPath]: HandlerMetaMap[K]['payload']
|
|
31
41
|
}
|
|
32
42
|
export type HandlerResultMap = {
|
|
33
|
-
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
[K in HandlerPath]: HandlerMetaMap[K]['result']
|
|
34
45
|
}
|
|
35
46
|
export type HandlerMethodMap = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
: never
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
[K in HandlerPath]: HandlerMetaMap[K]['method']
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
export const routes: XServer.Route[] = basePathWithHandlers.reduce<
|
package/lib/_cjs/core/handler.js
CHANGED
package/lib/_cjs/core/server.js
CHANGED
|
@@ -159,7 +159,8 @@ class Server {
|
|
|
159
159
|
websocket: isWS,
|
|
160
160
|
handler: (req, res) => handleRoute(item, req, res)
|
|
161
161
|
});
|
|
162
|
-
}
|
|
162
|
+
} // 接口统一入口
|
|
163
|
+
|
|
163
164
|
|
|
164
165
|
fastify.route({
|
|
165
166
|
method: 'POST',
|
|
@@ -183,6 +184,22 @@ class Server {
|
|
|
183
184
|
|
|
184
185
|
return handleRoute(routeMap[requestPath], req, res, requestPath);
|
|
185
186
|
}
|
|
187
|
+
}); // 开发管理入口
|
|
188
|
+
|
|
189
|
+
fastify.route({
|
|
190
|
+
method: 'POST',
|
|
191
|
+
url: '/$',
|
|
192
|
+
handler: async req => {
|
|
193
|
+
const payload = req.body;
|
|
194
|
+
|
|
195
|
+
if (payload && typeof payload === 'object' && _x.x.env.APP_TOKEN && payload.token === _x.x.env.APP_TOKEN) {
|
|
196
|
+
if (payload.type === 'ping') {
|
|
197
|
+
return 'ping:success';
|
|
198
|
+
} else if (payload.type === 'updateEnv') {
|
|
199
|
+
Object.assign(_x.x.env, payload.data);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
186
203
|
});
|
|
187
204
|
});
|
|
188
205
|
}
|
package/lib/cli/cli.js
CHANGED
|
@@ -124,7 +124,9 @@ yargs.command('dev', '开始开发', _ => _.positional('index', {
|
|
|
124
124
|
await EnvUtil.importFile({
|
|
125
125
|
cwd: process.cwd(),
|
|
126
126
|
file: '.env'
|
|
127
|
-
});
|
|
127
|
+
}); // 之所以能成功是因为 Prisma 用的 dotenv 默认不会覆盖已经设置的环境变量
|
|
128
|
+
// https://github.com/motdotla/dotenv#override
|
|
129
|
+
|
|
128
130
|
process.env.DATABASE_URL = process.env.DATABASE_ACTION_URL || process.env.DATABASE_URL;
|
|
129
131
|
await execa('tnpx', ['prisma', ...argv._.slice(1), '--schema', 'src/db/schema.prisma'], {
|
|
130
132
|
cwd: process.cwd(),
|
|
@@ -161,18 +163,38 @@ yargs.command('dev', '开始开发', _ => _.positional('index', {
|
|
|
161
163
|
stdio: 'inherit'
|
|
162
164
|
});
|
|
163
165
|
}
|
|
164
|
-
}).command('deploy', '部署',
|
|
165
|
-
|
|
166
|
+
}).command('deploy [type]', '部署', _ => _.positional('type', {
|
|
167
|
+
describe: '类型',
|
|
168
|
+
type: 'string',
|
|
169
|
+
choices: ['env']
|
|
170
|
+
}), async argv => {
|
|
171
|
+
const deployEnv = await EnvUtil.parseFileAsMap({
|
|
166
172
|
cwd: process.cwd(),
|
|
167
173
|
file: '.env.deploy'
|
|
168
174
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
|
|
176
|
+
if (argv.type === 'env') {
|
|
177
|
+
const appEnv = await EnvUtil.parseFileAsMap({
|
|
178
|
+
cwd: process.cwd(),
|
|
179
|
+
file: '.env'
|
|
180
|
+
});
|
|
181
|
+
await DeployUtil.deployEnv({
|
|
182
|
+
host: deployEnv.HOST,
|
|
183
|
+
user: deployEnv.USER,
|
|
184
|
+
key: deployEnv.KEY,
|
|
185
|
+
token: appEnv.APP_TOKEN,
|
|
186
|
+
port: appEnv.APP_PORT,
|
|
187
|
+
env: appEnv
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
await DeployUtil.deploy({
|
|
191
|
+
cwd: process.cwd(),
|
|
192
|
+
host: deployEnv.HOST,
|
|
193
|
+
user: deployEnv.USER,
|
|
194
|
+
key: deployEnv.KEY,
|
|
195
|
+
dir: deployEnv.DIR,
|
|
196
|
+
cmd: deployEnv.CMD,
|
|
197
|
+
memory: deployEnv.MEMORY
|
|
198
|
+
});
|
|
199
|
+
}
|
|
178
200
|
}).parse();
|
package/lib/cli/deploy_util.d.ts
CHANGED
|
@@ -7,6 +7,15 @@ export interface DeployOptions {
|
|
|
7
7
|
memory?: string;
|
|
8
8
|
cmd?: string;
|
|
9
9
|
}
|
|
10
|
+
export interface DeployEnvOptions {
|
|
11
|
+
host: string;
|
|
12
|
+
user: string;
|
|
13
|
+
key: string;
|
|
14
|
+
port: number;
|
|
15
|
+
token: string;
|
|
16
|
+
env: Record<string, any>;
|
|
17
|
+
}
|
|
10
18
|
export declare class DeployUtil {
|
|
11
19
|
static deploy(options: DeployOptions): Promise<void>;
|
|
20
|
+
static deployEnv(options: DeployEnvOptions): Promise<void>;
|
|
12
21
|
}
|
package/lib/cli/deploy_util.js
CHANGED
|
@@ -45,4 +45,28 @@ export class DeployUtil {
|
|
|
45
45
|
ssh.dispose();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
static async deployEnv(options) {
|
|
49
|
+
const ssh = new NodeSSH();
|
|
50
|
+
await ssh.connect({
|
|
51
|
+
host: options.host,
|
|
52
|
+
username: options.user,
|
|
53
|
+
privateKey: options.key
|
|
54
|
+
});
|
|
55
|
+
await ssh.execCommand(dedent`
|
|
56
|
+
set -ex
|
|
57
|
+
curl --header "Content-Type: application/json" \\
|
|
58
|
+
--request "POST" \\
|
|
59
|
+
--data ${JSON.stringify(JSON.stringify({
|
|
60
|
+
token: options.token,
|
|
61
|
+
type: 'updateEnv',
|
|
62
|
+
data: options.env
|
|
63
|
+
}))} \\
|
|
64
|
+
"http://localhost:${options.port}/$"
|
|
65
|
+
`, {
|
|
66
|
+
onStdout: buf => console.log(buf.toString()),
|
|
67
|
+
onStderr: buf => console.log(buf.toString())
|
|
68
|
+
});
|
|
69
|
+
ssh.dispose();
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
}
|
package/lib/cli/env_util.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
export interface ParsedEnv {
|
|
3
2
|
comment: string;
|
|
4
3
|
key: string;
|
|
@@ -10,12 +9,16 @@ export declare class EnvUtil {
|
|
|
10
9
|
static RE_NEWLINES: RegExp;
|
|
11
10
|
static NEWLINES_MATCH: RegExp;
|
|
12
11
|
static COMMENT_MATCH: RegExp;
|
|
13
|
-
static normalizeValue(value:
|
|
14
|
-
static parseContent(src: string
|
|
12
|
+
static normalizeValue(value: any): any;
|
|
13
|
+
static parseContent(src: string, isYaml?: boolean): ParsedEnv[];
|
|
15
14
|
static parseFile(options: {
|
|
16
15
|
cwd: string;
|
|
17
16
|
file: string;
|
|
18
17
|
}): Promise<ParsedEnv[]>;
|
|
18
|
+
static parseFileAsMap(options: {
|
|
19
|
+
cwd: string;
|
|
20
|
+
file: string;
|
|
21
|
+
}): Promise<Record<string, any>>;
|
|
19
22
|
static importFile(options: {
|
|
20
23
|
cwd: string;
|
|
21
24
|
file: string;
|
package/lib/cli/env_util.js
CHANGED
|
@@ -1,80 +1,105 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { dedent, devOrProd, isNumeric } from 'vtils';
|
|
3
|
+
import { dedent, devOrProd, escapeRegExp, isNumeric } from 'vtils';
|
|
4
|
+
import { parse as yamlParse } from 'yaml';
|
|
4
5
|
export class EnvUtil {
|
|
5
6
|
static normalizeValue(value) {
|
|
6
|
-
if (value
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if (typeof value === 'string') {
|
|
8
|
+
if (value.includes(' || ')) {
|
|
9
|
+
const [devValue, prodValue] = value.split(/\s+\|\|\s+/);
|
|
10
|
+
return devOrProd(() => EnvUtil.normalizeValue(devValue), () => EnvUtil.normalizeValue(prodValue));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return value === 'true' ? true : value === 'false' ? false : value.startsWith('[') && value.endsWith(']') || value.startsWith('{') && value.endsWith('}') ? JSON.parse(value) : isNumeric(value) ? Number(value) : value;
|
|
14
|
+
} else if (value && typeof value === 'object') {
|
|
15
|
+
if (value.dev != null && value.prod != null) {
|
|
16
|
+
return devOrProd(() => value.dev, () => value.prod);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return value;
|
|
9
20
|
}
|
|
10
21
|
|
|
11
|
-
return value
|
|
22
|
+
return value;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
|
-
static parseContent(src) {
|
|
15
|
-
const envs = [];
|
|
25
|
+
static parseContent(src, isYaml = false) {
|
|
26
|
+
const envs = [];
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
if (isYaml) {
|
|
29
|
+
const envObj = yamlParse(src);
|
|
30
|
+
Object.keys(envObj).forEach(key => {
|
|
31
|
+
var _src$match;
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
envs.push({
|
|
34
|
+
key: key,
|
|
35
|
+
value: this.normalizeValue(envObj[key]),
|
|
36
|
+
comment: ((_src$match = src.match(new RegExp(`#\\s*(.+?)\\s*[\\r\\n]+\\s*${escapeRegExp(key)}:`))) == null ? void 0 : _src$match[1]) || ''
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
// https://github.com/andreialecu/dotenv/blob/feat-multiline/lib/main.js
|
|
41
|
+
const multilineLineBreaks = true; // convert Buffers before splitting into lines and processing
|
|
21
42
|
|
|
22
|
-
|
|
23
|
-
let
|
|
43
|
+
const lines = src.split(EnvUtil.NEWLINES_MATCH);
|
|
44
|
+
let lastComment = '';
|
|
24
45
|
|
|
25
|
-
|
|
46
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
47
|
+
let line = lines[idx]; // matching "KEY' and 'VAL' in 'KEY=VAL'
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
const key = keyValueArr[1]; // default undefined or missing values to empty string
|
|
49
|
+
const keyValueArr = line.match(EnvUtil.RE_INI_KEY_VAL); // matched?
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const isDoubleQuoted = val[0] === '"' && val[end] === '"';
|
|
33
|
-
const isSingleQuoted = val[0] === "'" && val[end] === "'";
|
|
34
|
-
const isMultilineDoubleQuoted = val[0] === '"' && (val.length === 1 || val[end] !== '"');
|
|
35
|
-
const isMultilineSingleQuoted = val[0] === "'" && (val.length === 1 || val[end] !== "'"); // if parsing line breaks and the value starts with a quote
|
|
51
|
+
if (keyValueArr != null) {
|
|
52
|
+
const key = keyValueArr[1]; // default undefined or missing values to empty string
|
|
36
53
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
let val = keyValueArr[2] || '';
|
|
55
|
+
let end = val.length - 1;
|
|
56
|
+
const isDoubleQuoted = val[0] === '"' && val[end] === '"';
|
|
57
|
+
const isSingleQuoted = val[0] === "'" && val[end] === "'";
|
|
58
|
+
const isMultilineDoubleQuoted = val[0] === '"' && (val.length === 1 || val[end] !== '"');
|
|
59
|
+
const isMultilineSingleQuoted = val[0] === "'" && (val.length === 1 || val[end] !== "'"); // if parsing line breaks and the value starts with a quote
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
if (multilineLineBreaks && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
|
|
62
|
+
const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
|
|
63
|
+
val = val.substring(1);
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
65
|
+
while (idx++ < lines.length - 1) {
|
|
66
|
+
line = lines[idx];
|
|
67
|
+
end = line.length - 1;
|
|
68
|
+
|
|
69
|
+
if (line[end] === quoteChar) {
|
|
70
|
+
val += EnvUtil.NEWLINE + line.substring(0, end);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val += EnvUtil.NEWLINE + line;
|
|
48
75
|
}
|
|
49
76
|
|
|
50
|
-
val
|
|
51
|
-
}
|
|
77
|
+
val = dedent(val); // if single or double quoted, remove quotes
|
|
78
|
+
} else if (isSingleQuoted || isDoubleQuoted) {
|
|
79
|
+
val = val.substring(1, end); // if double quoted, expand newlines
|
|
52
80
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
if (isDoubleQuoted) {
|
|
82
|
+
val = val.replace(EnvUtil.RE_NEWLINES, EnvUtil.NEWLINE);
|
|
83
|
+
}
|
|
56
84
|
|
|
57
|
-
|
|
58
|
-
|
|
85
|
+
val = dedent(val);
|
|
86
|
+
} else {
|
|
87
|
+
// remove surrounding whitespace
|
|
88
|
+
val = dedent(val).trim();
|
|
59
89
|
}
|
|
60
90
|
|
|
61
|
-
|
|
91
|
+
envs.push({
|
|
92
|
+
key: key,
|
|
93
|
+
value: EnvUtil.normalizeValue(val),
|
|
94
|
+
comment: lastComment
|
|
95
|
+
});
|
|
96
|
+
lastComment = '';
|
|
62
97
|
} else {
|
|
63
|
-
|
|
64
|
-
val = dedent(val).trim();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
envs.push({
|
|
68
|
-
key: key,
|
|
69
|
-
value: EnvUtil.normalizeValue(val),
|
|
70
|
-
comment: lastComment
|
|
71
|
-
});
|
|
72
|
-
lastComment = '';
|
|
73
|
-
} else {
|
|
74
|
-
const commentArr = line.match(EnvUtil.COMMENT_MATCH);
|
|
98
|
+
const commentArr = line.match(EnvUtil.COMMENT_MATCH);
|
|
75
99
|
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
if (commentArr) {
|
|
101
|
+
lastComment = commentArr[1];
|
|
102
|
+
}
|
|
78
103
|
}
|
|
79
104
|
}
|
|
80
105
|
}
|
|
@@ -84,16 +109,30 @@ export class EnvUtil {
|
|
|
84
109
|
|
|
85
110
|
static async parseFile(options) {
|
|
86
111
|
const envFile = path.join(options.cwd, options.file);
|
|
112
|
+
const envYmlFile = path.join(options.cwd, `${options.file}.yml`);
|
|
113
|
+
const envYamlFile = path.join(options.cwd, `${options.file}.yaml`);
|
|
114
|
+
const file = (await fs.pathExists(envYmlFile)) ? envYmlFile : (await fs.pathExists(envYamlFile)) ? envYamlFile : (await fs.pathExists(envFile)) ? envFile : '';
|
|
87
115
|
|
|
88
|
-
if (
|
|
89
|
-
const envContent = await fs.readFile(
|
|
90
|
-
const envs = EnvUtil.parseContent(envContent);
|
|
116
|
+
if (file) {
|
|
117
|
+
const envContent = await fs.readFile(file, 'utf-8');
|
|
118
|
+
const envs = EnvUtil.parseContent(envContent, /\.ya?ml$/i.test(file));
|
|
91
119
|
return envs;
|
|
92
120
|
}
|
|
93
121
|
|
|
94
122
|
return [];
|
|
95
123
|
}
|
|
96
124
|
|
|
125
|
+
static async parseFileAsMap(options) {
|
|
126
|
+
const envs = await EnvUtil.parseFile(options);
|
|
127
|
+
const envsObj = {};
|
|
128
|
+
|
|
129
|
+
for (const env of envs) {
|
|
130
|
+
envsObj[env.key] = env.value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return envsObj;
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
static async importFile(options) {
|
|
98
137
|
const envs = await EnvUtil.parseFile(options);
|
|
99
138
|
const envsObj = {};
|
|
@@ -26,16 +26,26 @@ export type HandlerMap = UnionToIntersection<
|
|
|
26
26
|
}[keyof RouteMap]
|
|
27
27
|
>
|
|
28
28
|
export type HandlerPath = keyof HandlerMap
|
|
29
|
+
export type HandlerMetaMap = {
|
|
30
|
+
[K in HandlerPath]: HandlerMap[K] extends Handler<infer X, infer Y, infer Z>
|
|
31
|
+
? {
|
|
32
|
+
payload: X
|
|
33
|
+
result: Y
|
|
34
|
+
method: Z
|
|
35
|
+
}
|
|
36
|
+
: {}
|
|
37
|
+
}
|
|
29
38
|
export type HandlerPayloadMap = {
|
|
30
|
-
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
[K in HandlerPath]: HandlerMetaMap[K]['payload']
|
|
31
41
|
}
|
|
32
42
|
export type HandlerResultMap = {
|
|
33
|
-
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
[K in HandlerPath]: HandlerMetaMap[K]['result']
|
|
34
45
|
}
|
|
35
46
|
export type HandlerMethodMap = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
: never
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
[K in HandlerPath]: HandlerMetaMap[K]['method']
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
export const routes: XServer.Route[] = basePathWithHandlers.reduce<
|
package/lib/core/handler.js
CHANGED
package/lib/core/server.js
CHANGED
|
@@ -144,7 +144,8 @@ export class Server {
|
|
|
144
144
|
websocket: isWS,
|
|
145
145
|
handler: (req, res) => handleRoute(item, req, res)
|
|
146
146
|
});
|
|
147
|
-
}
|
|
147
|
+
} // 接口统一入口
|
|
148
|
+
|
|
148
149
|
|
|
149
150
|
fastify.route({
|
|
150
151
|
method: 'POST',
|
|
@@ -168,6 +169,22 @@ export class Server {
|
|
|
168
169
|
|
|
169
170
|
return handleRoute(routeMap[requestPath], req, res, requestPath);
|
|
170
171
|
}
|
|
172
|
+
}); // 开发管理入口
|
|
173
|
+
|
|
174
|
+
fastify.route({
|
|
175
|
+
method: 'POST',
|
|
176
|
+
url: '/$',
|
|
177
|
+
handler: async req => {
|
|
178
|
+
const payload = req.body;
|
|
179
|
+
|
|
180
|
+
if (payload && typeof payload === 'object' && x.env.APP_TOKEN && payload.token === x.env.APP_TOKEN) {
|
|
181
|
+
if (payload.type === 'ping') {
|
|
182
|
+
return 'ping:success';
|
|
183
|
+
} else if (payload.type === 'updateEnv') {
|
|
184
|
+
Object.assign(x.env, payload.data);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
171
188
|
});
|
|
172
189
|
});
|
|
173
190
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayfong/x-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "lib/_cjs/index.js",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"utf-8-validate": "^5.0.9",
|
|
76
76
|
"vscode-generate-index-standalone": "^1.7.1",
|
|
77
77
|
"vtils": "^4.85.2",
|
|
78
|
+
"yaml": "^2.3.1",
|
|
78
79
|
"yargs": "^17.4.1"
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|