@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.
@@ -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', '部署', async () => {
183
- await _env_util.EnvUtil.importFile({
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
- await _deploy_util.DeployUtil.deploy({
188
- cwd: process.cwd(),
189
- host: process.env.HOST,
190
- user: process.env.USER,
191
- key: process.env.KEY,
192
- dir: process.env.DIR,
193
- cmd: process.env.CMD,
194
- memory: process.env.MEMORY
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;
@@ -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.includes(' || ')) {
17
- const [devValue, prodValue] = value.split(/\s+\|\|\s+/);
18
- return (0, _vtils.devOrProd)(() => EnvUtil.normalizeValue(devValue), () => EnvUtil.normalizeValue(prodValue));
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 === 'true' ? true : value === 'false' ? false : value.startsWith('[') && value.endsWith(']') || value.startsWith('{') && value.endsWith('}') ? JSON.parse(value) : (0, _vtils.isNumeric)(value) ? Number(value) : value;
33
+ return value;
22
34
  }
23
35
 
24
- static parseContent(src) {
25
- const envs = []; // https://github.com/andreialecu/dotenv/blob/feat-multiline/lib/main.js
36
+ static parseContent(src, isYaml = false) {
37
+ const envs = [];
26
38
 
27
- const multilineLineBreaks = true; // convert Buffers before splitting into lines and processing
39
+ if (isYaml) {
40
+ const envObj = (0, _yaml.parse)(src);
41
+ Object.keys(envObj).forEach(key => {
42
+ var _src$match;
28
43
 
29
- const lines = src.toString().split(EnvUtil.NEWLINES_MATCH);
30
- let lastComment = '';
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
- for (let idx = 0; idx < lines.length; idx++) {
33
- let line = lines[idx]; // matching "KEY' and 'VAL' in 'KEY=VAL'
54
+ const lines = src.split(EnvUtil.NEWLINES_MATCH);
55
+ let lastComment = '';
34
56
 
35
- const keyValueArr = line.match(EnvUtil.RE_INI_KEY_VAL); // matched?
57
+ for (let idx = 0; idx < lines.length; idx++) {
58
+ let line = lines[idx]; // matching "KEY' and 'VAL' in 'KEY=VAL'
36
59
 
37
- if (keyValueArr != null) {
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
- let val = keyValueArr[2] || '';
41
- let end = val.length - 1;
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
- if (multilineLineBreaks && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
48
- const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
49
- val = val.substring(1);
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
- while (idx++ < lines.length - 1) {
52
- line = lines[idx];
53
- end = line.length - 1;
72
+ if (multilineLineBreaks && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
73
+ const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
74
+ val = val.substring(1);
54
75
 
55
- if (line[end] === quoteChar) {
56
- val += EnvUtil.NEWLINE + line.substring(0, end);
57
- break;
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 += EnvUtil.NEWLINE + line;
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
- val = (0, _vtils.dedent)(val); // if single or double quoted, remove quotes
64
- } else if (isSingleQuoted || isDoubleQuoted) {
65
- val = val.substring(1, end); // if double quoted, expand newlines
92
+ if (isDoubleQuoted) {
93
+ val = val.replace(EnvUtil.RE_NEWLINES, EnvUtil.NEWLINE);
94
+ }
66
95
 
67
- if (isDoubleQuoted) {
68
- val = val.replace(EnvUtil.RE_NEWLINES, EnvUtil.NEWLINE);
96
+ val = (0, _vtils.dedent)(val);
97
+ } else {
98
+ // remove surrounding whitespace
99
+ val = (0, _vtils.dedent)(val).trim();
69
100
  }
70
101
 
71
- val = (0, _vtils.dedent)(val);
102
+ envs.push({
103
+ key: key,
104
+ value: EnvUtil.normalizeValue(val),
105
+ comment: lastComment
106
+ });
107
+ lastComment = '';
72
108
  } else {
73
- // remove surrounding whitespace
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
- if (commentArr) {
87
- lastComment = commentArr[1];
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
- if (await _fsExtra.default.pathExists(envFile)) {
99
- const envContent = await _fsExtra.default.readFile(envFile, 'utf-8');
100
- const envs = EnvUtil.parseContent(envContent);
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
- [K in HandlerPath]: HandlerMap[K] extends Handler<infer X, any> ? X : {}
39
+ // @ts-ignore
40
+ [K in HandlerPath]: HandlerMetaMap[K]['payload']
31
41
  }
32
42
  export type HandlerResultMap = {
33
- [K in HandlerPath]: HandlerMap[K] extends Handler<any, infer X> ? X : {}
43
+ // @ts-ignore
44
+ [K in HandlerPath]: HandlerMetaMap[K]['result']
34
45
  }
35
46
  export type HandlerMethodMap = {
36
- [K in HandlerPath]: HandlerMap[K] extends Handler<any, any, infer X>
37
- ? X
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<
@@ -38,7 +38,6 @@ class Handler {
38
38
  if (this.requestDataSchema) {
39
39
  try {
40
40
  data = await this.requestDataSchema.validate(data, {
41
- strict: true,
42
41
  abortEarly: true,
43
42
  stripUnknown: true
44
43
  });
@@ -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', '部署', async () => {
165
- await EnvUtil.importFile({
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
- await DeployUtil.deploy({
170
- cwd: process.cwd(),
171
- host: process.env.HOST,
172
- user: process.env.USER,
173
- key: process.env.KEY,
174
- dir: process.env.DIR,
175
- cmd: process.env.CMD,
176
- memory: process.env.MEMORY
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();
@@ -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
  }
@@ -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
  }
@@ -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: string): any;
14
- static parseContent(src: string | Buffer): ParsedEnv[];
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;
@@ -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.includes(' || ')) {
7
- const [devValue, prodValue] = value.split(/\s+\|\|\s+/);
8
- return devOrProd(() => EnvUtil.normalizeValue(devValue), () => EnvUtil.normalizeValue(prodValue));
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 === 'true' ? true : value === 'false' ? false : value.startsWith('[') && value.endsWith(']') || value.startsWith('{') && value.endsWith('}') ? JSON.parse(value) : isNumeric(value) ? Number(value) : value;
22
+ return value;
12
23
  }
13
24
 
14
- static parseContent(src) {
15
- const envs = []; // https://github.com/andreialecu/dotenv/blob/feat-multiline/lib/main.js
25
+ static parseContent(src, isYaml = false) {
26
+ const envs = [];
16
27
 
17
- const multilineLineBreaks = true; // convert Buffers before splitting into lines and processing
28
+ if (isYaml) {
29
+ const envObj = yamlParse(src);
30
+ Object.keys(envObj).forEach(key => {
31
+ var _src$match;
18
32
 
19
- const lines = src.toString().split(EnvUtil.NEWLINES_MATCH);
20
- let lastComment = '';
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
- for (let idx = 0; idx < lines.length; idx++) {
23
- let line = lines[idx]; // matching "KEY' and 'VAL' in 'KEY=VAL'
43
+ const lines = src.split(EnvUtil.NEWLINES_MATCH);
44
+ let lastComment = '';
24
45
 
25
- const keyValueArr = line.match(EnvUtil.RE_INI_KEY_VAL); // matched?
46
+ for (let idx = 0; idx < lines.length; idx++) {
47
+ let line = lines[idx]; // matching "KEY' and 'VAL' in 'KEY=VAL'
26
48
 
27
- if (keyValueArr != null) {
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
- let val = keyValueArr[2] || '';
31
- let end = val.length - 1;
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
- if (multilineLineBreaks && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
38
- const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
39
- val = val.substring(1);
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
- while (idx++ < lines.length - 1) {
42
- line = lines[idx];
43
- end = line.length - 1;
61
+ if (multilineLineBreaks && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
62
+ const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
63
+ val = val.substring(1);
44
64
 
45
- if (line[end] === quoteChar) {
46
- val += EnvUtil.NEWLINE + line.substring(0, end);
47
- break;
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 += EnvUtil.NEWLINE + line;
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
- val = dedent(val); // if single or double quoted, remove quotes
54
- } else if (isSingleQuoted || isDoubleQuoted) {
55
- val = val.substring(1, end); // if double quoted, expand newlines
81
+ if (isDoubleQuoted) {
82
+ val = val.replace(EnvUtil.RE_NEWLINES, EnvUtil.NEWLINE);
83
+ }
56
84
 
57
- if (isDoubleQuoted) {
58
- val = val.replace(EnvUtil.RE_NEWLINES, EnvUtil.NEWLINE);
85
+ val = dedent(val);
86
+ } else {
87
+ // remove surrounding whitespace
88
+ val = dedent(val).trim();
59
89
  }
60
90
 
61
- val = dedent(val);
91
+ envs.push({
92
+ key: key,
93
+ value: EnvUtil.normalizeValue(val),
94
+ comment: lastComment
95
+ });
96
+ lastComment = '';
62
97
  } else {
63
- // remove surrounding whitespace
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
- if (commentArr) {
77
- lastComment = commentArr[1];
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 (await fs.pathExists(envFile)) {
89
- const envContent = await fs.readFile(envFile, 'utf-8');
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
- [K in HandlerPath]: HandlerMap[K] extends Handler<infer X, any> ? X : {}
39
+ // @ts-ignore
40
+ [K in HandlerPath]: HandlerMetaMap[K]['payload']
31
41
  }
32
42
  export type HandlerResultMap = {
33
- [K in HandlerPath]: HandlerMap[K] extends Handler<any, infer X> ? X : {}
43
+ // @ts-ignore
44
+ [K in HandlerPath]: HandlerMetaMap[K]['result']
34
45
  }
35
46
  export type HandlerMethodMap = {
36
- [K in HandlerPath]: HandlerMap[K] extends Handler<any, any, infer X>
37
- ? X
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<
@@ -26,7 +26,6 @@ export class Handler {
26
26
  if (this.requestDataSchema) {
27
27
  try {
28
28
  data = await this.requestDataSchema.validate(data, {
29
- strict: true,
30
29
  abortEarly: true,
31
30
  stripUnknown: true
32
31
  });
@@ -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.10.0",
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": {