刘光辉
11 小时以前 0dfe84494048ce27ba8449831782128412d3eb13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { parse } from 'yaml';
 
const SAFE_CODE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
const SAFE_PACKAGE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
const SAFE_SCENE = /^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/u;
const GUID = /^asc\.\{[0-9A-F-]+\}$/u;
 
/** @typedef {{ autostart: boolean, bizModule: string, bizScene: string, code: string, devPort: number, directory: string, disabledPluginGuids: readonly string[], guid: string, packageName: string, version: '0.1.0' }} RegistryPlugin */
 
/** @param {unknown} value @param {string} message @returns {Record<string, unknown>} */
function record(value, message) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(message);
  return /** @type {Record<string, unknown>} */ (value);
}
 
/** @param {unknown} value @param {RegExp} pattern @param {string} message */
function text(value, pattern, message) {
  if (typeof value !== 'string' || !pattern.test(value)) throw new Error(message);
  return value;
}
 
/** @param {RegistryPlugin[]} plugins @param {keyof RegistryPlugin} property @param {string} message */
function unique(plugins, property, message) {
  const values = plugins.map((plugin) => plugin[property]);
  if (new Set(values).size !== values.length) throw new Error(message);
}
 
/** @param {string} source */
export function parseRegistry(source) {
  const root = record(parse(source), '插件注册表无效');
  if (root.version !== 1 || root.release !== '0.1.0-dev' || !Array.isArray(root.plugins) || root.plugins.length === 0) {
    throw new Error('插件注册表无效');
  }
  /** @type {RegistryPlugin[]} */
  const plugins = root.plugins.map((raw) => {
    const plugin = record(raw, '插件定义无效');
    if (plugin.version !== '0.1.0') throw new Error('插件版本必须为 0.1.0');
    const devPort = plugin['dev-port'];
    if (typeof devPort !== 'number' || !Number.isInteger(devPort) || devPort < 1024 || devPort > 65_535) throw new Error('插件开发端口无效');
    const code = text(plugin.code, SAFE_CODE, '插件编码无效');
    const directory = text(plugin.directory, SAFE_CODE, '插件目录无效');
    const packageName = text(plugin.package, SAFE_PACKAGE, '插件包名无效');
    const disabled = plugin['disabled-plugin-guids'] ?? [];
    if (!Array.isArray(disabled) || disabled.some((guid) => typeof guid !== 'string' || !GUID.test(guid))) throw new Error('禁用插件 GUID 无效');
    const disabledPluginGuids = /** @type {string[]} */ (disabled);
    return Object.freeze({
      autostart: plugin.autostart === true,
      bizModule: text(plugin['biz-module'], SAFE_SCENE, '业务模块无效'),
      bizScene: text(plugin['biz-scene'], SAFE_SCENE, '业务场景无效'),
      code,
      devPort,
      directory,
      disabledPluginGuids: Object.freeze([...disabledPluginGuids]),
      guid: text(plugin.guid, GUID, '插件 GUID 无效'),
      packageName,
      version: '0.1.0',
    });
  });
  unique(plugins, 'code', '插件编码重复');
  unique(plugins, 'guid', '插件 GUID 重复');
  unique(plugins, 'devPort', '插件开发端口重复');
  unique(plugins, 'bizScene', '业务场景重复');
  return Object.freeze({ plugins: Object.freeze(plugins), release: root.release, version: 1 });
}