@oxiaom/adoremix 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/adoremix.js +11 -0
- package/bin/postinstall-hint.js +14 -0
- package/package.json +50 -0
- package/src/cli.js +263 -0
- package/src/config/defaults.js +98 -0
- package/src/config/index.js +102 -0
- package/src/config/ini.js +60 -0
- package/src/config/wizard.js +124 -0
- package/src/index.js +7 -0
- package/src/install.js +173 -0
- package/src/logger.js +29 -0
- package/src/paths.js +94 -0
- package/src/runner/index.js +6 -0
- package/src/runner/pid.js +65 -0
- package/src/runner/signals.js +15 -0
- package/src/runner/spawn.js +153 -0
- package/src/service/index.js +15 -0
- package/src/service/linux.js +139 -0
- package/src/service/windows.js +88 -0
- package/templates/adoremix.service.tmpl +19 -0
- package/templates/winservice.js.tmpl +66 -0
package/bin/adoremix.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const cli = require('../src/cli');
|
|
5
|
+
cli.run(process.argv.slice(2)).then((code) => {
|
|
6
|
+
process.exit(code || 0);
|
|
7
|
+
}).catch((err) => {
|
|
8
|
+
const logger = require('../src/logger');
|
|
9
|
+
logger.error('未捕获的错误:', err);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const logger = require('../src/logger');
|
|
4
|
+
|
|
5
|
+
logger.log('');
|
|
6
|
+
logger.log(' AdoreMix CLI 已安装。');
|
|
7
|
+
logger.log('');
|
|
8
|
+
logger.log(' 下一步:');
|
|
9
|
+
logger.log(' 1. 运行 adoremix install 初始化工作目录、复制资源并生成 config.ini');
|
|
10
|
+
logger.log(' 2. 运行 adoremix start 启动服务(前台)');
|
|
11
|
+
logger.log(' 3. 运行 adoremix service install 注册开机自启动');
|
|
12
|
+
logger.log('');
|
|
13
|
+
logger.log(' 帮助:adoremix --help');
|
|
14
|
+
logger.log('');
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oxiaom/adoremix",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AdoreMix broadcast server - cross-platform installer, runner and service manager",
|
|
5
|
+
"bin": {
|
|
6
|
+
"adoremix": "./bin/adoremix.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./src/index.js",
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=16"
|
|
12
|
+
},
|
|
13
|
+
"optionalDependencies": {
|
|
14
|
+
"@oxiaom/adoremix-win32-x64": "1.0.0",
|
|
15
|
+
"@oxiaom/adoremix-linux-x64": "1.0.0",
|
|
16
|
+
"@oxiaom/adoremix-linux-arm64": "1.0.0",
|
|
17
|
+
"@oxiaom/adoremix-linux-arm": "1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^12.1.0",
|
|
21
|
+
"ini": "^4.1.3",
|
|
22
|
+
"pidusage": "^3.0.2",
|
|
23
|
+
"fs-extra": "^11.2.0",
|
|
24
|
+
"prompts": "^2.4.2"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"node-windows": "^1.0.0-beta.8"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"node-windows": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"postinstall": "node ./bin/postinstall-hint.js"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"bin",
|
|
39
|
+
"src",
|
|
40
|
+
"templates"
|
|
41
|
+
],
|
|
42
|
+
"keywords": [
|
|
43
|
+
"adoremix",
|
|
44
|
+
"broadcast",
|
|
45
|
+
"server",
|
|
46
|
+
"qt",
|
|
47
|
+
"installer"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT"
|
|
50
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { Command } = require('commander');
|
|
5
|
+
const pkg = require('../package.json');
|
|
6
|
+
const paths = require('./paths');
|
|
7
|
+
const logger = require('./logger');
|
|
8
|
+
|
|
9
|
+
function resolveWorkdir(option) {
|
|
10
|
+
if (option) return option;
|
|
11
|
+
return paths.defaultWorkdir();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildProgram() {
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program
|
|
17
|
+
.name('adoremix')
|
|
18
|
+
.description('AdoreMix 广播服务器 - 跨平台安装、运行与服务管理')
|
|
19
|
+
.version(pkg.version, '-v, --version');
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('version')
|
|
23
|
+
.description('打印主包、平台子包和原生二进制信息')
|
|
24
|
+
.action(() => {
|
|
25
|
+
logger.log(`adoremix CLI v${pkg.version}`);
|
|
26
|
+
logger.log(`platform/arch ${paths.PLATFORM_KEY}`);
|
|
27
|
+
logger.log(`platform pkg ${paths.PLATFORM_PKG || '(none)'}`);
|
|
28
|
+
try {
|
|
29
|
+
const n = paths.loadNative();
|
|
30
|
+
logger.log(`native root ${n.root}`);
|
|
31
|
+
logger.log(`native bin ${n.binName} (exists: ${n.exists() ? 'yes' : 'no'})`);
|
|
32
|
+
if (n.notice) logger.warn(`notice ${n.notice}`);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
logger.warn(`native 未加载:${e.message.split('\n')[0]}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command('install')
|
|
40
|
+
.description('初始化工作目录、复制资源、安装协作依赖、生成 config.ini')
|
|
41
|
+
.option('--workdir <path>', '工作目录路径')
|
|
42
|
+
.option('--force', '覆盖已有安装')
|
|
43
|
+
.option('--skip-npm-install', '跳过工作目录内 npm install(tts.js 等协作脚本依赖)')
|
|
44
|
+
.option('--no-interactive', '跳过配置向导,使用默认值')
|
|
45
|
+
.action(async (opts) => {
|
|
46
|
+
const install = require('./install');
|
|
47
|
+
const code = await install.runInstall({
|
|
48
|
+
workdir: resolveWorkdir(opts.workdir),
|
|
49
|
+
force: !!opts.force,
|
|
50
|
+
skipNpmInstall: !!opts.skipNpmInstall,
|
|
51
|
+
interactive: opts.interactive !== false
|
|
52
|
+
});
|
|
53
|
+
process.exitCode = code || 0;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const config = program
|
|
57
|
+
.command('config')
|
|
58
|
+
.description('读取或修改 config.ini');
|
|
59
|
+
|
|
60
|
+
config
|
|
61
|
+
.command('get <key>')
|
|
62
|
+
.description('读取配置项(支持 dotted:Settings.LocalIP)')
|
|
63
|
+
.option('--workdir <path>')
|
|
64
|
+
.action((key, opts) => {
|
|
65
|
+
const cfg = require('./config');
|
|
66
|
+
const workdir = resolveWorkdir(opts.workdir);
|
|
67
|
+
const v = cfg.getConfigValue(workdir, key);
|
|
68
|
+
if (v === undefined) {
|
|
69
|
+
logger.warn(`键不存在:${key}`);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
logger.log(`${key} = ${v}`);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
config
|
|
77
|
+
.command('set <key> <value>')
|
|
78
|
+
.description('写入配置项')
|
|
79
|
+
.option('--workdir <path>')
|
|
80
|
+
.action((key, value, opts) => {
|
|
81
|
+
const cfg = require('./config');
|
|
82
|
+
const workdir = resolveWorkdir(opts.workdir);
|
|
83
|
+
cfg.setConfigValue(workdir, key, value);
|
|
84
|
+
logger.ok(`${key} = ${value}`);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
config
|
|
88
|
+
.command('list')
|
|
89
|
+
.description('打印全部配置')
|
|
90
|
+
.option('--workdir <path>')
|
|
91
|
+
.action((opts) => {
|
|
92
|
+
const cfg = require('./config');
|
|
93
|
+
const workdir = resolveWorkdir(opts.workdir);
|
|
94
|
+
const obj = cfg.listConfig(workdir);
|
|
95
|
+
if (!obj) {
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const flat = require('./config/ini').flatten(obj);
|
|
100
|
+
for (const k of Object.keys(flat)) {
|
|
101
|
+
logger.log(`${k} = ${flat[k]}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
config
|
|
106
|
+
.command('init')
|
|
107
|
+
.description('生成默认 config.ini(已存在需 --force)')
|
|
108
|
+
.option('--workdir <path>')
|
|
109
|
+
.option('--force', '覆盖已有 config.ini')
|
|
110
|
+
.option('--no-interactive', '跳过向导')
|
|
111
|
+
.action(async (opts) => {
|
|
112
|
+
const cfg = require('./config');
|
|
113
|
+
const workdir = resolveWorkdir(opts.workdir);
|
|
114
|
+
await cfg.ensureConfig(workdir, {
|
|
115
|
+
force: !!opts.force,
|
|
116
|
+
interactive: opts.interactive !== false
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const runner = require('./runner');
|
|
121
|
+
const nativeRef = () => {
|
|
122
|
+
try { return paths.loadNative(); }
|
|
123
|
+
catch (e) { logger.error(e.message); process.exit(1); }
|
|
124
|
+
};
|
|
125
|
+
const runnerOpts = (opts) => ({
|
|
126
|
+
workdir: resolveWorkdir(opts.workdir),
|
|
127
|
+
native: nativeRef(),
|
|
128
|
+
configPath: path.join(resolveWorkdir(opts.workdir), 'config.ini'),
|
|
129
|
+
daemon: opts.daemon === true
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
program
|
|
133
|
+
.command('start')
|
|
134
|
+
.description('启动 AdoreMix(默认前台,加 --daemon 后台)')
|
|
135
|
+
.option('--workdir <path>')
|
|
136
|
+
.option('--daemon', '后台运行(nohup 风格)')
|
|
137
|
+
.action((opts) => {
|
|
138
|
+
try {
|
|
139
|
+
if (opts.daemon) runner.startDaemon(runnerOpts(opts));
|
|
140
|
+
else runner.startForeground(runnerOpts(opts));
|
|
141
|
+
} catch (e) {
|
|
142
|
+
logger.error(e.message);
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
program
|
|
148
|
+
.command('stop')
|
|
149
|
+
.description('停止 AdoreMix(读 pidfile 并 kill)')
|
|
150
|
+
.option('--workdir <path>')
|
|
151
|
+
.option('--timeout <ms>', 'SIGTERM 超时后 SIGKILL 的毫秒数', parseInt)
|
|
152
|
+
.action(async (opts) => {
|
|
153
|
+
process.exitCode = await runner.stop(runnerOpts(opts));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
program
|
|
157
|
+
.command('status')
|
|
158
|
+
.description('查看运行状态')
|
|
159
|
+
.option('--workdir <path>')
|
|
160
|
+
.action(async (opts) => {
|
|
161
|
+
process.exitCode = await runner.status(runnerOpts(opts));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
program
|
|
165
|
+
.command('restart')
|
|
166
|
+
.description('重启')
|
|
167
|
+
.option('--workdir <path>')
|
|
168
|
+
.option('--daemon', '后台模式重启')
|
|
169
|
+
.action(async (opts) => {
|
|
170
|
+
const r = await runner.restart(runnerOpts(opts));
|
|
171
|
+
if (r && r.alreadyRunning === false && r.pid) {
|
|
172
|
+
// ok
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command('logs')
|
|
178
|
+
.description('查看日志(默认 var/app.log)')
|
|
179
|
+
.option('--workdir <path>')
|
|
180
|
+
.option('-f, --follow', '实时跟随')
|
|
181
|
+
.option('--file <path>', '指定日志文件')
|
|
182
|
+
.option('-n, --lines <n>', '初始打印行数', parseInt, 50)
|
|
183
|
+
.action((opts) => {
|
|
184
|
+
const wp = paths.workdirPaths(resolveWorkdir(opts.workdir));
|
|
185
|
+
const file = opts.file || wp.logfile;
|
|
186
|
+
const fs = require('fs');
|
|
187
|
+
const { spawn } = require('child_process');
|
|
188
|
+
if (!fs.existsSync(file)) {
|
|
189
|
+
logger.warn(`日志文件不存在 ${file}`);
|
|
190
|
+
process.exitCode = 1;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!opts.follow) {
|
|
194
|
+
const tail = spawn(process.platform === 'win32' ? 'more' : 'tail', process.platform === 'win32' ? [file] : ['-n', String(opts.lines), file], { stdio: 'inherit' });
|
|
195
|
+
tail.on('exit', (c) => process.exit(c || 0));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (process.platform === 'win32') {
|
|
199
|
+
logger.warn('Windows 不支持 -f,请用 PowerShell 自行 Get-Content -Wait');
|
|
200
|
+
const tail = spawn('more', [file], { stdio: 'inherit' });
|
|
201
|
+
tail.on('exit', (c) => process.exit(c || 0));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const tail = spawn('tail', ['-n', String(opts.lines), '-f', file], { stdio: 'inherit' });
|
|
205
|
+
process.on('SIGINT', () => tail.kill('SIGINT'));
|
|
206
|
+
tail.on('exit', (c) => process.exit(c || 0));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const service = program
|
|
210
|
+
.command('service')
|
|
211
|
+
.description('系统服务管理(开机自启)');
|
|
212
|
+
|
|
213
|
+
service
|
|
214
|
+
.command('install')
|
|
215
|
+
.description('注册系统服务并启动(Linux: systemd / Windows: node-windows)')
|
|
216
|
+
.option('--workdir <path>')
|
|
217
|
+
.action((opts) => {
|
|
218
|
+
const svc = require('./service');
|
|
219
|
+
try {
|
|
220
|
+
process.exitCode = svc.install({ workdir: resolveWorkdir(opts.workdir) });
|
|
221
|
+
} catch (e) {
|
|
222
|
+
logger.error(e.message);
|
|
223
|
+
process.exitCode = 1;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
service
|
|
228
|
+
.command('uninstall')
|
|
229
|
+
.description('卸载系统服务')
|
|
230
|
+
.option('--workdir <path>')
|
|
231
|
+
.action((opts) => {
|
|
232
|
+
const svc = require('./service');
|
|
233
|
+
try {
|
|
234
|
+
process.exitCode = svc.uninstall({ workdir: resolveWorkdir(opts.workdir) });
|
|
235
|
+
} catch (e) {
|
|
236
|
+
logger.error(e.message);
|
|
237
|
+
process.exitCode = 1;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
service
|
|
242
|
+
.command('status')
|
|
243
|
+
.description('查看系统服务状态')
|
|
244
|
+
.action(() => {
|
|
245
|
+
const svc = require('./service');
|
|
246
|
+
try {
|
|
247
|
+
process.exitCode = svc.status();
|
|
248
|
+
} catch (e) {
|
|
249
|
+
logger.error(e.message);
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return program;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function run(argv) {
|
|
258
|
+
const program = buildProgram();
|
|
259
|
+
await program.parseAsync(argv, { from: 'user' });
|
|
260
|
+
return process.exitCode || 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = { run, buildProgram };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
Settings: {
|
|
5
|
+
ISLOCALHOST: false,
|
|
6
|
+
wbport: 6082,
|
|
7
|
+
isopenrizhi: true,
|
|
8
|
+
exename: '',
|
|
9
|
+
Meida_port: 6002,
|
|
10
|
+
Meida_ip: '127.0.0.1',
|
|
11
|
+
askey: '96195ab3abb66e4d1ed2e2f1df4728c9',
|
|
12
|
+
AryLen: 5,
|
|
13
|
+
Check_: 1000,
|
|
14
|
+
RDDATAPORT: 6036,
|
|
15
|
+
wxcxpreurl: 'https://example.com/',
|
|
16
|
+
Check_len: 3,
|
|
17
|
+
DPort: 3333,
|
|
18
|
+
DataPort: 6002,
|
|
19
|
+
EnableRedis: false,
|
|
20
|
+
LocalIP: '127.0.0.1',
|
|
21
|
+
ManagePort: 6007,
|
|
22
|
+
Netcross_Port0: 6000,
|
|
23
|
+
Netcross_Port1: 6001,
|
|
24
|
+
OPort0: 6000,
|
|
25
|
+
OPort1: 6001,
|
|
26
|
+
DataPortBack2: 6030,
|
|
27
|
+
DataPortBack3: 6031,
|
|
28
|
+
bitrate: 48,
|
|
29
|
+
Pastcmd: 6008,
|
|
30
|
+
BjPort: 6004,
|
|
31
|
+
PackSize: 1024,
|
|
32
|
+
RedisIP: '127.0.0.1',
|
|
33
|
+
RedisPort: 6379,
|
|
34
|
+
RedisAUTH: '',
|
|
35
|
+
PS1: 6003,
|
|
36
|
+
PS2: 6006,
|
|
37
|
+
Fip: '127.0.0.1',
|
|
38
|
+
Fport: 6333,
|
|
39
|
+
TSWait: 3000,
|
|
40
|
+
key: 'EQTCP2017%$#@!',
|
|
41
|
+
task_ip: '127.0.0.1',
|
|
42
|
+
sqltask_port: 3307,
|
|
43
|
+
task_username: 'root',
|
|
44
|
+
task_passwd: '',
|
|
45
|
+
basename: 'adore',
|
|
46
|
+
username: 'root',
|
|
47
|
+
task_id: 101,
|
|
48
|
+
nocdn: true,
|
|
49
|
+
dbk: 3,
|
|
50
|
+
taskstart: 0,
|
|
51
|
+
taskend: 86400,
|
|
52
|
+
preurl: 'http://127.0.0.1:12080/',
|
|
53
|
+
ttsxfAPPID: '',
|
|
54
|
+
ttsxfAPISecret: '',
|
|
55
|
+
ttsxfAPIKey: '',
|
|
56
|
+
cutepath: 'etc/docroot',
|
|
57
|
+
cuteport: 12083
|
|
58
|
+
},
|
|
59
|
+
listener: {
|
|
60
|
+
port: 12080,
|
|
61
|
+
readTimeout: 60000,
|
|
62
|
+
maxRequestSize: 16000000,
|
|
63
|
+
maxMultiPartSize: 100000000,
|
|
64
|
+
minThreads: 100,
|
|
65
|
+
maxThreads: 3000,
|
|
66
|
+
cleanupInterval: 60000
|
|
67
|
+
},
|
|
68
|
+
templates: {
|
|
69
|
+
path: './etc/templates',
|
|
70
|
+
suffix: '.fucker',
|
|
71
|
+
encoding: 'UTF-8',
|
|
72
|
+
cacheSize: 1000000,
|
|
73
|
+
cacheTime: 60000
|
|
74
|
+
},
|
|
75
|
+
docroot: {
|
|
76
|
+
path: './etc/docroot',
|
|
77
|
+
encoding: 'UTF-8',
|
|
78
|
+
maxAge: 60000,
|
|
79
|
+
cacheTime: 60000,
|
|
80
|
+
cacheSize: 1000000,
|
|
81
|
+
maxCachedFileSize: 65536
|
|
82
|
+
},
|
|
83
|
+
sessions: {
|
|
84
|
+
expirationTime: 3600000,
|
|
85
|
+
cookieName: 'sessionid',
|
|
86
|
+
cookiePath: '/',
|
|
87
|
+
cookieComment: 'Identifies the user'
|
|
88
|
+
},
|
|
89
|
+
logging: {
|
|
90
|
+
fileName: './reqhttp.log',
|
|
91
|
+
minLevel: 'WARNING',
|
|
92
|
+
bufferSize: 100,
|
|
93
|
+
maxSize: 1000000,
|
|
94
|
+
maxBackups: 2,
|
|
95
|
+
timestampFormat: 'dd.MM.yyyy hh:mm:ss.zzz',
|
|
96
|
+
msgFormat: '{timestamp} {typeNr} {type} {thread} {msg}'
|
|
97
|
+
}
|
|
98
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const ini = require('./ini');
|
|
6
|
+
const defaults = require('./defaults');
|
|
7
|
+
const wizard = require('./wizard');
|
|
8
|
+
const logger = require('../logger');
|
|
9
|
+
const paths = require('../paths');
|
|
10
|
+
|
|
11
|
+
function mergeDeep(target, src) {
|
|
12
|
+
if (target == null) target = {};
|
|
13
|
+
for (const k of Object.keys(src || {})) {
|
|
14
|
+
if (src[k] && typeof src[k] === 'object' && !Array.isArray(src[k])) {
|
|
15
|
+
target[k] = mergeDeep(target[k] || {}, src[k]);
|
|
16
|
+
} else {
|
|
17
|
+
target[k] = src[k];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function configPath(workdir) {
|
|
24
|
+
return path.join(workdir, 'config.ini');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readConfig(workdir) {
|
|
28
|
+
const p = configPath(workdir);
|
|
29
|
+
if (!fs.existsSync(p)) return null;
|
|
30
|
+
return ini.read(p);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeConfig(workdir, obj) {
|
|
34
|
+
const p = configPath(workdir);
|
|
35
|
+
ini.write(p, obj);
|
|
36
|
+
return p;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function ensureConfig(workdir, opts) {
|
|
40
|
+
opts = opts || {};
|
|
41
|
+
const p = configPath(workdir);
|
|
42
|
+
if (fs.existsSync(p) && !opts.force) {
|
|
43
|
+
logger.info(`配置已存在 ${p}`);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
let merged = mergeDeep({}, defaults);
|
|
47
|
+
if (opts.interactive !== false) {
|
|
48
|
+
logger.log('');
|
|
49
|
+
logger.log('=== AdoreMix 配置向导 ===');
|
|
50
|
+
logger.log('提示:可直接回车使用默认值;密码类项回车留空。');
|
|
51
|
+
logger.log('');
|
|
52
|
+
const answers = await wizard.run(merged.Settings || {});
|
|
53
|
+
merged.Settings = Object.assign({}, merged.Settings, answers);
|
|
54
|
+
}
|
|
55
|
+
writeConfig(workdir, merged);
|
|
56
|
+
logger.ok(`配置已生成 ${p}`);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function listConfig(workdir) {
|
|
61
|
+
const obj = readConfig(workdir);
|
|
62
|
+
if (!obj) {
|
|
63
|
+
logger.warn('配置不存在,请先 adoremix install 或 adoremix config init');
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return obj;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getConfigValue(workdir, dottedKey) {
|
|
70
|
+
const obj = readConfig(workdir);
|
|
71
|
+
if (!obj) return undefined;
|
|
72
|
+
return ini.get(obj, dottedKey);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setConfigValue(workdir, dottedKey, value) {
|
|
76
|
+
let obj = readConfig(workdir);
|
|
77
|
+
if (!obj) obj = mergeDeep({}, defaults);
|
|
78
|
+
const typed = autoCast(value);
|
|
79
|
+
ini.set(obj, dottedKey, typed);
|
|
80
|
+
writeConfig(workdir, obj);
|
|
81
|
+
return obj;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function autoCast(value) {
|
|
85
|
+
if (value === 'true') return true;
|
|
86
|
+
if (value === 'false') return false;
|
|
87
|
+
if (/^-?\d+$/.test(value)) return parseInt(value, 10);
|
|
88
|
+
if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
defaults,
|
|
94
|
+
configPath,
|
|
95
|
+
readConfig,
|
|
96
|
+
writeConfig,
|
|
97
|
+
ensureConfig,
|
|
98
|
+
listConfig,
|
|
99
|
+
getConfigValue,
|
|
100
|
+
setConfigValue,
|
|
101
|
+
mergeDeep
|
|
102
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const ini = require('ini');
|
|
5
|
+
|
|
6
|
+
function read(configPath) {
|
|
7
|
+
if (!fs.existsSync(configPath)) return {};
|
|
8
|
+
const txt = fs.readFileSync(configPath, 'utf8');
|
|
9
|
+
return ini.parse(txt);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function write(configPath, obj) {
|
|
13
|
+
const txt = ini.stringify(obj, {
|
|
14
|
+
section: '',
|
|
15
|
+
whitespace: true,
|
|
16
|
+
align: false,
|
|
17
|
+
sort: false
|
|
18
|
+
});
|
|
19
|
+
fs.writeFileSync(configPath, txt, { encoding: 'utf8' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function get(obj, dottedKey) {
|
|
23
|
+
const parts = dottedKey.split('.');
|
|
24
|
+
let cur = obj;
|
|
25
|
+
for (const p of parts) {
|
|
26
|
+
if (cur == null || typeof cur !== 'object') return undefined;
|
|
27
|
+
cur = cur[p];
|
|
28
|
+
}
|
|
29
|
+
return cur;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function set(obj, dottedKey, value) {
|
|
33
|
+
const parts = dottedKey.split('.');
|
|
34
|
+
let cur = obj;
|
|
35
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
36
|
+
if (cur[parts[i]] == null || typeof cur[parts[i]] !== 'object') {
|
|
37
|
+
cur[parts[i]] = {};
|
|
38
|
+
}
|
|
39
|
+
cur = cur[parts[i]];
|
|
40
|
+
}
|
|
41
|
+
cur[parts[parts.length - 1]] = value;
|
|
42
|
+
return obj;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function flatten(obj, prefix, out) {
|
|
46
|
+
out = out || {};
|
|
47
|
+
prefix = prefix || '';
|
|
48
|
+
for (const k of Object.keys(obj)) {
|
|
49
|
+
const v = obj[k];
|
|
50
|
+
const full = prefix ? `${prefix}.${k}` : k;
|
|
51
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
52
|
+
flatten(v, full, out);
|
|
53
|
+
} else {
|
|
54
|
+
out[full] = v;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { read, write, get, set, flatten };
|