@koishi-ce/plugin-config 1.1.3 → 1.1.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.
- package/dist/index.js +3 -3
- package/dist/style.css +1 -1
- package/lib/index.d.ts +0 -10
- package/lib/index.mjs +1 -35
- package/package.json +4 -4
- package/src/__tests__/helpers.ts +428 -0
- package/src/__tests__/package-provider.test.ts +242 -0
- package/src/__tests__/services.test.ts +105 -0
- package/src/__tests__/writer-events.test.ts +270 -0
- package/src/shared/console-events.ts +46 -0
- package/src/shared/writer.ts +4 -73
- package/src/__tests__/index.test.ts +0 -942
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
afterAll,
|
|
7
|
+
beforeAll,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
} from "bun:test";
|
|
12
|
+
import {
|
|
13
|
+
App,
|
|
14
|
+
type Dict,
|
|
15
|
+
type Plugin,
|
|
16
|
+
} from "@koishi-ce/koishi";
|
|
17
|
+
import * as configPlugin from "@koishi-ce/plugin-config";
|
|
18
|
+
import {
|
|
19
|
+
app,
|
|
20
|
+
itQuiet,
|
|
21
|
+
readSent,
|
|
22
|
+
socket,
|
|
23
|
+
startApp,
|
|
24
|
+
stopApp,
|
|
25
|
+
TestConsole,
|
|
26
|
+
tick,
|
|
27
|
+
} from "./helpers.ts";
|
|
28
|
+
|
|
29
|
+
beforeAll(startApp);
|
|
30
|
+
afterAll(stopApp);
|
|
31
|
+
|
|
32
|
+
describe("@koishi-ce/plugin-config", () => {
|
|
33
|
+
itQuiet(
|
|
34
|
+
["app"],
|
|
35
|
+
"无可写 loader 时仅告警并跳过装配",
|
|
36
|
+
async () => {
|
|
37
|
+
const bare = new App();
|
|
38
|
+
bare.plugin(
|
|
39
|
+
TestConsole as unknown as Plugin.Constructor<App>,
|
|
40
|
+
);
|
|
41
|
+
bare.plugin(configPlugin);
|
|
42
|
+
await bare.start();
|
|
43
|
+
expect(
|
|
44
|
+
bare.get("console.services.config"),
|
|
45
|
+
).toBeUndefined();
|
|
46
|
+
expect(
|
|
47
|
+
bare.get("console.services.packages"),
|
|
48
|
+
).toBeUndefined();
|
|
49
|
+
expect(
|
|
50
|
+
bare.get("console.services.services"),
|
|
51
|
+
).toBeUndefined();
|
|
52
|
+
await bare.stop();
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
it("挂载 packages / services / config 三个数据服务", () => {
|
|
57
|
+
expect(
|
|
58
|
+
app.get("console.services.packages"),
|
|
59
|
+
).toBeTruthy();
|
|
60
|
+
expect(
|
|
61
|
+
app.get("console.services.services"),
|
|
62
|
+
).toBeTruthy();
|
|
63
|
+
expect(app.get("console.services.config")).toBeTruthy();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("ServiceProvider", () => {
|
|
67
|
+
it("上报各服务的提供者上下文并在服务变动时刷新", async () => {
|
|
68
|
+
const provider = app.get(
|
|
69
|
+
"console.services.services",
|
|
70
|
+
) as unknown as {
|
|
71
|
+
get(): Promise<Dict<number>>;
|
|
72
|
+
};
|
|
73
|
+
const data = await provider.get();
|
|
74
|
+
expect(data["console"]).toBeGreaterThanOrEqual(0);
|
|
75
|
+
// 服务注册(set 会广播 internal/service)→ refresh → 客户端收到 services 数据
|
|
76
|
+
socket.sent.length = 0;
|
|
77
|
+
app.set(
|
|
78
|
+
"probe.svc" as never,
|
|
79
|
+
{ marker: true } as never,
|
|
80
|
+
);
|
|
81
|
+
await tick(30);
|
|
82
|
+
const messages = readSent().filter(
|
|
83
|
+
(msg) =>
|
|
84
|
+
msg.type === "data" &&
|
|
85
|
+
msg.body.key === "services",
|
|
86
|
+
);
|
|
87
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("provide() 注册的服务同样上报(loader / watcher 形态)", async () => {
|
|
91
|
+
const provider = app.get(
|
|
92
|
+
"console.services.services",
|
|
93
|
+
) as unknown as {
|
|
94
|
+
get(): Promise<Dict<number>>;
|
|
95
|
+
};
|
|
96
|
+
// cordis 3.18 的 ctx.provide() 不给值定义自有 "ctx" 属性
|
|
97
|
+
// (只有 tracker 符号,经 traceable 代理的属性访问可达),
|
|
98
|
+
// descriptor 查询落空时须以属性访问兜底,否则配置页
|
|
99
|
+
// 恒显示「必需服务未加载」
|
|
100
|
+
app.provide("probe.plain", { marker: 1 });
|
|
101
|
+
const data = await provider.get();
|
|
102
|
+
expect(data["probe.plain"]).toBeGreaterThanOrEqual(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
afterAll,
|
|
7
|
+
beforeAll,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
} from "bun:test";
|
|
12
|
+
import type { Plugin } from "@koishi-ce/koishi";
|
|
13
|
+
import {
|
|
14
|
+
app,
|
|
15
|
+
client,
|
|
16
|
+
flushWrites,
|
|
17
|
+
itQuiet,
|
|
18
|
+
loader,
|
|
19
|
+
readSent,
|
|
20
|
+
startApp,
|
|
21
|
+
stopApp,
|
|
22
|
+
writer,
|
|
23
|
+
} from "./helpers.ts";
|
|
24
|
+
|
|
25
|
+
beforeAll(startApp);
|
|
26
|
+
afterAll(stopApp);
|
|
27
|
+
|
|
28
|
+
describe("@koishi-ce/plugin-config", () => {
|
|
29
|
+
describe("ConfigWriter.get()", () => {
|
|
30
|
+
it("过滤未加载项,分组递归展开,$ 与 ~ 键原样保留", async () => {
|
|
31
|
+
const result = await writer().get();
|
|
32
|
+
const plugins = result.plugins as Record<
|
|
33
|
+
string,
|
|
34
|
+
unknown
|
|
35
|
+
>;
|
|
36
|
+
// $if 为假的键不出现;$ 内部键与 ~ 停用键保留
|
|
37
|
+
expect("gone:xyz" in plugins).toBe(false);
|
|
38
|
+
expect(plugins["$sfolded"]).toBe(true);
|
|
39
|
+
expect(plugins["~disabled:q"]).toEqual({ a: 1 });
|
|
40
|
+
expect(plugins["keep:abc"]).toEqual({ v: 1 });
|
|
41
|
+
expect(plugins["group:g1"]).toEqual({
|
|
42
|
+
"./plugins/webui/auth": {},
|
|
43
|
+
"./missing-pkg": {},
|
|
44
|
+
"inner:two": { t: 2 },
|
|
45
|
+
"mov:me": { m: 1 },
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("ConfigWriter 事件", () => {
|
|
51
|
+
it("manager/meta 更新元数据键(含 null 删除)", async () => {
|
|
52
|
+
const listener =
|
|
53
|
+
app.console.listeners["manager/meta"];
|
|
54
|
+
await listener?.callback.call(
|
|
55
|
+
client as never,
|
|
56
|
+
"abc",
|
|
57
|
+
{
|
|
58
|
+
$label: "L",
|
|
59
|
+
$collapsed: null,
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
await flushWrites();
|
|
63
|
+
const plugins = loader.config.plugins as Record<
|
|
64
|
+
string,
|
|
65
|
+
Record<string, unknown>
|
|
66
|
+
>;
|
|
67
|
+
expect(plugins["keep:abc"]?.["$label"]).toBe("L");
|
|
68
|
+
expect(
|
|
69
|
+
"$collapsed" in (plugins["keep:abc"] ?? {}),
|
|
70
|
+
).toBe(false);
|
|
71
|
+
// 元数据键置于配置开头
|
|
72
|
+
expect(
|
|
73
|
+
Object.keys(plugins["keep:abc"] ?? {})[0],
|
|
74
|
+
).toBe("$label");
|
|
75
|
+
// 分组内插件的 meta(按 ident 递归定位)
|
|
76
|
+
await listener?.callback.call(
|
|
77
|
+
client as never,
|
|
78
|
+
"two",
|
|
79
|
+
{
|
|
80
|
+
$label: "G",
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
await flushWrites();
|
|
84
|
+
const group = plugins["group:g1"] as Record<
|
|
85
|
+
string,
|
|
86
|
+
Record<string, unknown>
|
|
87
|
+
>;
|
|
88
|
+
expect(group["inner:two"]?.["$label"]).toBe("G");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("manager/reload 更新插件配置并保持键位置", async () => {
|
|
92
|
+
const listener =
|
|
93
|
+
app.console.listeners["manager/reload"];
|
|
94
|
+
await listener?.callback.call(
|
|
95
|
+
client as never,
|
|
96
|
+
"",
|
|
97
|
+
"keep:abc",
|
|
98
|
+
{
|
|
99
|
+
v: 2,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
await flushWrites();
|
|
103
|
+
const plugins = loader.config.plugins as Record<
|
|
104
|
+
string,
|
|
105
|
+
unknown
|
|
106
|
+
>;
|
|
107
|
+
expect(plugins["keep:abc"]).toEqual({ v: 2 });
|
|
108
|
+
const dummy = loader.data["keep"] as Plugin;
|
|
109
|
+
expect(app.registry.get(dummy)?.config).toEqual({
|
|
110
|
+
v: 2,
|
|
111
|
+
});
|
|
112
|
+
// 运行期更新触发回写
|
|
113
|
+
expect(loader.writes.length).toBeGreaterThan(0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("manager/unload 停用插件(原位改 ~ 与按位置插入)", async () => {
|
|
117
|
+
const listener =
|
|
118
|
+
app.console.listeners["manager/unload"];
|
|
119
|
+
// 原位重命名
|
|
120
|
+
await listener?.callback.call(
|
|
121
|
+
client as never,
|
|
122
|
+
"",
|
|
123
|
+
"keep:abc",
|
|
124
|
+
{
|
|
125
|
+
v: 3,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
await flushWrites();
|
|
129
|
+
const plugins = loader.config.plugins as Record<
|
|
130
|
+
string,
|
|
131
|
+
unknown
|
|
132
|
+
>;
|
|
133
|
+
expect("keep:abc" in plugins).toBe(false);
|
|
134
|
+
expect(plugins["~keep:abc"]).toEqual({ v: 3 });
|
|
135
|
+
const dummy = loader.data["keep"] as Plugin;
|
|
136
|
+
expect(app.registry.get(dummy)).toBeUndefined();
|
|
137
|
+
|
|
138
|
+
// 先补一个可停用键,再按 index 插入到指定位置
|
|
139
|
+
const reload =
|
|
140
|
+
app.console.listeners["manager/reload"];
|
|
141
|
+
await reload?.callback.call(
|
|
142
|
+
client as never,
|
|
143
|
+
"",
|
|
144
|
+
"back:two",
|
|
145
|
+
{
|
|
146
|
+
b: 1,
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
await flushWrites();
|
|
150
|
+
await listener?.callback.call(
|
|
151
|
+
client as never,
|
|
152
|
+
"",
|
|
153
|
+
"back:two",
|
|
154
|
+
{},
|
|
155
|
+
1,
|
|
156
|
+
);
|
|
157
|
+
await flushWrites();
|
|
158
|
+
const keys = Object.keys(
|
|
159
|
+
loader.config.plugins as object,
|
|
160
|
+
);
|
|
161
|
+
expect(keys.indexOf("~back:two")).toBe(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("manager/remove 彻底移除插件(含 ~ 停用态键)", async () => {
|
|
165
|
+
const listener =
|
|
166
|
+
app.console.listeners["manager/remove"];
|
|
167
|
+
await listener?.callback.call(
|
|
168
|
+
client as never,
|
|
169
|
+
"",
|
|
170
|
+
"~keep:abc",
|
|
171
|
+
);
|
|
172
|
+
await flushWrites();
|
|
173
|
+
const plugins = loader.config.plugins as Record<
|
|
174
|
+
string,
|
|
175
|
+
unknown
|
|
176
|
+
>;
|
|
177
|
+
expect("keep:abc" in plugins).toBe(false);
|
|
178
|
+
expect("~keep:abc" in plugins).toBe(false);
|
|
179
|
+
await listener?.callback.call(
|
|
180
|
+
client as never,
|
|
181
|
+
"",
|
|
182
|
+
"pos:one",
|
|
183
|
+
);
|
|
184
|
+
await flushWrites();
|
|
185
|
+
expect("pos:one" in plugins).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("manager/teleport 跨分组迁移与同分组重排", async () => {
|
|
189
|
+
const listener =
|
|
190
|
+
app.console.listeners["manager/teleport"];
|
|
191
|
+
const plugins = loader.config.plugins as Record<
|
|
192
|
+
string,
|
|
193
|
+
unknown
|
|
194
|
+
>;
|
|
195
|
+
// 跨分组:group:g1 → 根(index 0)
|
|
196
|
+
await listener?.callback.call(
|
|
197
|
+
client as never,
|
|
198
|
+
"g1",
|
|
199
|
+
"mov:me",
|
|
200
|
+
"",
|
|
201
|
+
0,
|
|
202
|
+
);
|
|
203
|
+
await flushWrites();
|
|
204
|
+
expect(plugins["mov:me"]).toEqual({ m: 1 });
|
|
205
|
+
const group = plugins["group:g1"] as Record<
|
|
206
|
+
string,
|
|
207
|
+
unknown
|
|
208
|
+
>;
|
|
209
|
+
expect("mov:me" in group).toBe(false);
|
|
210
|
+
// 运行时 fork 已改挂根作用域
|
|
211
|
+
const dummy = loader.data["mov"] as Plugin;
|
|
212
|
+
const fork = app.registry.get(dummy)?.children[0];
|
|
213
|
+
expect(fork?.parent.scope.uid).toBe(
|
|
214
|
+
loader.entry.scope.uid,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// 同分组:仅重排键位置
|
|
218
|
+
await listener?.callback.call(
|
|
219
|
+
client as never,
|
|
220
|
+
"",
|
|
221
|
+
"mov:me",
|
|
222
|
+
"",
|
|
223
|
+
0,
|
|
224
|
+
);
|
|
225
|
+
await flushWrites();
|
|
226
|
+
expect(Object.keys(plugins)[0]).toBe("mov:me");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
itQuiet(
|
|
230
|
+
["loader"],
|
|
231
|
+
"manager/* 失败统一回执 failed",
|
|
232
|
+
async () => {
|
|
233
|
+
const listener =
|
|
234
|
+
app.console.listeners["manager/teleport"];
|
|
235
|
+
await expect(
|
|
236
|
+
listener?.callback.call(
|
|
237
|
+
client as never,
|
|
238
|
+
"no-such-group",
|
|
239
|
+
"k",
|
|
240
|
+
"",
|
|
241
|
+
0,
|
|
242
|
+
),
|
|
243
|
+
).rejects.toThrow("failed");
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
it("manager/app-reload 替换全局配置并整进程重载", async () => {
|
|
248
|
+
const listener =
|
|
249
|
+
app.console.listeners["manager/app-reload"];
|
|
250
|
+
const writesBefore = loader.writes.length;
|
|
251
|
+
await listener?.callback.call(client as never, {
|
|
252
|
+
prefix: ["."],
|
|
253
|
+
});
|
|
254
|
+
await flushWrites();
|
|
255
|
+
expect(loader.config.prefix).toEqual(["."]);
|
|
256
|
+
// plugins 部分被保留
|
|
257
|
+
expect(loader.config.plugins).toBeTruthy();
|
|
258
|
+
expect(loader.writes.length).toBeGreaterThan(
|
|
259
|
+
writesBefore,
|
|
260
|
+
);
|
|
261
|
+
expect(loader.fullReloadCount).toBe(1);
|
|
262
|
+
// 非静默写盘触发 config 事件 → config 服务刷新推送
|
|
263
|
+
const messages = readSent().filter(
|
|
264
|
+
(msg) =>
|
|
265
|
+
msg.type === "data" && msg.body.key === "config",
|
|
266
|
+
);
|
|
267
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* config 插件的 manager/* 控制台事件签名(唯一权威定义)。
|
|
7
|
+
*
|
|
8
|
+
* 服务端(writer.ts)与浏览器端(client/components/utils.ts)分别对
|
|
9
|
+
* "@koishi-ce/console" 与 "@koishi-ce/plugin-console" 做同名声明合并:
|
|
10
|
+
* 浏览器端 tsconfig 无 paths,console 的类型来自宿主 shims 的手写环境
|
|
11
|
+
* 声明,模块名必须分开挂载。Events 文本以本文件的 ManagerEvents 为
|
|
12
|
+
* 唯一来源,两端经 extends 引用,避免双份镜像漂移。本文件须被两端
|
|
13
|
+
* import(type-only 即可)才会进入各自的类型程序。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** manager/* 事件的载荷签名(配置的重载 / 停用 / 移除 / 改名 / 拖拽)。 */
|
|
17
|
+
interface ManagerEvents {
|
|
18
|
+
"manager/app-reload"(config: unknown): void;
|
|
19
|
+
"manager/teleport"(
|
|
20
|
+
source: string,
|
|
21
|
+
key: string,
|
|
22
|
+
target: string,
|
|
23
|
+
index: number,
|
|
24
|
+
): void;
|
|
25
|
+
"manager/reload"(
|
|
26
|
+
parent: string,
|
|
27
|
+
key: string,
|
|
28
|
+
config: unknown,
|
|
29
|
+
): void;
|
|
30
|
+
"manager/unload"(
|
|
31
|
+
parent: string,
|
|
32
|
+
key: string,
|
|
33
|
+
config: unknown,
|
|
34
|
+
index?: number,
|
|
35
|
+
): void;
|
|
36
|
+
"manager/remove"(parent: string, key: string): void;
|
|
37
|
+
"manager/meta"(ident: string, config: unknown): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare module "@koishi-ce/console" {
|
|
41
|
+
interface Events extends ManagerEvents {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare module "@koishi-ce/plugin-console" {
|
|
45
|
+
interface Events extends ManagerEvents {}
|
|
46
|
+
}
|
package/src/shared/writer.ts
CHANGED
|
@@ -21,86 +21,17 @@ import {
|
|
|
21
21
|
remove,
|
|
22
22
|
} from "@koishi-ce/koishi";
|
|
23
23
|
import {
|
|
24
|
+
insertKey,
|
|
24
25
|
Loader,
|
|
25
26
|
type LoaderScope,
|
|
27
|
+
rename,
|
|
26
28
|
} from "@koishi-ce/loader";
|
|
27
29
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
declare module "@koishi-ce/console" {
|
|
31
|
-
interface Events {
|
|
32
|
-
"manager/app-reload"(config: unknown): void;
|
|
33
|
-
"manager/teleport"(
|
|
34
|
-
source: string,
|
|
35
|
-
key: string,
|
|
36
|
-
target: string,
|
|
37
|
-
index: number,
|
|
38
|
-
): void;
|
|
39
|
-
"manager/reload"(
|
|
40
|
-
parent: string,
|
|
41
|
-
key: string,
|
|
42
|
-
config: unknown,
|
|
43
|
-
): void;
|
|
44
|
-
"manager/unload"(
|
|
45
|
-
parent: string,
|
|
46
|
-
key: string,
|
|
47
|
-
config: unknown,
|
|
48
|
-
index?: number,
|
|
49
|
-
): void;
|
|
50
|
-
"manager/remove"(parent: string, key: string): void;
|
|
51
|
-
"manager/meta"(ident: string, config: unknown): void;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
30
|
+
// manager/* 事件的签名合并见同目录 console-events.ts(唯一权威定义)
|
|
31
|
+
import type {} from "./console-events.ts";
|
|
54
32
|
|
|
55
33
|
const logger = new Logger("loader");
|
|
56
34
|
|
|
57
|
-
/**
|
|
58
|
-
* 把 temp 中的键插入到 object 的开头(原有 rest 键依次后移),用于在
|
|
59
|
-
* 不破坏其它键相对顺序的前提下完成"插入到指定位置"的配置操作。
|
|
60
|
-
*
|
|
61
|
-
* 实现方式是先把 rest 中的键全部搬到 temp 里,再用 Object.assign 合并回来。
|
|
62
|
-
*
|
|
63
|
-
* @param object 目标配置对象(会被原地修改)
|
|
64
|
-
* @param temp 待插入的键值对
|
|
65
|
-
* @param rest 需要让位的既有键名列表
|
|
66
|
-
*/
|
|
67
|
-
function insertKey(
|
|
68
|
-
object: Record<string, unknown>,
|
|
69
|
-
temp: Record<string, unknown>,
|
|
70
|
-
rest: string[],
|
|
71
|
-
) {
|
|
72
|
-
for (const key of rest) {
|
|
73
|
-
temp[key] = object[key];
|
|
74
|
-
delete object[key];
|
|
75
|
-
}
|
|
76
|
-
Object.assign(object, temp);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 在对象中把键 old 重命名为 neo(并可顺带替换其值),且保持键原有的位置。
|
|
81
|
-
*
|
|
82
|
-
* @param object 目标配置对象
|
|
83
|
-
* @param old 旧键名(可为 `"~" + name` 形式的停用态键)
|
|
84
|
-
* @param neo 新键名
|
|
85
|
-
* @param value 新键对应的值
|
|
86
|
-
*/
|
|
87
|
-
function rename(
|
|
88
|
-
object: Record<string, unknown>,
|
|
89
|
-
old: string,
|
|
90
|
-
neo: string,
|
|
91
|
-
value: unknown,
|
|
92
|
-
) {
|
|
93
|
-
const keys = Object.keys(object);
|
|
94
|
-
const index = keys.findIndex(
|
|
95
|
-
(key) => key === old || key === `~${old}`,
|
|
96
|
-
);
|
|
97
|
-
const rest = index < 0 ? [] : keys.slice(index + 1);
|
|
98
|
-
const temp = { [neo]: value };
|
|
99
|
-
delete object[old];
|
|
100
|
-
delete object[`~${old}`];
|
|
101
|
-
insertKey(object, temp, rest);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
35
|
/**
|
|
105
36
|
* 从 plugins 配置中摘除指定的键(兼容 `~` 前缀的停用态键),返回以该键
|
|
106
37
|
* 为唯一成员的对象,便于随后通过 insertKey 插入到别处(teleport 场景)。
|