dsh-m 0.2.14 → 0.4.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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * cordis 补丁层 YAML 的三个纯函数(0.4.0 开关功能,DESIGN/plan Task 2):
3
+ *
4
+ * 1) readBundlePatchRows —— 包内 `cordis.patch.yml`(dsh.bundle.patch 层)行枚举,
5
+ * 供「单行/多行插件」粒度判定(CONTEXT.md:行覆盖 vs Bundle 选择);
6
+ * 2) planRowOverride —— 官方 `writePluginEnabled`(dsh-plugin-manager L881-916)的纯函数版:
7
+ * 在 profile `cordis.patch.yml` 文本上做保注释的 `disabled` 覆盖编辑(findLast 按 id 匹配、
8
+ * 跳过含 insert 的行、name 可选限定、已是目标态 changed:false、无匹配 append `{id, disabled}`;
9
+ * 启用写显式 `disabled: false` 以压过更低层)。文件 IO 留给调用方(toggle.ts 持锁后落盘);
10
+ * 3) readProfileOverrides —— 读 profile 补丁层的覆盖行(id → disabled),enablement 读路径用。
11
+ *
12
+ * 解析约定与官方一致:customTags `tag:yaml.org,2002:js` 原样保留(官方补丁可含 `!!js`
13
+ * disabled 表达式);planRowOverride 对坏 YAML / 根非序列 throw(调用方映射为失败),
14
+ * readBundlePatchRows 对不可解析形态返回 readable:false(调用方按多行 → Bundle 级处理),
15
+ * readProfileOverrides 宽松(读路径,坏文件 → [])。
16
+ */
17
+ import { isMap, isSeq, parseDocument } from 'yaml';
18
+ /** 官方同款 customTags:`!!js` 标量原样解析为字符串,不因未知标签炸解析。 */
19
+ const YAML_OPTIONS = {
20
+ customTags: [
21
+ {
22
+ tag: 'tag:yaml.org,2002:js',
23
+ resolve(value) {
24
+ return value;
25
+ },
26
+ },
27
+ ],
28
+ };
29
+ function scalarString(value) {
30
+ return typeof value === 'string' && value.trim() !== '' ? value : null;
31
+ }
32
+ /**
33
+ * 包内补丁层行枚举。判定口径:
34
+ * - map 且含 `insert` 键 → 其值必须是序列,逐元素取 {id, name?};元素非 map / 缺 id → 整体不可读;
35
+ * - map 无 `insert`(含 `- id: x` 的配置补丁行与覆盖行)→ configRows +1;
36
+ * - 非映射项(纯标量等畸形)→ 整体不可读。
37
+ */
38
+ export function readBundlePatchRows(text) {
39
+ const result = { inserts: [], configRows: 0, readable: true };
40
+ let seq;
41
+ try {
42
+ seq = parsePatchSeq(text).seq;
43
+ }
44
+ catch {
45
+ return { ...result, readable: false };
46
+ }
47
+ for (const item of seq.items) {
48
+ if (!isMap(item))
49
+ return { inserts: [], configRows: 0, readable: false };
50
+ if (item.has('insert')) {
51
+ const inserted = item.get('insert');
52
+ if (!isSeq(inserted))
53
+ return { inserts: [], configRows: 0, readable: false };
54
+ for (const element of inserted.items) {
55
+ if (!isMap(element))
56
+ return { inserts: [], configRows: 0, readable: false };
57
+ const id = scalarString(element.get('id'));
58
+ if (id === null)
59
+ return { inserts: [], configRows: 0, readable: false };
60
+ const name = scalarString(element.get('name'));
61
+ result.inserts.push(name === null ? { id } : { id, name });
62
+ }
63
+ continue;
64
+ }
65
+ result.configRows += 1;
66
+ }
67
+ return result;
68
+ }
69
+ /** 单行插件:可读、恰好一个 insert 条目、零配置补丁行——行开关即整插件开关。 */
70
+ export function isSingleRowPlugin(rows) {
71
+ return rows.readable && rows.inserts.length === 1 && rows.configRows === 0;
72
+ }
73
+ function parsePatchSeq(text) {
74
+ const document = parseDocument(text, YAML_OPTIONS);
75
+ const error = document.errors[0];
76
+ if (error !== undefined)
77
+ throw error;
78
+ // Document.contents 是 accessor,CFA 无法收窄——先落到局部变量再判形
79
+ const seq = document.contents;
80
+ if (!isSeq(seq))
81
+ throw new Error('cordis.patch.yml 必须是 YAML 序列');
82
+ return { document, seq };
83
+ }
84
+ /**
85
+ * 保注释的行覆盖编辑(官方 writePluginEnabled 纯函数版)。
86
+ * @param text 当前 profile `cordis.patch.yml` 文本(文件缺失由调用方传 `"[]\n"`)
87
+ * @param id 目标合成条目 id(来自 insert 行声明或 loader entry)
88
+ * @param name 模块名(可选限定:行有 name 且不等 → 不匹配;append 时不写 name,与官方一致)
89
+ * @param enabled 目标启停态(true → 写 `disabled: false` 显式正覆盖)
90
+ */
91
+ export function planRowOverride(text, id, name, enabled) {
92
+ const { document, seq } = parsePatchSeq(text);
93
+ const items = seq.items;
94
+ const targetIndex = findLastIndex(items, (item, index) => {
95
+ if (!isMap(item) || item.has('insert'))
96
+ return false;
97
+ if (document.getIn([index, 'id']) !== id)
98
+ return false;
99
+ const expectedName = document.getIn([index, 'name']);
100
+ return !expectedName || expectedName === name;
101
+ });
102
+ if (targetIndex >= 0) {
103
+ if (document.getIn([targetIndex, 'disabled']) === !enabled) {
104
+ return { text, changed: false };
105
+ }
106
+ document.setIn([targetIndex, 'disabled'], !enabled);
107
+ }
108
+ else {
109
+ document.add({ id, disabled: !enabled });
110
+ }
111
+ return { text: String(document), changed: true };
112
+ }
113
+ /** Array.prototype.findLast 的索引版(官方用 items.findLast 取项,这里需要下标做 setIn)。 */
114
+ function findLastIndex(items, predicate) {
115
+ for (let index = items.length - 1; index >= 0; index--) {
116
+ if (predicate(items[index], index))
117
+ return index;
118
+ }
119
+ return -1;
120
+ }
121
+ /**
122
+ * 读 profile 补丁层覆盖行(读路径,宽松):坏 YAML / 根非序列 / 非 map 项 → 跳过或整体 []。
123
+ * 只返回带字符串 id 的非 insert 行;`disabled` 键缺省视为 false。
124
+ */
125
+ export function readProfileOverrides(text) {
126
+ let seq;
127
+ try {
128
+ seq = parsePatchSeq(text).seq;
129
+ }
130
+ catch {
131
+ return [];
132
+ }
133
+ const rows = [];
134
+ for (const item of seq.items) {
135
+ if (!isMap(item) || item.has('insert'))
136
+ continue;
137
+ const id = scalarString(item.get('id'));
138
+ if (id === null)
139
+ continue;
140
+ rows.push({ id, disabled: item.get('disabled') === true });
141
+ }
142
+ return rows;
143
+ }
@@ -499,6 +499,30 @@ async function addWithLagRetry(req, spec, d, heal) {
499
499
  * npm 门最终验证(R3:可重复执行)——manifest 依赖存在与锚定、lockfile 存在、
500
500
  * 与 npm dist 一致的 resolution.integrity。firstPass 控制是否记录 range 放行 heal。
501
501
  */
502
+ /**
503
+ * bundle 身份观察(Task 10,只警告不回滚):包内无 cordis.patch.yml 且 profile
504
+ * bundles 数组不含该包 → 装成「纯依赖」(不进加载树、已装页进 others 折叠)。
505
+ * 存在合法的无补丁层 dsh 包(伴生库/纯资源包),故不判失败。
506
+ */
507
+ async function bundleIdentityWarning(profileDir, pkg) {
508
+ try {
509
+ await readFile(join(profileDir, 'node_modules', pkg, 'cordis.patch.yml'));
510
+ return undefined;
511
+ }
512
+ catch {
513
+ /* 无补丁层 → 再看 bundles */
514
+ }
515
+ try {
516
+ const raw = JSON.parse(await readFile(join(profileDir, 'package.json'), 'utf8'));
517
+ const bundles = raw?.dsh?.profile?.bundles;
518
+ if (Array.isArray(bundles) && bundles.includes(pkg))
519
+ return undefined;
520
+ }
521
+ catch {
522
+ /* manifest 读不到 → 如实报 warning(不升级为失败) */
523
+ }
524
+ return 'no-patch-layer';
525
+ }
502
526
  async function verifyNpmCommitState(req, d, heal, firstPass) {
503
527
  const depsNow = await strictReadDeps(d);
504
528
  if (depsNow[req.pkg] === undefined) {
@@ -577,7 +601,9 @@ async function installNpm(req, d, snapshots) {
577
601
  pkg: req.pkg,
578
602
  spec,
579
603
  version: req.version,
580
- usedAllowAllBuilds: addOut.usedAllowAllBuilds === true,
604
+ buildApprovals: addOut.buildApprovals ?? [],
605
+ fallbackAllBuilds: addOut.fallbackAllBuilds === true,
606
+ ...(await bundleIdentityWarning(d.profileDir, req.pkg)) ? { bundleWarning: 'no-patch-layer' } : {},
581
607
  });
582
608
  }
583
609
  catch (err) {
@@ -646,7 +672,9 @@ async function installGithub(req, d, snapshots) {
646
672
  spec,
647
673
  sha: req.sha,
648
674
  tag: req.tag,
649
- usedAllowAllBuilds: addOut.usedAllowAllBuilds === true,
675
+ buildApprovals: addOut.buildApprovals ?? [],
676
+ fallbackAllBuilds: addOut.fallbackAllBuilds === true,
677
+ ...(await bundleIdentityWarning(d.profileDir, key)) ? { bundleWarning: 'no-patch-layer' } : {},
650
678
  });
651
679
  }
652
680
  catch (err) {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 元数据源竞速(0.4.0,plan Task 7;对齐官方 ui-plugin-manager 的 registry probe
3
+ * 语义:npmjs vs npmmirror `/-/ping` 并发,首个 2xx 胜出即定、败者 abort,
4
+ * 双败 → null 保持默认 npmjs;结果缓存 TTL,默认 1500ms 预算 / 5 分钟缓存)。
5
+ *
6
+ * 只读不写:探测结果仅用于 dsh-m 自身的元数据/packument 读取源选择与市场页展示,
7
+ * 安装链路(pnpm 读 profile .npmrc)完全不动(grilling Q9 定稿)。
8
+ */
9
+ import { fetchLimited } from './httpx.js';
10
+ export const PROBE_URLS = {
11
+ npmjs: 'https://registry.npmjs.org/-/ping',
12
+ npmmirror: 'https://registry.npmmirror.com/-/ping',
13
+ };
14
+ /** 探测源 → registry base(versions.ts 元数据读取的 registry 参数值)。 */
15
+ export const PROBE_REGISTRY_BASES = {
16
+ npmjs: 'https://registry.npmjs.org',
17
+ npmmirror: 'https://registry.npmmirror.com',
18
+ };
19
+ export class RegistryProbe {
20
+ options;
21
+ cached = null;
22
+ inflight = null;
23
+ constructor(options) {
24
+ this.options = options;
25
+ }
26
+ /** 最近一次快照(不发请求);无记录 → null。 */
27
+ cachedSnapshot() {
28
+ return this.cached;
29
+ }
30
+ /** 缓存期内直接回放;否则并发竞速一次(并发调用共享同一次探测)。 */
31
+ async fastest() {
32
+ const now = this.options.now?.() ?? Date.now();
33
+ if (this.cached !== null && now - this.cached.checkedAt < this.options.cacheTtlMs) {
34
+ return this.cached.source;
35
+ }
36
+ if (this.inflight !== null)
37
+ return this.inflight;
38
+ this.inflight = this.race().finally(() => {
39
+ this.inflight = null;
40
+ });
41
+ return this.inflight;
42
+ }
43
+ async race() {
44
+ const fetchImpl = this.options.fetchImpl ?? fetchLimited;
45
+ const sources = Object.keys(PROBE_URLS);
46
+ const racers = sources.map((source) => {
47
+ const controller = new AbortController();
48
+ const promise = Promise.resolve()
49
+ .then(() => fetchImpl(PROBE_URLS[source], { timeoutMs: this.options.timeoutMs, signal: controller.signal }))
50
+ .then((res) => res.ok === true);
51
+ return { source, controller, promise };
52
+ });
53
+ const winner = await new Promise((resolve) => {
54
+ let settled = false;
55
+ let pending = racers.length;
56
+ const done = (source) => {
57
+ if (settled)
58
+ return;
59
+ settled = true;
60
+ for (const racer of racers) {
61
+ if (racer.source !== source)
62
+ racer.controller.abort();
63
+ }
64
+ resolve(source);
65
+ };
66
+ const oneFinished = (ok, source) => {
67
+ if (ok) {
68
+ done(source);
69
+ return;
70
+ }
71
+ pending -= 1;
72
+ if (pending <= 0)
73
+ done(null);
74
+ };
75
+ for (const racer of racers) {
76
+ racer.promise.then((ok) => oneFinished(ok, racer.source)).catch(() => oneFinished(false, racer.source));
77
+ }
78
+ });
79
+ this.cached = { source: winner, checkedAt: this.options.now?.() ?? Date.now() };
80
+ return winner;
81
+ }
82
+ }
@@ -116,7 +116,12 @@ export function parseRegistryAddress(raw) {
116
116
  }
117
117
  // ---------- 严格 v1 校验 ----------
118
118
  const TOP_LEVEL_KEYS = new Set(['version', 'plugins']);
119
- const ENTRY_KEYS = new Set(['id', 'name', 'description', 'category', 'tags', 'source', 'npm', 'github', 'homepage', 'icon']);
119
+ const ENTRY_KEYS = new Set(['id', 'name', 'description', 'category', 'tags', 'source', 'npm', 'github', 'homepage', 'icon', 'verified']);
120
+ /** 精确 semver 判定(与 versions.ts EXACT_VERSION_RE 同语义;接受 prerelease/build,拒绝 range/前缀)。 */
121
+ const EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
122
+ function semverParse(value) {
123
+ return EXACT_SEMVER_RE.test(value) ? value : null;
124
+ }
120
125
  function httpsUrlError(raw) {
121
126
  if (raw.length > MAX_URL)
122
127
  return `超过 ${MAX_URL} 字符`;
@@ -269,6 +274,31 @@ export function validateRegistry(raw) {
269
274
  entryIcon = s;
270
275
  }
271
276
  // 错误条目不产生合法输出:整份清单在存在任何错误时返回 null
277
+ // verified(0.4.0):可选字符串数组,每项精确 semver(禁 range/前缀)
278
+ let verified;
279
+ if (e.verified !== undefined) {
280
+ if (!Array.isArray(e.verified)) {
281
+ errors.push(`${where}.verified: 必须是字符串数组`);
282
+ }
283
+ else {
284
+ const list = [];
285
+ e.verified.forEach((v, vi) => {
286
+ if (typeof v !== 'string' || v.trim() === '' || v !== v.trim()) {
287
+ errors.push(`${where}.verified[${vi}]: 必须是非空白精确 semver`);
288
+ return;
289
+ }
290
+ const parsed = semverParse(v);
291
+ if (parsed === null)
292
+ errors.push(`${where}.verified[${vi}}: ${JSON.stringify(v)} 不是合法 semver(禁 range/前缀)`);
293
+ else if (e.verified.includes(v, vi + 1))
294
+ errors.push(`${where}.verified[${vi}]: 重复 ${v}`);
295
+ else
296
+ list.push(v);
297
+ });
298
+ if (list.length > 0)
299
+ verified = list;
300
+ }
301
+ }
272
302
  plugins.push({
273
303
  id,
274
304
  name,
@@ -280,6 +310,7 @@ export function validateRegistry(raw) {
280
310
  ...(entryGithub !== undefined ? { github: entryGithub } : {}),
281
311
  ...(entryHomepage !== undefined ? { homepage: entryHomepage } : {}),
282
312
  ...(entryIcon !== undefined ? { icon: entryIcon } : {}),
313
+ ...(verified !== undefined ? { verified } : {}),
283
314
  });
284
315
  });
285
316
  return { ok: errors.length === 0, errors, registry: errors.length === 0 ? { version: 1, plugins } : null };
@@ -0,0 +1,180 @@
1
+ const VOLATILE_WRITE = Symbol.for('cosmokit.volatile.write');
2
+ /** cosmokit Volatile 引用判定(鸭子类型,跨副本安全)。 */
3
+ export function isVolatileRef(value) {
4
+ return typeof value === 'object' && value !== null && VOLATILE_WRITE in value;
5
+ }
6
+ /** 解析引用为快照;普通值原样返回。深度有界(防循环与异常结构),超界截断为 undefined。 */
7
+ export function unwrapValue(value, depth = 4) {
8
+ const walk = (v, d) => {
9
+ if (isVolatileRef(v)) {
10
+ if (d <= 0)
11
+ return undefined;
12
+ return walk(v.get(), d - 1);
13
+ }
14
+ if (Array.isArray(v)) {
15
+ if (d <= 0)
16
+ return undefined;
17
+ return v.map((item) => walk(item, d - 1));
18
+ }
19
+ if (typeof v === 'object' && v !== null) {
20
+ if (d <= 0)
21
+ return undefined;
22
+ const out = {};
23
+ for (const [key, item] of Object.entries(v))
24
+ out[key] = walk(item, d - 1);
25
+ return out;
26
+ }
27
+ return v;
28
+ };
29
+ return walk(value, depth);
30
+ }
31
+ /** 从任意形态(plain / 带 Volatile 引用 / 未知脏值)提取已知 registry 字段,类型不符丢弃。 */
32
+ export function unwrapConfig(source) {
33
+ const raw = unwrapValue(source);
34
+ const out = {};
35
+ if (raw && typeof raw === 'object') {
36
+ if (typeof raw.registryUrl === 'string')
37
+ out.registryUrl = raw.registryUrl;
38
+ if (typeof raw.timeoutMs === 'number')
39
+ out.timeoutMs = raw.timeoutMs;
40
+ if (typeof raw.cacheTtlMin === 'number')
41
+ out.cacheTtlMin = raw.cacheTtlMin;
42
+ }
43
+ return out;
44
+ }
45
+ /** 从任意形态提取 probe 三字段(Task 7/14;类型不符丢弃,缺省交调用方)。 */
46
+ export function unwrapProbeConfig(source) {
47
+ const raw = unwrapValue(source);
48
+ const out = {};
49
+ if (raw && typeof raw === 'object') {
50
+ if (typeof raw.probeEnabled === 'boolean')
51
+ out.probeEnabled = raw.probeEnabled;
52
+ if (typeof raw.probeTimeoutMs === 'number')
53
+ out.probeTimeoutMs = raw.probeTimeoutMs;
54
+ if (typeof raw.probeCacheTtlMin === 'number')
55
+ out.probeCacheTtlMin = raw.probeCacheTtlMin;
56
+ }
57
+ return out;
58
+ }
59
+ /** 运行时形态探测:register 在前(老服务不会带 describe/update),forms 次之。 */
60
+ export function detectSettingsKind(settings) {
61
+ const s = settings;
62
+ if (s && typeof s === 'object') {
63
+ if (typeof s.register === 'function')
64
+ return 'legacy';
65
+ if (typeof s.describe === 'function' && typeof s.update === 'function')
66
+ return 'forms';
67
+ }
68
+ return 'unknown';
69
+ }
70
+ /** 在 loader entries 里找本插件实例的 profile entry id(新 API 写入的 ns)。范式同 dsh-better-sidebar。 */
71
+ export function ownEntryId(ctx, packageName) {
72
+ const loader = ctx?.loader;
73
+ if (!loader || typeof loader.entries !== 'function')
74
+ return undefined;
75
+ const ownFiber = ctx?.fiber;
76
+ let fallback;
77
+ try {
78
+ for (const entry of loader.entries()) {
79
+ const id = entry?.options?.id;
80
+ if (entry?.options?.name !== packageName || typeof id !== 'string' || id === '')
81
+ continue;
82
+ if (ownFiber !== undefined && entry.fiber === ownFiber)
83
+ return id;
84
+ if (entry.options.disabled !== true && fallback === undefined)
85
+ fallback = id;
86
+ }
87
+ }
88
+ catch {
89
+ return undefined;
90
+ }
91
+ return fallback;
92
+ }
93
+ /** 旧 API store:scope 值出站前统一解包(防御老 runtime 解析出引用的混合形态)。 */
94
+ export function createLegacyRegistryStore(scope) {
95
+ return {
96
+ get: () => unwrapConfig(scope.get()),
97
+ update: async (patch) => {
98
+ await scope.update({ ...patch });
99
+ },
100
+ watch: (callback) => scope.watch((next, prev) => {
101
+ callback(unwrapConfig(next), unwrapConfig(prev));
102
+ }),
103
+ };
104
+ }
105
+ function subscribeVolatileUpdate(ctx, callback) {
106
+ const on = ctx?.on;
107
+ if (typeof on !== 'function')
108
+ return () => { };
109
+ const disposed = on.call(ctx, 'loader/volatile-update', () => callback());
110
+ return typeof disposed === 'function' ? disposed : () => { };
111
+ }
112
+ /**
113
+ * 新 API store(0.1.7-rc.1 / rc.2):
114
+ * - 读:直接解包 loader 解析出的 Config(Volatile 引用 `.get()` 即最新已提交值);
115
+ * - 写:`settings.update(ns, patch)`,经 config-editor 落 profile patch(重启持久),省略
116
+ * expectedRevision = 与旧 API 相同的末写胜语义(controller 串行队列 + lastSelfWrite 去重);
117
+ * - 变更通知:`loader/volatile-update`(loader 只发给本 fiber,路径含变更字段)。
118
+ */
119
+ export function createFormsRegistryStore(ctx, settings, ns, parsedConfig) {
120
+ const read = () => unwrapConfig(parsedConfig);
121
+ let prev = read();
122
+ return {
123
+ get: read,
124
+ update: async (patch) => {
125
+ await settings.update(ns, { ...patch });
126
+ },
127
+ watch: (callback) => subscribeVolatileUpdate(ctx, () => {
128
+ const next = read();
129
+ callback(next, prev);
130
+ prev = next;
131
+ }),
132
+ };
133
+ }
134
+ /**
135
+ * 找不到本插件 loader 条目(ns 缺失)时的只读降级 store:
136
+ * 读与变更通知仍可用(Config 引用由 loader 解析),只有写不可持久化。
137
+ */
138
+ export function createFormsRegistryStoreWithoutNs(ctx, parsedConfig, note) {
139
+ const read = () => unwrapConfig(parsedConfig);
140
+ let prev = read();
141
+ return {
142
+ get: read,
143
+ update: async () => {
144
+ throw new Error(`settings 命名空间不可用,registry 地址无法持久化(${note})`);
145
+ },
146
+ watch: (callback) => subscribeVolatileUpdate(ctx, () => {
147
+ const next = read();
148
+ callback(next, prev);
149
+ prev = next;
150
+ }),
151
+ };
152
+ }
153
+ /**
154
+ * 按运行时形态把 RegistrySettingsStore 接到 controller 上。
155
+ * 不抛:任何形态失败都以返回值 + warn 收场,绝不拖垮插件其余部分(webServer / tools)。
156
+ */
157
+ export function wireRegistrySettings(input) {
158
+ const kind = detectSettingsKind(input.settings);
159
+ if (kind === 'legacy') {
160
+ const scope = input.settings.register('dshm', input.schema, {
161
+ base: input.parsedConfig,
162
+ applies: 'live',
163
+ });
164
+ input.attachStore(createLegacyRegistryStore(scope));
165
+ return 'legacy';
166
+ }
167
+ if (kind === 'forms') {
168
+ const ns = ownEntryId(input.ctx, input.packageName);
169
+ if (ns === undefined) {
170
+ const note = 'loader 中未找到本插件条目';
171
+ input.logger?.warn?.(`dsh-m: ${note},设置卡不可持久化,registry 地址仅剩 cordis 配置文件通路`);
172
+ input.attachStore(createFormsRegistryStoreWithoutNs(input.ctx, input.parsedConfig, note));
173
+ return 'forms-without-ns';
174
+ }
175
+ input.attachStore(createFormsRegistryStore(input.ctx, input.settings, ns, input.parsedConfig));
176
+ return 'forms';
177
+ }
178
+ input.logger?.warn?.('dsh-m: settings 服务形态未识别(无 register 也无 describe/update),registry 地址走 cordis 配置文件');
179
+ return 'unknown';
180
+ }