@zhin.js/command 1.0.6 → 1.0.9
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/README.md +18 -4
- package/lib/command-index.d.ts +4 -0
- package/lib/command-index.js +280 -45
- package/lib/definition.d.ts +83 -7
- package/lib/definition.js +90 -40
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/permit.d.ts +5 -0
- package/lib/permit.js +5 -0
- package/lib/provider.d.ts +2 -1
- package/lib/provider.js +47 -68
- package/package.json +4 -3
- package/src/command-index.ts +339 -45
- package/src/definition.ts +175 -43
- package/src/index.ts +10 -0
- package/src/permit.ts +14 -0
- package/src/provider.ts +68 -87
package/lib/definition.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { createCapabilityContext, } from '@zhin.js/feature-kit';
|
|
2
|
+
import { assertPermitSyntax } from '@zhin.js/permission';
|
|
2
3
|
const commandBrand = 'zhin.command/1';
|
|
4
|
+
export const commandParameterTypes = new Set([
|
|
5
|
+
'string',
|
|
6
|
+
'number',
|
|
7
|
+
'integer',
|
|
8
|
+
'float',
|
|
9
|
+
'boolean',
|
|
10
|
+
'word',
|
|
11
|
+
'text',
|
|
12
|
+
'mention',
|
|
13
|
+
'image',
|
|
14
|
+
'face',
|
|
15
|
+
'reply',
|
|
16
|
+
'forward',
|
|
17
|
+
'dice',
|
|
18
|
+
'rps',
|
|
19
|
+
]);
|
|
3
20
|
/**
|
|
4
21
|
* 定义一个命令模块(`commands/` 约定目录下默认导出)。
|
|
5
22
|
* @public 用户侧创作面,承诺 semver(见 docs/contributing/public-api-surface.md)。
|
|
@@ -8,8 +25,66 @@ export function defineCommand(definition) {
|
|
|
8
25
|
if (typeof definition.execute !== 'function') {
|
|
9
26
|
throw new TypeError('Command execute must be a function');
|
|
10
27
|
}
|
|
28
|
+
if (definition.params !== undefined) {
|
|
29
|
+
if (!definition.params || typeof definition.params !== 'object') {
|
|
30
|
+
throw new TypeError('Command params must be a Record<string, CommandParamSchema>');
|
|
31
|
+
}
|
|
32
|
+
for (const [name, schema] of Object.entries(definition.params)) {
|
|
33
|
+
if (!schema || typeof schema !== 'object'
|
|
34
|
+
|| !commandParameterTypes.has(schema.type)) {
|
|
35
|
+
throw new TypeError(`Command params.${name} requires a valid type`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
validateCommandAlias(definition.alias);
|
|
40
|
+
validateCommandPermit(definition.permit);
|
|
41
|
+
validateCommandShortcutShape(definition.shortcut);
|
|
11
42
|
return Object.freeze({ $feature: commandBrand, ...definition });
|
|
12
43
|
}
|
|
44
|
+
function validateCommandAlias(alias) {
|
|
45
|
+
if (alias === undefined)
|
|
46
|
+
return;
|
|
47
|
+
if (!Array.isArray(alias)) {
|
|
48
|
+
throw new TypeError('Command alias must be a readonly string[]');
|
|
49
|
+
}
|
|
50
|
+
for (const [index, entry] of alias.entries()) {
|
|
51
|
+
if (typeof entry !== 'string') {
|
|
52
|
+
throw new TypeError(`Command alias[${index}] must be a string`);
|
|
53
|
+
}
|
|
54
|
+
const tokens = entry.trim().split(/\s+/u).filter(Boolean);
|
|
55
|
+
if (tokens.length === 0) {
|
|
56
|
+
throw new TypeError(`Command alias[${index}] must contain at least one token`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function validateCommandPermit(permit) {
|
|
61
|
+
if (permit === undefined)
|
|
62
|
+
return;
|
|
63
|
+
if (!Array.isArray(permit)) {
|
|
64
|
+
throw new TypeError('Command permit must be a readonly string[]');
|
|
65
|
+
}
|
|
66
|
+
for (const [index, entry] of permit.entries()) {
|
|
67
|
+
if (typeof entry !== 'string') {
|
|
68
|
+
throw new TypeError(`Command permit[${index}] must be a string`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
assertPermitSyntax(permit);
|
|
72
|
+
}
|
|
73
|
+
function validateCommandShortcutShape(shortcut) {
|
|
74
|
+
if (shortcut === undefined)
|
|
75
|
+
return;
|
|
76
|
+
if (!shortcut || typeof shortcut !== 'object' || Array.isArray(shortcut)) {
|
|
77
|
+
throw new TypeError('Command shortcut must be a Record<string, Record<string, value>>');
|
|
78
|
+
}
|
|
79
|
+
for (const [trigger, params] of Object.entries(shortcut)) {
|
|
80
|
+
if (!trigger.trim()) {
|
|
81
|
+
throw new TypeError('Command shortcut keys must be non-empty after trim');
|
|
82
|
+
}
|
|
83
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
84
|
+
throw new TypeError(`Command shortcut[${JSON.stringify(trigger)}] must be a params Record`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
13
88
|
export function bindCommandParameter(definition, parameter) {
|
|
14
89
|
if (!parameter)
|
|
15
90
|
return definition;
|
|
@@ -47,10 +122,9 @@ export function resolveCommandSession(input) {
|
|
|
47
122
|
const metadata = input.metadata && typeof input.metadata === 'object'
|
|
48
123
|
? input.metadata
|
|
49
124
|
: undefined;
|
|
50
|
-
const adapter = input.adapter
|
|
51
|
-
const endpoint =
|
|
52
|
-
? metadata.endpoint
|
|
53
|
-
: undefined;
|
|
125
|
+
const adapter = input.conversation.endpoint.adapter || undefined;
|
|
126
|
+
const endpoint = input.endpointId
|
|
127
|
+
|| (typeof metadata?.endpoint === 'string' && metadata.endpoint ? metadata.endpoint : undefined);
|
|
54
128
|
const scene = resolveScene(input, metadata);
|
|
55
129
|
const sender = resolveSender(input, metadata);
|
|
56
130
|
return Object.freeze({
|
|
@@ -64,8 +138,8 @@ function isCommandMessageLike(input) {
|
|
|
64
138
|
if (!input || typeof input !== 'object')
|
|
65
139
|
return false;
|
|
66
140
|
const value = input;
|
|
67
|
-
return
|
|
68
|
-
&& typeof value.
|
|
141
|
+
return !!value.conversation
|
|
142
|
+
&& typeof value.conversation === 'object'
|
|
69
143
|
&& typeof value.content === 'string';
|
|
70
144
|
}
|
|
71
145
|
function resolveScene(input, metadata) {
|
|
@@ -76,12 +150,9 @@ function resolveScene(input, metadata) {
|
|
|
76
150
|
...(input.scene.name !== undefined ? { name: input.scene.name } : {}),
|
|
77
151
|
});
|
|
78
152
|
}
|
|
79
|
-
const
|
|
80
|
-
const type =
|
|
81
|
-
|
|
82
|
-
|| parsed?.type;
|
|
83
|
-
const id = (typeof metadata?.channelId === 'string' && metadata.channelId)
|
|
84
|
-
|| parsed?.id;
|
|
153
|
+
const conversation = input.conversation;
|
|
154
|
+
const type = conversation.kind;
|
|
155
|
+
const id = conversation.id;
|
|
85
156
|
if (!type || !id)
|
|
86
157
|
return undefined;
|
|
87
158
|
const name = firstString(metadata?.channelName, metadata?.group_name, metadata?.groupName, metadata?.sceneName);
|
|
@@ -96,24 +167,21 @@ function resolveSender(input, metadata) {
|
|
|
96
167
|
if (isCommandSender(structured)) {
|
|
97
168
|
return freezeSender(structured);
|
|
98
169
|
}
|
|
99
|
-
|
|
100
|
-
if (isCommandSender(input.sender)) {
|
|
101
|
-
return freezeSender(input.sender);
|
|
102
|
-
}
|
|
103
|
-
const id = typeof input.sender === 'string' && input.sender
|
|
104
|
-
? input.sender
|
|
105
|
-
: firstString(metadata?.user_id, metadata?.userId);
|
|
170
|
+
const id = input.sender?.id || firstString(metadata?.user_id, metadata?.userId);
|
|
106
171
|
if (!id)
|
|
107
172
|
return undefined;
|
|
108
|
-
const name = firstString(metadata?.nickname, metadata?.senderName, metadata?.name);
|
|
109
|
-
const role = resolveRoles(metadata);
|
|
173
|
+
const name = input.sender?.name || firstString(metadata?.nickname, metadata?.senderName, metadata?.name);
|
|
174
|
+
const role = resolveRoles(input, metadata);
|
|
110
175
|
return Object.freeze({
|
|
111
176
|
id,
|
|
112
177
|
...(name !== undefined ? { name } : {}),
|
|
113
178
|
role,
|
|
114
179
|
});
|
|
115
180
|
}
|
|
116
|
-
function resolveRoles(metadata) {
|
|
181
|
+
function resolveRoles(input, metadata) {
|
|
182
|
+
if (input.sender?.roles?.length) {
|
|
183
|
+
return Object.freeze([...input.sender.roles]);
|
|
184
|
+
}
|
|
117
185
|
const roles = [];
|
|
118
186
|
const push = (value) => {
|
|
119
187
|
if (typeof value !== 'string')
|
|
@@ -136,24 +204,6 @@ function resolveRoles(metadata) {
|
|
|
136
204
|
roles.push('user');
|
|
137
205
|
return Object.freeze(roles);
|
|
138
206
|
}
|
|
139
|
-
function parseTarget(target) {
|
|
140
|
-
const parts = target.split(':').filter(Boolean);
|
|
141
|
-
if (parts.length < 2)
|
|
142
|
-
return undefined;
|
|
143
|
-
const [kind, ...rest] = parts;
|
|
144
|
-
if (!kind)
|
|
145
|
-
return undefined;
|
|
146
|
-
const lastPart = parts.at(-1);
|
|
147
|
-
if (!lastPart)
|
|
148
|
-
return undefined;
|
|
149
|
-
if (kind === 'channel' && parts.length >= 3) {
|
|
150
|
-
return { type: 'channel', id: lastPart };
|
|
151
|
-
}
|
|
152
|
-
if (kind === 'temp' && parts.length >= 3) {
|
|
153
|
-
return { type: 'private', id: lastPart };
|
|
154
|
-
}
|
|
155
|
-
return { type: kind, id: rest.join(':') };
|
|
156
|
-
}
|
|
157
207
|
function isCommandScene(value) {
|
|
158
208
|
if (!value || typeof value !== 'object')
|
|
159
209
|
return false;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './command-index.js';
|
|
2
2
|
export * from './definition.js';
|
|
3
|
+
export { assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, isBuiltinPermit, isPlatformPermit, parsePermitName, type ParsedPermit, type PermitKind, } from './permit.js';
|
|
3
4
|
export { CommandPathSyntaxError, commandFeatureId, default as commandFeature, } from './provider.js';
|
|
4
5
|
export { default } from './provider.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './command-index.js';
|
|
2
2
|
export * from './definition.js';
|
|
3
|
+
export { assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, isBuiltinPermit, isPlatformPermit, parsePermitName, } from './permit.js';
|
|
3
4
|
export { CommandPathSyntaxError, commandFeatureId, default as commandFeature, } from './provider.js';
|
|
4
5
|
export { default } from './provider.js';
|
package/lib/permit.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export permit 解析/校验 from @zhin.js/permission (SSOT)。
|
|
3
|
+
* 保留 CommandSession 兼容签名供 command-index 过渡使用。
|
|
4
|
+
*/
|
|
5
|
+
export { type PermitKind, type ParsedPermit, parsePermitName, isBuiltinPermit, isPlatformPermit, assertPermitSyntax as assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, } from '@zhin.js/permission';
|
package/lib/permit.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export permit 解析/校验 from @zhin.js/permission (SSOT)。
|
|
3
|
+
* 保留 CommandSession 兼容签名供 command-index 过渡使用。
|
|
4
|
+
*/
|
|
5
|
+
export { parsePermitName, isBuiltinPermit, isPlatformPermit, assertPermitSyntax as assertBuiltinPermits, checkBuiltinPermit, checkBuiltinPermitList, } from '@zhin.js/permission';
|
package/lib/provider.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CommandIndex } from './command-index.js';
|
|
2
|
+
import { type CommandDefinition } from './definition.js';
|
|
2
3
|
export declare const commandFeatureId: import("@zhin.js/plugin-runtime").FeatureId;
|
|
3
4
|
export declare class CommandPathSyntaxError extends TypeError {
|
|
4
5
|
constructor(file: string, detail?: string);
|
|
5
6
|
}
|
|
6
|
-
declare const commandFeature: Readonly<import("@zhin.js/feature-kit").FeatureProvider<
|
|
7
|
+
declare const commandFeature: Readonly<import("@zhin.js/feature-kit").FeatureProvider<CommandDefinition<unknown, unknown, import("./definition.js").CommandMessage>, CommandIndex>>;
|
|
7
8
|
export default commandFeature;
|
package/lib/provider.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { basename, join, parse, sep } from 'node:path';
|
|
2
|
-
import { featureId } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { featureId, isCapabilityLocalSegment } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import { defineFeatureProvider, } from '@zhin.js/feature-kit';
|
|
4
4
|
import { CommandIndex } from './command-index.js';
|
|
5
5
|
import { bindCommandParameter, parseCommandDefinition, } from './definition.js';
|
|
@@ -14,7 +14,7 @@ const commandFiles = {
|
|
|
14
14
|
const module = await context.host.loadModule(source.source);
|
|
15
15
|
const definition = parseCommandDefinition(module.default);
|
|
16
16
|
const file = parseCommandFile(basename(source.source));
|
|
17
|
-
return bindCommandParameter(definition, file
|
|
17
|
+
return bindCommandParameter(definition, resolveParameter(definition, file, source.source));
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
async function* discoverCommandDirectory(context, directory, ancestors) {
|
|
@@ -38,7 +38,7 @@ async function* discoverCommandDirectory(context, directory, ancestors) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
for (const entry of entries) {
|
|
41
|
-
if (entry.kind === 'directory' &&
|
|
41
|
+
if (entry.kind === 'directory' && isCapabilityLocalSegment(entry.name)) {
|
|
42
42
|
yield* discoverCommandDirectory(context, join(directory, entry.name), [...ancestors, entry.name]);
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
@@ -56,48 +56,58 @@ async function* discoverCommandDirectory(context, directory, ancestors) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'integer',
|
|
67
|
-
'float',
|
|
68
|
-
'boolean',
|
|
69
|
-
'word',
|
|
70
|
-
'text',
|
|
71
|
-
'mention',
|
|
72
|
-
'image',
|
|
73
|
-
'face',
|
|
74
|
-
'reply',
|
|
75
|
-
'forward',
|
|
76
|
-
'dice',
|
|
77
|
-
'rps',
|
|
78
|
-
]);
|
|
59
|
+
const dynamicCommandFilePatterns = [
|
|
60
|
+
{ pattern: /^\[\[\.\.\.([a-zA-Z][a-zA-Z0-9]*)\]\]\.(?:tsx?|[cm]?js)$/, optional: true, rest: true },
|
|
61
|
+
{ pattern: /^\[\.\.\.([a-zA-Z][a-zA-Z0-9]*)\]\.(?:tsx?|[cm]?js)$/, optional: false, rest: true },
|
|
62
|
+
{ pattern: /^\[\[([a-zA-Z][a-zA-Z0-9]*)\]\]\.(?:tsx?|[cm]?js)$/, optional: true, rest: false },
|
|
63
|
+
{ pattern: /^\[([a-zA-Z][a-zA-Z0-9]*)\]\.(?:tsx?|[cm]?js)$/, optional: false, rest: false },
|
|
64
|
+
];
|
|
65
|
+
const commandModuleExtension = /\.(?:tsx?|[cm]?js)$/u;
|
|
79
66
|
function parseCommandFile(value) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const [, name, rawType, rawDefault] = match;
|
|
86
|
-
if (!name || !rawType || !commandParameterTypes.has(rawType)) {
|
|
87
|
-
throw new CommandPathSyntaxError(value, `unsupported parameter type: ${rawType ?? ''}`);
|
|
67
|
+
// 静态段:ASCII kebab(hello.ts)或 Unicode 名(赞我.ts);与 isCapabilityLocalSegment 对齐。
|
|
68
|
+
if (commandModuleExtension.test(value)) {
|
|
69
|
+
const localSegment = parse(value).name;
|
|
70
|
+
if (isCapabilityLocalSegment(localSegment)) {
|
|
71
|
+
return { localSegment };
|
|
88
72
|
}
|
|
89
|
-
|
|
73
|
+
}
|
|
74
|
+
for (const { pattern, optional, rest } of dynamicCommandFilePatterns) {
|
|
75
|
+
const match = pattern.exec(value);
|
|
76
|
+
if (!match || !match[1])
|
|
77
|
+
continue;
|
|
78
|
+
const name = match[1];
|
|
90
79
|
// Metadata can change during HMR while $name keeps the Capability identity stable.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
: { name,
|
|
94
|
-
|
|
80
|
+
return {
|
|
81
|
+
localSegment: `$${name}`,
|
|
82
|
+
parameter: { name, optional, rest },
|
|
83
|
+
};
|
|
95
84
|
}
|
|
96
85
|
if (value.startsWith('[') || value.includes(']')) {
|
|
97
86
|
throw new CommandPathSyntaxError(value);
|
|
98
87
|
}
|
|
99
88
|
return undefined;
|
|
100
89
|
}
|
|
90
|
+
/** 把文件名形态与 `definition.params` 合并成完整参数定义。 */
|
|
91
|
+
function resolveParameter(definition, file, source) {
|
|
92
|
+
const hint = file?.parameter;
|
|
93
|
+
if (!hint)
|
|
94
|
+
return undefined;
|
|
95
|
+
const schema = definition.params?.[hint.name];
|
|
96
|
+
if (!schema) {
|
|
97
|
+
throw new CommandPathSyntaxError(source, `missing params.${hint.name} declaration in defineCommand({ params })`);
|
|
98
|
+
}
|
|
99
|
+
if (!hint.optional && schema.default !== undefined) {
|
|
100
|
+
throw new CommandPathSyntaxError(source, `params.${hint.name} has a default but the file is required: rename to [[${hint.name}]]`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
name: hint.name,
|
|
104
|
+
type: schema.type,
|
|
105
|
+
...(schema.default !== undefined ? { defaultValue: schema.default } : {}),
|
|
106
|
+
optional: hint.optional,
|
|
107
|
+
rest: hint.rest,
|
|
108
|
+
...(schema.description !== undefined ? { description: schema.description } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
101
111
|
function commandFilePriority(value, preferJavaScript) {
|
|
102
112
|
const extension = value.slice(value.lastIndexOf('.') + 1);
|
|
103
113
|
const order = preferJavaScript
|
|
@@ -106,39 +116,8 @@ function commandFilePriority(value, preferJavaScript) {
|
|
|
106
116
|
const priority = order.indexOf(extension);
|
|
107
117
|
return priority < 0 ? Number.MAX_SAFE_INTEGER : priority;
|
|
108
118
|
}
|
|
109
|
-
function parseParameterValue(name, type, value, source) {
|
|
110
|
-
if (type === 'string' || type === 'word' || type === 'text')
|
|
111
|
-
return value;
|
|
112
|
-
if (type === 'number' || type === 'integer' || type === 'float') {
|
|
113
|
-
const number = Number(value);
|
|
114
|
-
if (value.trim().length > 0
|
|
115
|
-
&& Number.isFinite(number)
|
|
116
|
-
&& (type !== 'integer' || Number.isInteger(number))
|
|
117
|
-
&& (type !== 'float' || value.includes('.')))
|
|
118
|
-
return number;
|
|
119
|
-
throw new CommandPathSyntaxError(source, `default for ${name}:${type} is invalid`);
|
|
120
|
-
}
|
|
121
|
-
if (type === 'boolean') {
|
|
122
|
-
if (value === 'true' || value === 'false')
|
|
123
|
-
return value === 'true';
|
|
124
|
-
throw new CommandPathSyntaxError(source, `default for ${name}:${type} is invalid`);
|
|
125
|
-
}
|
|
126
|
-
if (isStructuredParameter(type)) {
|
|
127
|
-
throw new CommandPathSyntaxError(source, `default for structured parameter ${name}:${type} is not supported`);
|
|
128
|
-
}
|
|
129
|
-
throw new CommandPathSyntaxError(source, `default for ${name}:${type} is invalid`);
|
|
130
|
-
}
|
|
131
|
-
function isStructuredParameter(type) {
|
|
132
|
-
return type === 'mention'
|
|
133
|
-
|| type === 'image'
|
|
134
|
-
|| type === 'face'
|
|
135
|
-
|| type === 'reply'
|
|
136
|
-
|| type === 'forward'
|
|
137
|
-
|| type === 'dice'
|
|
138
|
-
|| type === 'rps';
|
|
139
|
-
}
|
|
140
119
|
export class CommandPathSyntaxError extends TypeError {
|
|
141
|
-
constructor(file, detail = 'expected [name
|
|
120
|
+
constructor(file, detail = 'expected [name].ts(x), [[name]].ts(x), [...name].ts(x) or [[...name]].ts(x)') {
|
|
142
121
|
super(`Invalid Command path ${file}: ${detail}`);
|
|
143
122
|
this.name = 'CommandPathSyntaxError';
|
|
144
123
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/command",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Convention-based Command Feature for Zhin Plugin Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"segment-matcher": "^1.0.5",
|
|
21
|
-
"@zhin.js/feature-kit": "1.0.
|
|
22
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
21
|
+
"@zhin.js/feature-kit": "1.0.8",
|
|
22
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
23
|
+
"@zhin.js/permission": "1.0.1"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@types/node": "^26.1.2",
|