mm_machine 2.9.3 → 2.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (8) hide show
  1. package/dir_scanner.js +262 -0
  2. package/drive.js +668 -490
  3. package/id_gen.js +138 -0
  4. package/index.js +1141 -921
  5. package/mod.js +742 -711
  6. package/package.json +6 -2
  7. package/registry.js +634 -0
  8. package/word.js +209 -0
package/drive.js CHANGED
@@ -1,377 +1,394 @@
1
- const prettier = require('@prettier/sync');
2
- require('mm_tpl');
3
- let { conf } = require('mm_config');
4
- // const { Word } = require('./ulits/word.js');
5
- const { Mod } = require('./mod');
6
-
7
- if (!$.prettyCode) {
8
- $.prettyCode = function (code, options) {
9
- return prettier.format(code, options);
10
- };
11
- }
1
+ /**
2
+ * Drive 驱动类
3
+ * 提供 JSON 配置文件加载、JS 脚本文件热重载、模板文件生成等能力
4
+ * @class Drive
5
+ * @augments Mod
6
+ */
7
+ var path = require('path');
8
+ var { Mod } = require('./mod');
12
9
 
13
10
  /**
14
- * 驱动基础类
15
- * @class
11
+ * 静态配置默认值
16
12
  */
17
- class Drive extends Mod {
18
- // 配置项
19
- static config = {
20
- // 名称, 由中英文和下"_"组成, 用于卸载接口 例如: demo
21
- name: '',
22
- // 标题, 介绍作用
23
- title: '',
24
- // 描述, 用于描述该有什么用的
25
- description: '',
26
- // 文件路径, 当调用函数不存在时,会先从文件中加载
27
- main: './index.js',
28
- // 回调函数名 用于决定调用脚本的哪个函数
29
- func_name: 'main',
30
- // 作用域(同时作为检索的后缀名)
31
- scope: $.val && $.val.scope ? $.val.scope : 'server',
32
- // 排序
33
- sort: 10,
34
- // 状态, 0表示未启用, 1表示启用
35
- state: 1,
36
- // 显示, 0表示不显示, 1表示显示
37
- show: 0,
38
- // 是否结束,可选,默认false
39
- end: false
40
- };
13
+ var defaultConfig = {
14
+ name: '',
15
+ title: '',
16
+ description: '',
17
+ main: '',
18
+ func_name: 'main',
19
+ scope: 0,
20
+ sort: 100,
21
+ mode: 3,
22
+ state: 1
23
+ };
41
24
 
25
+ class Drive extends Mod {
42
26
  /**
43
27
  * 构造函数
44
- * @param {object} config 配置项
45
- * @param {object} parent 父项
46
- * @class
28
+ * @param {object} config 配置参数
29
+ * @param {object} [parent] 父对象
47
30
  */
48
31
  constructor(config, parent) {
49
- super({ ...Drive.config, ...config }, parent);
50
- // 当前配置文件路径
32
+ super({ ...defaultConfig, ...config, parent: parent });
33
+ this._config = {};
34
+ this._script = '';
35
+ this.is_loaded = false;
36
+ this._main_func = null;
37
+ this._watch = null;
38
+ this._notify_timer = null;
39
+ this._script_keys = null;
51
40
  this.config_file = '';
52
-
53
- // 当前脚本文件路径
54
- this.script_file = '';
55
-
56
- // 模式: 1.生产模式 2.热更新模式 3.热重载模式 4.热更新+重载模式 5.重载模式
57
- this.mode = 1;
41
+ this.config = { ...defaultConfig, ...config };
42
+ this.mode = this.config.mode;
58
43
  }
59
44
  }
60
45
 
46
+ // ==================== 事件管理 ====================
47
+
61
48
  /**
62
49
  * 获取事件触发器
63
50
  * @returns {object} 事件触发器
64
51
  */
65
52
  Drive.prototype.getEventer = function () {
66
- return $.eventer;
53
+ return typeof $.eventer !== 'undefined' ? $.eventer : this;
67
54
  };
68
55
 
69
56
  /**
70
- * 配置驱动
57
+ * 获取配置
58
+ * @returns {object} 配置对象
71
59
  */
72
- Drive.prototype._preset = function () {
73
- let file = this.config.main;
74
- if (file) {
75
- this.script_file = file.fullname(this.getDir());
76
- }
60
+ Drive.prototype.getConfig = function () {
61
+ return this.config;
77
62
  };
78
63
 
79
64
  /**
80
- * 获取配置
81
- * @param {string} file 配置文件路径
82
- * @returns {object} 配置项
65
+ * 获取模板目录
66
+ * @returns {string} 模板目录
83
67
  */
84
- Drive.prototype._loadConfig = function (file) {
85
- if (file) {
86
- this.config_file = file;
87
- }
88
- // 加载配置
89
- let config = $.loadJson(this.config_file, (cg) => {
90
- this.setConfig(cg);
91
- return cg;
92
- });
93
- if (!config) {
94
- this.newConfig(this.config_file);
95
- config = $.loadJson(this.config_file, (cg) => {
96
- this.setConfig(cg);
97
- return cg;
98
- });
68
+ Drive.prototype.getTplDir = function () {
69
+ if (!this.getParent) return '';
70
+ var parent = this.getParent();
71
+ return parent ? parent.getTplDir ? parent.getTplDir() : '' : '';
72
+ };
73
+
74
+ // ==================== 目录管理 ====================
75
+
76
+ /**
77
+ * 获取模块目录
78
+ * 优先级:脚本文件目录 > 配置文件目录 > 父级目录 > 按名称拼接
79
+ * @returns {string} 目录路径
80
+ */
81
+ Drive.prototype.getDir = function () {
82
+ if (this._dir) {
83
+ return this._dir;
84
+ }
85
+ if (this._script) {
86
+ this._dir = path.dirname(this._script);
87
+ } else if (this.config_file) {
88
+ this._dir = path.dirname(this.config_file);
89
+ } else if (this.getParent && this.getParent()) {
90
+ var parent_dir = this.getParent().getDir ? this.getParent().getDir() : '';
91
+ if (parent_dir && this.config.name) {
92
+ this._dir = path.resolve(parent_dir, this.config.name);
93
+ }
99
94
  }
100
- this.setConfig(config);
101
- return config;
95
+ return this._dir || '';
102
96
  };
103
97
 
98
+ // ==================== 配置管理 ====================
99
+
104
100
  /**
105
- * 加载配置
106
- * @param {string} file 配置文件路径
101
+ * 预设处理
102
+ * 计算脚本文件路径
103
+ * @private
107
104
  */
108
- Drive.prototype.loadConfig = function (file) {
109
- this._loadConfig(file);
105
+ Drive.prototype._preset = function () {
106
+ var main = this.config.main;
107
+ if (main && this.config_file) {
108
+ this._script = path.resolve(path.dirname(this.config_file), main);
109
+ }
110
110
  };
111
111
 
112
112
  /**
113
113
  * 设置配置
114
- * @param {object} config 配置对象
115
- * @returns {object} 配置项
114
+ * 合并配置并触发变更通知
115
+ * @param {object} cfg 配置对象
116
+ * @returns {object} 合并后的配置
116
117
  */
117
- Drive.prototype.setConfig = function (config) {
118
- if (config) {
119
- // 如果main改变,重置加载状态
120
- if (config.main !== this.config.main) {
121
- this.is_loaded = false;
122
- }
118
+ Drive.prototype.setConfig = function (cfg) {
119
+ if (!cfg) {
120
+ return this.config;
121
+ }
123
122
 
124
- if (this.config_file) {
125
- // 合并配置并应用配置处理
126
- this.config = conf(
127
- {
128
- ...this.config,
129
- ...config
130
- },
131
- this.config_file
132
- );
133
- } else {
134
- Object.assign(this.config, config);
135
- }
123
+ // 如果 main 改变,重置加载状态
124
+ if (cfg.main !== undefined && cfg.main !== this.config.main) {
125
+ this.is_loaded = false;
136
126
  }
127
+
128
+ this.config = { ...this.config, ...cfg };
137
129
  this._preset();
138
- // 通知父级刷新快照
139
- this._notifySnapshot();
140
130
  return this.config;
141
131
  };
142
132
 
143
133
  /**
144
- * 通知父级Manager刷新快照(防抖)
134
+ * 加载配置
135
+ * 从 JSON 配置文件读取配置并合并
136
+ * @param {string} file 配置文件路径
137
+ * @returns {object} 配置对象
145
138
  */
146
- Drive.prototype._notifySnapshot = function () {
147
- if (!this.getParent) {
148
- return;
149
- }
150
- var parent = this.getParent();
151
- if (!parent || !parent.config || !parent.config.use_snapshot) {
152
- return;
153
- }
154
- // 防抖:避免频繁写文件,500ms内只刷新一次
155
- if (this._snapshot_timer) {
156
- return;
139
+ Drive.prototype.loadConfig = function (file) {
140
+ if (file) {
141
+ this.config_file = file;
157
142
  }
158
- this._snapshot_timer = setTimeout(() => {
159
- this._snapshot_timer = null;
160
- if (parent.refreshSnapshot) {
161
- parent.refreshSnapshot();
162
- }
163
- }, 500);
143
+ return this._loadConfig();
164
144
  };
165
145
 
166
146
  /**
167
- * 获取配置
168
- * @returns {object} 配置项
147
+ * 内部加载配置
148
+ * 支持热更新模式($.loadJson)和普通模式(require)
149
+ * @returns {object} 配置对象
150
+ * @private
169
151
  */
170
- Drive.prototype.getConfig = function () {
171
- return this.config;
172
- };
152
+ Drive.prototype._loadConfig = function () {
153
+ if (!this.config_file) {
154
+ return this.config;
155
+ }
173
156
 
174
- /**
175
- * 获取当前文件
176
- * @returns {object} 当前文件对象
177
- */
178
- Drive.prototype.getDir = function () {
179
- let dir = this._dir;
180
- if (!dir) {
181
- if (this.script_file) {
182
- dir = this.script_file.dirname();
183
- this._dir = dir;
184
- } else if (this.config_file) {
185
- dir = this.config_file.dirname();
186
- this._dir = dir;
187
- } else if (this.getParent) {
188
- let parent_dir = this.getParent()?.getDir() || '';
189
- if (parent_dir) {
190
- dir = this.config.name.fullname(parent_dir);
157
+ try {
158
+ var config;
159
+ if (this.mode === 2 || this.mode === 3 || this.mode === 4) {
160
+ // 热更新模式:使用 $.loadJson
161
+ if (typeof $.loadJson === 'function') {
162
+ config = $.loadJson(this.config_file, (cg) => {
163
+ this.setConfig(cg);
164
+ return cg;
165
+ });
191
166
  }
192
167
  }
168
+ if (!config) {
169
+ // 普通模式:使用 require 或 readFileSync
170
+ try {
171
+ config = require(this.config_file);
172
+ } catch {
173
+ var fs = require('fs');
174
+ var content = fs.readFileSync(this.config_file, 'utf8');
175
+ config = JSON.parse(content);
176
+ }
177
+ }
178
+ if (config) {
179
+ this.setConfig(config);
180
+ }
181
+ } catch {
182
+ // 配置文件不存在或解析失败,使用默认配置
193
183
  }
194
- return dir;
195
- };
196
184
 
197
- /**
198
- * 获取模板目录
199
- * @returns {string} 模板目录
200
- */
201
- Drive.prototype._getTplDir = function () {
202
- return this.getParent()?.getTplDir() || '';
185
+ return this.config;
203
186
  };
204
187
 
205
188
  /**
206
- * 移除加载的脚本模块
189
+ * 加载配置文件
190
+ * @param {string} file 文件路径
191
+ * @param {string} name 配置项名称
192
+ * @returns {object|null} 配置对象
207
193
  */
208
- Drive.prototype._remove = function () {
209
- let file = this._getScriptFile();
210
- if (!file) return;
211
-
194
+ Drive.prototype.loadFile = function (file, name) {
212
195
  try {
213
- if (this.mode === 3 || this.mode === 4) {
214
- // 移除模块和监听
215
- $.mod.unload(file);
216
- } else {
217
- // 移除模块缓存
218
- let filename = require.resolve(file);
219
- if (require.cache[filename]) {
220
- require.cache[filename] = null;
221
- delete require.cache[filename];
196
+ var fs = require('fs');
197
+ var filename = path.resolve(this.getDir(), file);
198
+ var text = '';
199
+
200
+ try {
201
+ text = fs.readFileSync(filename, 'utf8');
202
+ } catch {
203
+ // 文件不存在,创建新的配置文件
204
+ this.newConfig(filename);
205
+ try {
206
+ text = fs.readFileSync(filename, 'utf8');
207
+ } catch {
208
+ return null;
222
209
  }
223
210
  }
211
+
212
+ if (!text) return null;
213
+
214
+ var config = this._loadHotReload(filename);
215
+ var final_config = this._findConfigByName(config, name);
216
+ return final_config;
224
217
  } catch (err) {
225
- this.log('error', `移除加载的脚本模块失败: `, err);
218
+ this.log('error', '加载配置文件失败: ', err);
219
+ return null;
226
220
  }
227
221
  };
228
222
 
229
223
  /**
230
- * 卸载脚本
224
+ * 根据模式加载配置(支持热更新)
225
+ * @param {string} file 完整文件路径
226
+ * @returns {object|Array|null} 配置对象
227
+ * @private
231
228
  */
232
- Drive.prototype.unloadScript = function () {
233
- this._remove();
234
- this._script_keys = null;
235
- this.is_loaded = false;
229
+ Drive.prototype._loadHotReload = function (file) {
230
+ if (this.mode === 2 || this.mode === 3 || this.mode === 4) {
231
+ if (typeof $.loadJson === 'function') {
232
+ return $.loadJson(file, this._handleHotReload.bind(this));
233
+ }
234
+ }
235
+ try {
236
+ return require(file);
237
+ } catch {
238
+ var fs = require('fs');
239
+ var content = fs.readFileSync(file, 'utf8');
240
+ return JSON.parse(content);
241
+ }
236
242
  };
237
243
 
238
244
  /**
239
- * 重新加载脚本
240
- * @returns {object | null} 返回加载的模块对象
245
+ * 热更新回调处理
246
+ * @param {object} config 配置对象
247
+ * @private
241
248
  */
242
- Drive.prototype.reloadScript = function () {
243
- this.unloadScript();
244
- return this.loadScript();
249
+ Drive.prototype._handleHotReload = function (config) {
250
+ this.setConfig(config);
251
+ this._reloadIfNeeded();
252
+ return config;
245
253
  };
246
254
 
247
255
  /**
248
- * 卸载之后处理
249
- * @param {boolean} remove 是否删除文件
256
+ * 根据名称在配置数组中查找配置项
257
+ * @param {object|Array} config 配置对象或数组
258
+ * @param {string} name 配置项名称
259
+ * @returns {object|null} 匹配的配置项
260
+ * @private
250
261
  */
251
- Drive.prototype.unloadCore = async function (remove) {
252
- // 删除脚本
253
- this.unloadScript();
254
- if (remove) {
255
- this.delFile();
262
+ Drive.prototype._findConfigByName = function (config, name) {
263
+ if (!name || !Array.isArray(config)) return config;
264
+ return config.find((item) => item.name === name) || null;
265
+ };
266
+
267
+ /**
268
+ * 根据模式决定是否重新加载脚本
269
+ * @private
270
+ */
271
+ Drive.prototype._reloadIfNeeded = function () {
272
+ if (this.mode === 3 || this.mode === 4) {
273
+ this.reloadScript();
256
274
  }
257
275
  };
258
276
 
277
+ // ==================== 脚本管理 ====================
278
+
259
279
  /**
260
280
  * 获取脚本文件路径
261
- * @returns {string | null} 完整文件路径
281
+ * @returns {string|null} 完整文件路径
282
+ * @private
262
283
  */
263
284
  Drive.prototype._getScriptFile = function () {
264
- let main = this.config.main || '';
285
+ var main = this.config.main || '';
265
286
  if (!main) return null;
266
287
 
267
- let filename = main.fullname(this.getDir());
268
- if (!filename.hasFile()) {
288
+ var filename = path.resolve(this.getDir(), main);
289
+ var fs = require('fs');
290
+ if (!fs.existsSync(filename)) {
269
291
  this.newScript(filename);
270
292
  }
271
293
  return filename;
272
294
  };
273
295
 
274
296
  /**
275
- * 新建脚本文件
276
- * @param {string} file 目标文件路径
297
+ * 加载脚本
298
+ * 检查 main 配置,require 脚本文件并注册方法
299
+ * @returns {boolean} 是否加载成功
300
+ * @private
277
301
  */
278
- Drive.prototype.newScript = function (file) {
279
- this._createScriptFile(file);
280
- };
302
+ Drive.prototype._loadScript = function () {
303
+ var main = this.config.main;
304
+ if (!main) {
305
+ // 配置型模块,无脚本
306
+ return false;
307
+ }
281
308
 
282
- /**
283
- * 创建脚本
284
- * @param {string} file 文件路径
285
- * @returns {boolean} 创建是否成功
286
- */
287
- Drive.prototype._createScriptFile = function (file) {
288
- var tpl = this._getScriptTpl();
289
- var content = $.tpl.render(tpl, this.getModel('script'));
290
- if (!content) {
291
- return '';
309
+ var script_path = this._script;
310
+ if (!script_path) {
311
+ return false;
292
312
  }
293
- let code = this.prettyCode(content, 'js');
294
- file.saveText(code);
295
- return file;
296
- };
297
313
 
298
- /**
299
- * 获取脚本模板
300
- * @returns {string} 脚本模板内容
301
- */
302
- Drive.prototype._getScriptTpl = function () {
303
- var f = './script.tpl.js'.fullname(this._getTplDir());
304
- if (f.hasFile()) {
305
- return f.loadText();
306
- } else {
307
- return `module.exports = {
308
- /**
309
- * 初始化
310
- */
311
- async _init() {
312
- this.log('debug', \`初始化!\`);
313
- },
314
- /**
315
- * 启动
316
- */
317
- async _start() {
318
- this.log('debug', \`启动!\`);
319
- },
320
- /**
321
- * 停止
322
- */
323
- async _stop() {
324
- this.log('debug', \`停止!\`);
325
- },
326
- /**
327
- * 销毁
328
- */
329
- async _destroy(...args) {
330
- this.log('debug', \`销毁!\`);
331
- },
332
- /**
333
- * 主要逻辑
334
- */
335
- async main(...args) {
336
- // 主要代码写在这
314
+ var fs = require('fs');
315
+ if (!fs.existsSync(script_path)) {
316
+ return false;
337
317
  }
338
- }`;
318
+
319
+ try {
320
+ var exports;
321
+ if (this.mode === 3 || this.mode === 4) {
322
+ // 开发模式:使用 $.require 进行热重载
323
+ if (typeof $.require === 'function') {
324
+ exports = $.require(script_path, (mod) => {
325
+ this._setMainMethod(mod);
326
+ });
327
+ } else {
328
+ exports = require(script_path);
329
+ }
330
+ } else {
331
+ // 生产模式:清除缓存后 require
332
+ try {
333
+ var resolved = require.resolve(script_path);
334
+ if (require.cache[resolved]) {
335
+ delete require.cache[resolved];
336
+ }
337
+ } catch {
338
+ // 模块未缓存,忽略
339
+ }
340
+ exports = require(script_path);
341
+ }
342
+
343
+ this._setMainMethod(exports);
344
+ this.is_loaded = true;
345
+ return true;
346
+ } catch {
347
+ // 脚本加载失败
348
+ this.is_loaded = false;
349
+ return false;
339
350
  }
340
351
  };
341
352
 
342
353
  /**
343
- * 加载脚本文件,支持热重载模式
344
- * 在开发模式(mode=3或4)下使用 $.require 进行热重载
345
- * 在生产模式下使用标准的 require 加载
346
- * @returns {object | null} 加载的模块对象
354
+ * 加载脚本文件(外部接口)
355
+ * 在开发模式下使用 $.require 进行热重载,在生产模式下使用标准 require
356
+ * @returns {object|null} 加载的模块对象
347
357
  */
348
358
  Drive.prototype.loadScript = function () {
349
- let file = this._getScriptFile();
359
+ var file = this._getScriptFile();
350
360
  if (!file) return null;
361
+
351
362
  if (this.mode === 3 || this.mode === 4) {
352
- // 开发模式:使用 $.require 进行热重载,绑定热更新回调
353
- var cs = $.require(file, (mod) => {
354
- this._setMainMethod(mod);
355
- });
356
- this._setMainMethod(cs);
357
- return cs;
358
- } else {
359
- var cs = require(file);
360
- this._setMainMethod(cs);
361
- return cs;
363
+ // 开发模式:使用 $.require 进行热重载
364
+ if (typeof $.require === 'function') {
365
+ var cs = $.require(file, (mod) => {
366
+ this._setMainMethod(mod);
367
+ });
368
+ this._setMainMethod(cs);
369
+ return cs;
370
+ }
362
371
  }
372
+
373
+ var cs = require(file);
374
+ this._setMainMethod(cs);
375
+ return cs;
363
376
  };
364
377
 
365
378
  /**
366
379
  * 设置主方法
367
- * @param {object} mod 模块对象
380
+ * 从脚本导出对象中注册方法和数据
381
+ * @param {object} mod 脚本导出对象
382
+ * @private
368
383
  */
369
384
  Drive.prototype._setMainMethod = function (mod) {
370
385
  if (!mod) return;
386
+
371
387
  var name = this.config.func_name || 'main';
372
388
  if (name && mod[name]) {
373
- this.main = mod[name];
389
+ this._main_func = mod[name];
374
390
  }
391
+
375
392
  var new_keys = Object.keys(mod);
376
393
  // 热重载时如果脚本未变化则跳过重建
377
394
  if (this._script_keys && this._script_keys.length === new_keys.length) {
@@ -387,355 +404,516 @@ Drive.prototype._setMainMethod = function (mod) {
387
404
  return;
388
405
  }
389
406
  }
407
+
390
408
  this._script_keys = new_keys;
391
409
  for (var key in mod) {
392
410
  var obj = mod[key];
393
411
  if (typeof obj === 'function') {
394
412
  this.setMethod(key, obj);
395
413
  } else {
396
- this.setData(key, obj);
414
+ try {
415
+ // eslint-disable-next-line mm_eslint/function-name
416
+ this.setData(key, obj);
417
+ } catch {
418
+ // 忽略类型错误
419
+ }
397
420
  }
398
421
  }
399
422
  this.is_loaded = true;
400
423
  };
401
424
 
402
425
  /**
403
- * 获取配置文件作用域
404
- * @param {object} file 文件对象
405
- * @returns {string} 配置文件作用域
426
+ * 移除加载的脚本模块
427
+ * @private
406
428
  */
407
- Drive.prototype._getScope = function (file) {
408
- return file.replace(/\\/g, '/').between('app/', '/');
409
- };
429
+ Drive.prototype._remove = function () {
430
+ var file = this._getScriptFile();
431
+ if (!file) return;
410
432
 
411
- /**
412
- * 新建配置文件
413
- * @param {string} file 目标文件路径
414
- */
415
- Drive.prototype.newConfig = function (file) {
416
- if (!this.config.name) {
417
- this.config.name = this._getName(file);
418
- this.config.scope = this._getScope(file);
433
+ try {
434
+ if (this.mode === 3 || this.mode === 4) {
435
+ // 移除模块和监听
436
+ if (typeof $.unload === 'function') {
437
+ $.unload(file);
438
+ }
439
+ } else {
440
+ // 移除模块缓存
441
+ try {
442
+ var filename = require.resolve(file);
443
+ if (require.cache[filename]) {
444
+ delete require.cache[filename];
445
+ }
446
+ } catch {
447
+ // 忽略
448
+ }
449
+ }
450
+ } catch (err) {
451
+ this.log('error', '移除加载的脚本模块失败: ', err);
419
452
  }
420
- this._createConfigFile(file);
421
453
  };
422
454
 
423
455
  /**
424
- * 预试代码
425
- * @param {string} code 要预试的代码
426
- * @param {string} type 预试类型,可选"json"或"js"
427
- * @returns {string} 预试后的代码字符串
456
+ * 卸载脚本
457
+ * 清除 require.cache,重置加载状态
428
458
  */
429
- Drive.prototype.prettyCode = function (code, type = 'json5') {
430
- let options = {
431
- tabWidth: 2
432
- };
433
- switch (type) {
434
- case 'json5':
435
- options.parser = 'json5';
436
- break;
437
- case 'js':
438
- case 'javascript':
439
- options.parser = 'babel';
440
- options.semi = true; // 在语句末尾添加分号
441
- options.singleQuote = true; // 使用单引号
442
- break;
443
- default:
444
- options.parser = 'json';
445
- break;
446
- }
447
- let new_code = $.prettyCode(code, options);
448
- return new_code;
459
+ Drive.prototype.unloadScript = function () {
460
+ this._remove();
461
+ this._script_keys = null;
462
+ this.is_loaded = false;
449
463
  };
450
464
 
451
465
  /**
452
- * 获取模块模型
453
- * @param {string} type 模型类型,可选"config"或"script"
454
- * @returns {object} 模块模型
466
+ * 重新加载脚本
467
+ * @returns {object|null} 返回加载的模块对象
455
468
  */
456
- Drive.prototype.getModel = function (type) {
457
- return this.config;
469
+ Drive.prototype.reloadScript = function () {
470
+ this.unloadScript();
471
+ return this.loadScript();
458
472
  };
459
473
 
460
474
  /**
461
- * 创建配置文件
462
- * @param {string} file 目标文件路径
463
- * @param {object} model 模块模型
464
- * @returns {string} 配置文件内容
475
+ * 加载前处理
465
476
  */
466
- Drive.prototype._createConfigFile = function (file, model) {
467
- var tpl = this._getConfigTpl();
468
- var content = $.tpl.render(tpl, model || this.getModel('config'));
469
- let code = this.prettyCode(content, 'json');
470
- file.saveText(code);
471
- return file;
477
+ Drive.prototype.loadBefore = function () {
478
+ try {
479
+ var module = this.loadScript();
480
+ if (module) {
481
+ this.is_loaded = true;
482
+ }
483
+ } catch (err) {
484
+ this.log('error', '加载前处理失败: ', err);
485
+ this.is_loaded = false;
486
+ }
472
487
  };
473
488
 
474
- /**
475
- * 创建配置文件
476
- * @param {string} file 目标文件路径
477
- * @param {object} model 模块模型
478
- * @returns {string} 配置文件内容
479
- */
480
- Drive.prototype.createConfigFile = function (file, model) {
481
- file.addDir();
482
- return this._createConfigFile(file, model);
483
- };
489
+ // ==================== 文件操作 ====================
484
490
 
485
491
  /**
486
- * 获取配置模板
487
- * @returns {string} 配置模板内容
492
+ * 保存配置
488
493
  */
489
- Drive.prototype._getConfigTpl = function () {
490
- var f = './config.tpl.json'.fullname(this._getTplDir());
491
- if (f.hasFile()) {
492
- return f.loadText();
493
- } else {
494
- return `{
495
- // 模块名称,必填
496
- "name": "\${name}",
497
- // 模块标题,可选,默认"示例标题"
498
- "title": "\${title || '示例标题'}",
499
- // 模块描述,可选,默认"示例描述"
500
- "description": "\${description || '示例描述'}",
501
- // 主脚本文件路径,可选,默认"./index.js"
502
- "main": "\${main || './index.js'}",
503
- // 模块作用域,可选,默认"server"
504
- "scope": "\${scope || 'server'}",
505
- // 模块状态,可选,默认1
506
- "state": \${state || 1},
507
- // 模块排序,可选,默认100
508
- "sort": \${sort || 100},
509
- // 是否结束,可选,默认false
510
- "end": \${end || false}
511
- }`;
512
- }
513
- };
494
+ Drive.prototype.save = function () {
495
+ var file = this._script || this.config_file;
496
+ if (!file) return;
514
497
 
515
- /**
516
- * 加载配置文件
517
- * @param {string} file 文件路径
518
- * @param {string} name 配置项名称
519
- * @returns {object | null} 配置对象
520
- */
521
- Drive.prototype.loadFile = function (file, name) {
498
+ var fs = require('fs');
522
499
  try {
523
- let filename = file.fullname(this.getDir());
524
- let text = filename.loadText();
500
+ var text = fs.readFileSync(file, 'utf8');
525
501
 
526
- // 如果文件不存在,创建新的配置文件
527
- if (!text) {
528
- this.newConfig(filename);
529
- text = filename.loadText();
502
+ if (text && text.trim().startsWith('[')) {
503
+ // 数组格式配置
504
+ var config = JSON.parse(text);
505
+ if (Array.isArray(config)) {
506
+ var index = config.findIndex((item) => item.name === this.config.name);
507
+ if (index !== -1) {
508
+ config[index] = this.config;
509
+ } else {
510
+ config.push(this.config);
511
+ }
512
+ fs.writeFileSync(file, JSON.stringify(config, null, 2), 'utf8');
513
+ return;
514
+ }
530
515
  }
531
516
 
532
- if (!text) return null;
533
-
534
- let config = this._loadHotReload(filename);
535
- let final_config = this._findConfigByName(config, name);
536
- return final_config;
517
+ // 单对象格式配置,直接保存
518
+ fs.writeFileSync(file, JSON.stringify(this.config, null, 2), 'utf8');
537
519
  } catch (err) {
538
- this.log('error', `加载配置文件失败: `, err);
539
- return null;
540
- }
541
- };
542
-
543
- /**
544
- * 根据模式加载配置(支持热更新)
545
- * @param {string} file 完整文件路径
546
- * @returns {object | Array | null} 配置对象
547
- */
548
- Drive.prototype._loadHotReload = function (file) {
549
- if (this.mode === 2 || this.mode === 3 || this.mode === 4) {
550
- return $.loadJson(file, this._handleHotReload.bind(this));
520
+ this.log('error', '保存配置失败: ', err);
551
521
  }
552
- return file.loadJson();
553
- };
554
-
555
- /**
556
- * 根据名称在配置数组中查找配置项
557
- * @param {object | Array} config 配置对象或数组
558
- * @param {string} name 配置项名称
559
- * @returns {object | null} 匹配的配置项
560
- */
561
- Drive.prototype._findConfigByName = function (config, name) {
562
- if (!name || !Array.isArray(config)) return config;
563
- return config.find((Drive) => Drive.name === name) || null;
564
522
  };
565
523
 
566
524
  /**
567
- * 根据模式决定是否重新加载脚本
525
+ * 删除项目
526
+ * @returns {string|null} 删除的目录路径
568
527
  */
569
- Drive.prototype._reloadIfNeeded = function () {
570
- if (this.mode === 3 || this.mode === 4) {
571
- this.reloadScript();
528
+ Drive.prototype.remove = function () {
529
+ var dir = this.getDir();
530
+ if (dir) {
531
+ var fs = require('fs');
532
+ try {
533
+ fs.rmdirSync(dir, { recursive: true });
534
+ } catch {
535
+ // 忽略
536
+ }
572
537
  }
538
+ return dir;
573
539
  };
574
540
 
575
541
  /**
576
542
  * 删除目录
577
543
  */
578
544
  Drive.prototype.delDir = function () {
579
- let main = this.config.main;
580
- if (main && $.dir && $.dir.del) {
581
- $.dir.del(this.dir);
545
+ var dir = this.getDir();
546
+ if (dir) {
547
+ var fs = require('fs');
548
+ try {
549
+ fs.rmdirSync(dir, { recursive: true });
550
+ } catch {
551
+ // 忽略
552
+ }
582
553
  }
583
554
  };
584
555
 
585
556
  /**
586
557
  * 删除配置和脚本文件
587
- * @returns {string | null} 错误消息,如果没有错误则返回null
558
+ * @returns {string|null} 错误消息,如果没有错误则返回 null
588
559
  */
589
560
  Drive.prototype.delFile = function () {
590
- let name = this.config.name;
591
- let file = this.script_file;
561
+ var fs = require('fs');
562
+ var name = this.config.name;
563
+ var file = this._script || this.config_file;
592
564
 
593
- let error_message = null;
594
565
  try {
595
- if (!file || !file.hasFile) return null;
596
-
597
- if (!file.hasFile()) {
598
- return '配置文件不存在';
599
- }
566
+ if (!file) return null;
567
+ if (!fs.existsSync(file)) return '文件不存在';
600
568
 
601
- error_message = this._delConfigFile(file, name);
569
+ return this._delConfigFile(file, name);
602
570
  } catch (err) {
603
- this.log('error', `删除文件失败: `, err);
604
- error_message = `删除失败: ${err.message}`;
571
+ this.log('error', '删除文件失败: ', err);
572
+ return '删除失败: ' + (err.message || '');
605
573
  }
606
- return error_message;
607
574
  };
608
575
 
609
576
  /**
610
577
  * 删除配置文件
611
- * @param {object} file 文件对象
578
+ * @param {string} file 文件路径
612
579
  * @param {string} name 配置名称
613
- * @returns {string | null} 错误消息
580
+ * @returns {string|null} 错误消息
581
+ * @private
614
582
  */
615
583
  Drive.prototype._delConfigFile = function (file, name) {
616
- let text = file.loadText();
617
- if (!text) {
584
+ var fs = require('fs');
585
+ try {
586
+ var text = fs.readFileSync(file, 'utf8');
587
+ if (!text) {
588
+ this.delDir();
589
+ return null;
590
+ }
591
+
592
+ var config = JSON.parse(text);
593
+ if (Array.isArray(config)) {
594
+ return this._delArray(file, config, name);
595
+ }
596
+
597
+ this.delDir();
598
+ return null;
599
+ } catch {
618
600
  this.delDir();
619
601
  return null;
620
602
  }
621
-
622
- let config = text.toJson();
623
- if (Array.isArray(config)) {
624
- return this._delArray(file, config, name);
625
- }
626
-
627
- this.delDir();
628
- return null;
629
603
  };
630
604
 
631
605
  /**
632
606
  * 从数组配置中删除指定项
633
- * @param {object} file 文件对象
607
+ * @param {string} file 文件路径
634
608
  * @param {Array} config 配置数组
635
609
  * @param {string} name 配置名称
636
- * @returns {string | null} 错误消息
610
+ * @returns {string|null} 错误消息
611
+ * @private
637
612
  */
638
613
  Drive.prototype._delArray = function (file, config, name) {
639
- let index = config.findIndex((Drive) => Drive.name === name);
614
+ var fs = require('fs');
615
+ var index = config.findIndex((item) => item.name === name);
640
616
  if (index === -1) return null;
641
617
 
642
- this.delDir();
643
618
  config.splice(index, 1);
644
619
 
645
620
  if (config.length > 0) {
646
- file.saveText(JSON.stringify(config, null, 2));
647
- } else if (file.delFile) {
648
- file.delFile();
621
+ fs.writeFileSync(file, JSON.stringify(config, null, 2), 'utf8');
622
+ } else {
623
+ try {
624
+ fs.unlinkSync(file);
625
+ } catch {
626
+ // 忽略
627
+ }
649
628
  }
650
629
 
630
+ this.delDir();
651
631
  return null;
652
632
  };
653
633
 
654
634
  /**
655
635
  * 获取配置文件名称
656
- * @param {object} file 文件对象
636
+ * @param {string} file 文件路径
657
637
  * @returns {string} 配置文件名称
638
+ * @private
658
639
  */
659
640
  Drive.prototype._getName = function (file) {
660
- let name = file.dirname().basename();
661
- return name;
662
- // // 提取文件名作为模块名
663
- // var word = new Word();
664
- // // 将文件名改为驼峰命名法
665
- // return word.toCamelCase(name);
641
+ return path.basename(path.dirname(file));
666
642
  };
667
643
 
668
644
  /**
669
- * 加载前处理
645
+ * 获取配置文件作用域
646
+ * @param {string} file 文件路径
647
+ * @returns {string} 配置文件作用域
648
+ * @private
670
649
  */
671
- Drive.prototype.loadBefore = function () {
672
- try {
673
- let module = this.loadScript();
674
- if (module) {
675
- this.is_loaded = true;
676
- }
677
- } catch (err) {
678
- this.log('error', `加载前处理失败: `, err);
679
- this.is_loaded = false;
650
+ Drive.prototype._getScope = function (file) {
651
+ var normalized = file.replace(/\\/g, '/');
652
+ var idx = normalized.indexOf('app/');
653
+ if (idx === -1) return '';
654
+ return normalized.substring(idx + 4, normalized.indexOf('/', idx + 4));
655
+ };
656
+
657
+ // ==================== 模板/文件创建 ====================
658
+
659
+ /**
660
+ * 获取脚本模板
661
+ * @returns {string} 脚本模板内容
662
+ * @private
663
+ */
664
+ Drive.prototype._getScriptTpl = function () {
665
+ var fs = require('fs');
666
+ var f = path.resolve(this.getTplDir(), 'script.tpl.js');
667
+ if (fs.existsSync(f)) {
668
+ return fs.readFileSync(f, 'utf8');
669
+ }
670
+ return 'module.exports = {\n' +
671
+ ' /**\n' +
672
+ ' * 初始化\n' +
673
+ ' */\n' +
674
+ ' async _init() {\n' +
675
+ " this.log('debug', '初始化!');\n" +
676
+ ' },\n' +
677
+ ' /**\n' +
678
+ ' * 启动\n' +
679
+ ' */\n' +
680
+ ' async _start() {\n' +
681
+ " this.log('debug', '启动!');\n" +
682
+ ' },\n' +
683
+ ' /**\n' +
684
+ ' * 停止\n' +
685
+ ' */\n' +
686
+ ' async _stop() {\n' +
687
+ " this.log('debug', '停止!');\n" +
688
+ ' },\n' +
689
+ ' /**\n' +
690
+ ' * 销毁\n' +
691
+ ' */\n' +
692
+ ' async _destroy() {\n' +
693
+ " this.log('debug', '销毁!');\n" +
694
+ ' },\n' +
695
+ ' /**\n' +
696
+ ' * 主要逻辑\n' +
697
+ ' */\n' +
698
+ ' async main() {\n' +
699
+ ' // 主要代码写在这\n' +
700
+ ' }\n' +
701
+ '};\n';
702
+ };
703
+
704
+ /**
705
+ * 获取配置模板
706
+ * @returns {string} 配置模板内容
707
+ * @private
708
+ */
709
+ Drive.prototype._getConfigTpl = function () {
710
+ var fs = require('fs');
711
+ var f = path.resolve(this.getTplDir(), 'config.tpl.json');
712
+ if (fs.existsSync(f)) {
713
+ return fs.readFileSync(f, 'utf8');
680
714
  }
715
+ return '{\n' +
716
+ ' "name": "${name}",\n' +
717
+ ' "title": "${title || \'示例标题\'}",\n' +
718
+ ' "description": "${description || \'示例描述\'}",\n' +
719
+ ' "main": "${main || \'./index.js\'}",\n' +
720
+ ' "scope": "${scope || \'server\'}",\n' +
721
+ ' "state": ${state || 1},\n' +
722
+ ' "sort": ${sort || 100}\n' +
723
+ '}\n';
681
724
  };
682
725
 
683
726
  /**
684
- * 加载处理
727
+ * 获取模块模型
728
+ * @param {string} type 模型类型,可选 "config" 或 "script"
729
+ * @returns {object} 模块模型
685
730
  */
686
- Drive.prototype._loadCore = function () {
687
- // this.loadConfig();
688
- // this.loadScript();
731
+ Drive.prototype.getModel = function (type) {
732
+ return this.config;
689
733
  };
690
734
 
691
735
  /**
692
- * 保存配置
736
+ * 新建配置文件
737
+ * @param {string} file 目标文件路径
693
738
  */
694
- Drive.prototype.save = function () {
695
- try {
696
- if (!this.script_file) return;
739
+ Drive.prototype.newConfig = function (file) {
740
+ if (!this.config.name) {
741
+ this.config.name = this._getName(file);
742
+ this.config.scope = this._getScope(file);
743
+ }
744
+ this._createConfigFile(file);
745
+ };
697
746
 
698
- let file = this.script_file.fullname(this.getDir());
699
- let text = file.loadText();
747
+ /**
748
+ * 创建配置文件
749
+ * @param {string} file 目标文件路径
750
+ * @param {object} model 模块模型
751
+ * @returns {string} 文件路径
752
+ */
753
+ Drive.prototype._createConfigFile = function (file, model) {
754
+ var fs = require('fs');
755
+ var tpl = this._getConfigTpl();
756
+ var content = this._renderTpl(tpl, model || this.getModel('config'));
757
+ var code = this.prettyCode(content, 'json');
758
+ var dir = path.dirname(file);
759
+ if (!fs.existsSync(dir)) {
760
+ fs.mkdirSync(dir, { recursive: true });
761
+ }
762
+ fs.writeFileSync(file, code, 'utf8');
763
+ return file;
764
+ };
700
765
 
701
- if (text && text.trim().startsWith('[')) {
702
- // 数组格式配置
703
- let config = text.toJson();
704
- if (Array.isArray(config)) {
705
- // 查找并更新现有配置项
706
- let index = config.findIndex((Drive) => Drive.name === this.config.name);
707
- if (index !== -1) {
708
- config[index] = this.config;
709
- } else {
710
- // 如果不存在,添加新配置项
711
- config.push(this.config);
712
- }
713
- file.saveText(JSON.stringify(config, null, 2));
714
- return;
715
- }
766
+ /**
767
+ * 创建配置文件(带目录创建)
768
+ * @param {string} file 目标文件路径
769
+ * @param {object} model 模块模型
770
+ * @returns {string} 文件路径
771
+ */
772
+ Drive.prototype.createConfigFile = function (file, model) {
773
+ var fs = require('fs');
774
+ var dir = path.dirname(file);
775
+ if (!fs.existsSync(dir)) {
776
+ fs.mkdirSync(dir, { recursive: true });
777
+ }
778
+ return this._createConfigFile(file, model);
779
+ };
780
+
781
+ /**
782
+ * 新建脚本文件
783
+ * @param {string} file 目标文件路径
784
+ */
785
+ Drive.prototype.newScript = function (file) {
786
+ this._createScriptFile(file);
787
+ };
788
+
789
+ /**
790
+ * 创建脚本文件
791
+ * @param {string} file 文件路径
792
+ * @returns {string} 文件路径
793
+ * @private
794
+ */
795
+ Drive.prototype._createScriptFile = function (file) {
796
+ var fs = require('fs');
797
+ var tpl = this._getScriptTpl();
798
+ var content = this._renderTpl(tpl, this.getModel('script'));
799
+ var code = this.prettyCode(content, 'js');
800
+ var dir = path.dirname(file);
801
+ if (!fs.existsSync(dir)) {
802
+ fs.mkdirSync(dir, { recursive: true });
803
+ }
804
+ fs.writeFileSync(file, code, 'utf8');
805
+ return file;
806
+ };
807
+
808
+ /**
809
+ * 渲染模板(简单替换 ${key} 占位符)
810
+ * @param {string} tpl 模板内容
811
+ * @param {object} model 模型数据
812
+ * @returns {string} 渲染后的内容
813
+ * @private
814
+ */
815
+ Drive.prototype._renderTpl = function (tpl, model) {
816
+ return tpl.replace(/\$\{([^}]+)\}/g, (match, key) => {
817
+ // 处理默认值表达式:${key || 'default'}
818
+ var parts = key.split('||').map((s) => s.trim());
819
+ var var_name = parts[0];
820
+ var default_val = '';
821
+ if (parts.length > 1) {
822
+ default_val = parts[1].replace(/^['"]|['"]$/g, '');
716
823
  }
824
+ var val = model ? model[var_name] : undefined;
825
+ return val !== undefined && val !== null ? val : default_val;
826
+ });
827
+ };
717
828
 
718
- // 单对象格式配置,直接保存
719
- file.saveText(JSON.stringify(this.config, null, 2));
720
- } catch (err) {
721
- this.log('error', `保存配置失败: `, err);
829
+ /**
830
+ * 美化代码
831
+ * @param {string} code 代码字符串
832
+ * @param {string} type 代码类型,可选 "json" 或 "js"
833
+ * @returns {string} 美化后的代码
834
+ */
835
+ Drive.prototype.prettyCode = function (code, type) {
836
+ if (typeof $.prettyCode === 'function') {
837
+ var options = {
838
+ tab_width: 2
839
+ };
840
+ switch (type) {
841
+ case 'json5':
842
+ case 'json':
843
+ options.parser = 'json5';
844
+ break;
845
+ case 'js':
846
+ case 'javascript':
847
+ options.parser = 'babel';
848
+ options.semi = true;
849
+ options.singleQuote = true;
850
+ break;
851
+ default:
852
+ options.parser = 'json';
853
+ break;
854
+ }
855
+ return $.prettyCode(code, options);
722
856
  }
857
+ return code;
723
858
  };
724
859
 
860
+ // ==================== 生命周期重写 ====================
861
+
725
862
  /**
726
- * 删除项目
863
+ * 加载核心
864
+ * 加载配置文件和脚本文件
865
+ * @returns {Promise<void>}
866
+ * @private
867
+ * @override
727
868
  */
728
- Drive.prototype.remove = function () {
729
- let dir = this.getDir();
730
- if (dir) {
731
- dir.delDir();
869
+ Drive.prototype._loadCore = async function () {
870
+ if (this.lifecycle_state !== 'created') {
871
+ throw new Error('模块未创建,无法加载');
872
+ }
873
+
874
+ this.emit('load:before');
875
+
876
+ try {
877
+ // 加载配置
878
+ this._loadConfig();
879
+ // 加载脚本
880
+ this._loadScript();
881
+ this.lifecycle_state = 'loaded';
882
+ this.emit('load:success');
883
+ } catch (err) {
884
+ this.emit('load:error', err);
885
+ throw err;
732
886
  }
733
- return dir;
734
887
  };
735
888
 
736
889
  /**
737
- * @module 导出Drive类
890
+ * 结束核心
891
+ * 先调用 _end 钩子,再卸载脚本
892
+ * @returns {Promise<void>}
893
+ * @private
894
+ * @override
738
895
  */
739
- module.exports = {
740
- Drive
896
+ Drive.prototype._endCore = async function () {
897
+ if (this.lifecycle_state !== 'started' && this.lifecycle_state !== 'paused') {
898
+ throw new Error('模块未启动或暂停,无法结束');
899
+ }
900
+
901
+ this.emit('end:before');
902
+
903
+ try {
904
+ var end_hook = this.getMethod('_end');
905
+ if (end_hook) {
906
+ end_hook(this);
907
+ }
908
+
909
+ // 卸载脚本
910
+ this.unloadScript();
911
+ this.lifecycle_state = 'ended';
912
+ this.emit('end:success');
913
+ } catch (err) {
914
+ this.emit('end:error', err);
915
+ throw err;
916
+ }
741
917
  };
918
+
919
+ module.exports = { Drive };