@jsbot-tellus/core 2.4.0 → 2.5.1

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 (183) hide show
  1. package/README.md +8 -5
  2. package/dist/app/cli.d.ts +10 -0
  3. package/dist/app/cli.d.ts.map +1 -0
  4. package/dist/app/cli.js +37 -0
  5. package/dist/app/cli.js.map +1 -0
  6. package/dist/app/runtime.d.ts +121 -0
  7. package/dist/app/runtime.d.ts.map +1 -0
  8. package/dist/app/runtime.js +296 -0
  9. package/dist/app/runtime.js.map +1 -0
  10. package/dist/app.js +63 -273
  11. package/dist/app.js.map +1 -1
  12. package/dist/core/dispatch/command.d.ts.map +1 -1
  13. package/dist/core/dispatch/command.js +5 -1
  14. package/dist/core/dispatch/command.js.map +1 -1
  15. package/dist/core/dispatch/router.d.ts.map +1 -1
  16. package/dist/core/dispatch/router.js +1 -1
  17. package/dist/core/dispatch/router.js.map +1 -1
  18. package/dist/core/plugin/command-params.d.ts +3 -36
  19. package/dist/core/plugin/command-params.d.ts.map +1 -1
  20. package/dist/core/plugin/command-params.js +2 -461
  21. package/dist/core/plugin/command-params.js.map +1 -1
  22. package/dist/core/plugin/install/paths.d.ts +7 -0
  23. package/dist/core/plugin/install/paths.d.ts.map +1 -0
  24. package/dist/core/plugin/install/paths.js +49 -0
  25. package/dist/core/plugin/install/paths.js.map +1 -0
  26. package/dist/core/plugin/install/state.d.ts +23 -0
  27. package/dist/core/plugin/install/state.d.ts.map +1 -0
  28. package/dist/core/plugin/install/state.js +129 -0
  29. package/dist/core/plugin/install/state.js.map +1 -0
  30. package/dist/core/plugin/install/validate-script.d.ts +3 -0
  31. package/dist/core/plugin/install/validate-script.d.ts.map +1 -0
  32. package/dist/core/plugin/install/validate-script.js +154 -0
  33. package/dist/core/plugin/install/validate-script.js.map +1 -0
  34. package/dist/core/plugin/install-dir.d.ts +3 -28
  35. package/dist/core/plugin/install-dir.d.ts.map +1 -1
  36. package/dist/core/plugin/install-dir.js +3 -326
  37. package/dist/core/plugin/install-dir.js.map +1 -1
  38. package/dist/core/plugin/installer/checks.d.ts +6 -0
  39. package/dist/core/plugin/installer/checks.d.ts.map +1 -0
  40. package/dist/core/plugin/installer/checks.js +99 -0
  41. package/dist/core/plugin/installer/checks.js.map +1 -0
  42. package/dist/core/plugin/installer/support.d.ts +37 -0
  43. package/dist/core/plugin/installer/support.d.ts.map +1 -0
  44. package/dist/core/plugin/installer/support.js +67 -0
  45. package/dist/core/plugin/installer/support.js.map +1 -0
  46. package/dist/core/plugin/installer/warnings.d.ts +5 -0
  47. package/dist/core/plugin/installer/warnings.d.ts.map +1 -0
  48. package/dist/core/plugin/installer/warnings.js +28 -0
  49. package/dist/core/plugin/installer/warnings.js.map +1 -0
  50. package/dist/core/plugin/installer.d.ts +4 -30
  51. package/dist/core/plugin/installer.d.ts.map +1 -1
  52. package/dist/core/plugin/installer.js +21 -202
  53. package/dist/core/plugin/installer.js.map +1 -1
  54. package/dist/core/plugin/params/compile.d.ts +3 -0
  55. package/dist/core/plugin/params/compile.d.ts.map +1 -0
  56. package/dist/core/plugin/params/compile.js +23 -0
  57. package/dist/core/plugin/params/compile.js.map +1 -0
  58. package/dist/core/plugin/params/declaration.d.ts +9 -0
  59. package/dist/core/plugin/params/declaration.d.ts.map +1 -0
  60. package/dist/core/plugin/params/declaration.js +223 -0
  61. package/dist/core/plugin/params/declaration.js.map +1 -0
  62. package/dist/core/plugin/params/input.d.ts +5 -0
  63. package/dist/core/plugin/params/input.d.ts.map +1 -0
  64. package/dist/core/plugin/params/input.js +181 -0
  65. package/dist/core/plugin/params/input.js.map +1 -0
  66. package/dist/core/plugin/params/reply.d.ts +3 -0
  67. package/dist/core/plugin/params/reply.d.ts.map +1 -0
  68. package/dist/core/plugin/params/reply.js +49 -0
  69. package/dist/core/plugin/params/reply.js.map +1 -0
  70. package/dist/core/plugin/params/type-level.d.ts +23 -0
  71. package/dist/core/plugin/params/type-level.d.ts.map +1 -0
  72. package/dist/core/plugin/params/type-level.js +2 -0
  73. package/dist/core/plugin/params/type-level.js.map +1 -0
  74. package/dist/core/plugin/params/types.d.ts +28 -0
  75. package/dist/core/plugin/params/types.d.ts.map +1 -0
  76. package/dist/core/plugin/params/types.js +12 -0
  77. package/dist/core/plugin/params/types.js.map +1 -0
  78. package/dist/core/plugin/params/values.d.ts +6 -0
  79. package/dist/core/plugin/params/values.d.ts.map +1 -0
  80. package/dist/core/plugin/params/values.js +108 -0
  81. package/dist/core/plugin/params/values.js.map +1 -0
  82. package/dist/core/web/api.d.ts +2 -14
  83. package/dist/core/web/api.d.ts.map +1 -1
  84. package/dist/core/web/api.js +8 -500
  85. package/dist/core/web/api.js.map +1 -1
  86. package/dist/core/web/routes/actions.d.ts +4 -0
  87. package/dist/core/web/routes/actions.d.ts.map +1 -0
  88. package/dist/core/web/routes/actions.js +33 -0
  89. package/dist/core/web/routes/actions.js.map +1 -0
  90. package/dist/core/web/routes/config.d.ts +5 -0
  91. package/dist/core/web/routes/config.d.ts.map +1 -0
  92. package/dist/core/web/routes/config.js +30 -0
  93. package/dist/core/web/routes/config.js.map +1 -0
  94. package/dist/core/web/routes/kv.d.ts +6 -0
  95. package/dist/core/web/routes/kv.d.ts.map +1 -0
  96. package/dist/core/web/routes/kv.js +80 -0
  97. package/dist/core/web/routes/kv.js.map +1 -0
  98. package/dist/core/web/routes/plugins.d.ts +5 -0
  99. package/dist/core/web/routes/plugins.d.ts.map +1 -0
  100. package/dist/core/web/routes/plugins.js +98 -0
  101. package/dist/core/web/routes/plugins.js.map +1 -0
  102. package/dist/core/web/routes/sessions.d.ts +8 -0
  103. package/dist/core/web/routes/sessions.d.ts.map +1 -0
  104. package/dist/core/web/routes/sessions.js +130 -0
  105. package/dist/core/web/routes/sessions.js.map +1 -0
  106. package/dist/core/web/routes/shared.d.ts +18 -0
  107. package/dist/core/web/routes/shared.d.ts.map +1 -0
  108. package/dist/core/web/routes/shared.js +19 -0
  109. package/dist/core/web/routes/shared.js.map +1 -0
  110. package/dist/core/web/routes/status.d.ts +6 -0
  111. package/dist/core/web/routes/status.d.ts.map +1 -0
  112. package/dist/core/web/routes/status.js +132 -0
  113. package/dist/core/web/routes/status.js.map +1 -0
  114. package/dist/core/web/server/auth.d.ts +17 -0
  115. package/dist/core/web/server/auth.d.ts.map +1 -0
  116. package/dist/core/web/server/auth.js +68 -0
  117. package/dist/core/web/server/auth.js.map +1 -0
  118. package/dist/core/web/server/csp.d.ts +11 -0
  119. package/dist/core/web/server/csp.d.ts.map +1 -0
  120. package/dist/core/web/server/csp.js +31 -0
  121. package/dist/core/web/server/csp.js.map +1 -0
  122. package/dist/core/web/server/request.d.ts +6 -0
  123. package/dist/core/web/server/request.d.ts.map +1 -0
  124. package/dist/core/web/server/request.js +33 -0
  125. package/dist/core/web/server/request.js.map +1 -0
  126. package/dist/core/web/server/security.d.ts +4 -0
  127. package/dist/core/web/server/security.d.ts.map +1 -0
  128. package/dist/core/web/server/security.js +12 -0
  129. package/dist/core/web/server/security.js.map +1 -0
  130. package/dist/core/web/server/sse.d.ts +18 -0
  131. package/dist/core/web/server/sse.d.ts.map +1 -0
  132. package/dist/core/web/server/sse.js +49 -0
  133. package/dist/core/web/server/sse.js.map +1 -0
  134. package/dist/core/web/server/static.d.ts +16 -0
  135. package/dist/core/web/server/static.d.ts.map +1 -0
  136. package/dist/core/web/server/static.js +80 -0
  137. package/dist/core/web/server/static.js.map +1 -0
  138. package/dist/core/web/server.d.ts +0 -7
  139. package/dist/core/web/server.d.ts.map +1 -1
  140. package/dist/core/web/server.js +41 -263
  141. package/dist/core/web/server.js.map +1 -1
  142. package/docs/architecture.md +28 -11
  143. package/docs/configuration.md +1 -1
  144. package/docs/migration.md +2 -2
  145. package/docs/plugin-api.md +3 -1
  146. package/docs/plugin-development.md +1 -1
  147. package/package.json +19 -17
  148. package/src/app/cli.ts +47 -0
  149. package/src/app/runtime.ts +522 -0
  150. package/src/app.ts +87 -395
  151. package/src/core/dispatch/command.ts +7 -1
  152. package/src/core/dispatch/router.ts +2 -1
  153. package/src/core/plugin/command-params.ts +8 -713
  154. package/src/core/plugin/install/paths.ts +72 -0
  155. package/src/core/plugin/install/state.ts +196 -0
  156. package/src/core/plugin/install/validate-script.ts +154 -0
  157. package/src/core/plugin/install-dir.ts +19 -427
  158. package/src/core/plugin/installer/checks.ts +137 -0
  159. package/src/core/plugin/installer/support.ts +120 -0
  160. package/src/core/plugin/installer/warnings.ts +52 -0
  161. package/src/core/plugin/installer.ts +52 -301
  162. package/src/core/plugin/params/compile.ts +41 -0
  163. package/src/core/plugin/params/declaration.ts +286 -0
  164. package/src/core/plugin/params/input.ts +254 -0
  165. package/src/core/plugin/params/reply.ts +59 -0
  166. package/src/core/plugin/params/type-level.ts +71 -0
  167. package/src/core/plugin/params/types.ts +49 -0
  168. package/src/core/plugin/params/values.ts +146 -0
  169. package/src/core/web/api.ts +33 -759
  170. package/src/core/web/routes/actions.ts +43 -0
  171. package/src/core/web/routes/config.ts +42 -0
  172. package/src/core/web/routes/kv.ts +122 -0
  173. package/src/core/web/routes/plugins.ts +151 -0
  174. package/src/core/web/routes/sessions.ts +188 -0
  175. package/src/core/web/routes/shared.ts +42 -0
  176. package/src/core/web/routes/status.ts +191 -0
  177. package/src/core/web/server/auth.ts +126 -0
  178. package/src/core/web/server/csp.ts +33 -0
  179. package/src/core/web/server/request.ts +38 -0
  180. package/src/core/web/server/security.ts +14 -0
  181. package/src/core/web/server/sse.ts +74 -0
  182. package/src/core/web/server/static.ts +112 -0
  183. package/src/core/web/server.ts +41 -383
@@ -1,713 +1,8 @@
1
- import { parseSegments } from '@snowluma/sdk'
2
-
3
- export type CommandParamType =
4
- 'string' | 'text' | 'number' | 'integer' | 'boolean' | 'at'
5
-
6
- export interface ParsedCommandParams {
7
- args: unknown[]
8
- ignored: string[]
9
- }
10
-
11
- export interface CompiledCommandParams {
12
- usage: string
13
- parse(input: string): ParsedCommandParams
14
- }
15
-
16
- export class CommandParamsError extends Error {
17
- readonly usage: string
18
-
19
- constructor(message: string, usage: string) {
20
- super(message)
21
- this.name = 'CommandParamsError'
22
- this.usage = usage
23
- }
24
- }
25
-
26
- interface ParamSpec {
27
- name: string
28
- type: CommandParamType
29
- required: boolean
30
- rest: boolean
31
- hasDefault: boolean
32
- defaultValue?: unknown
33
- }
34
-
35
- interface InputToken {
36
- value: string
37
- start: number
38
- }
39
-
40
- const NAME_PATTERN = /^[\p{L}_][\p{L}\p{N}_-]*$/u
41
-
42
- function declarationError(message: string): Error {
43
- return new Error(`参数声明错误:${message}`)
44
- }
45
-
46
- function parseType(value: string): CommandParamType {
47
- switch (value) {
48
- case 'string':
49
- case 'text':
50
- case 'number':
51
- case 'integer':
52
- case 'boolean':
53
- case 'at':
54
- return value
55
- default:
56
- throw declarationError(`未知的参数类型 "${value}"`)
57
- }
58
- }
59
-
60
- function validateName(value: string): string {
61
- const name = value.trim()
62
-
63
- if (!name) {
64
- throw declarationError('参数名不能为空')
65
- }
66
-
67
- if (!NAME_PATTERN.test(name)) {
68
- throw declarationError(`非法的参数名 "${name}"`)
69
- }
70
-
71
- return name
72
- }
73
-
74
- function splitDeclaration(input: string): string[] {
75
- const parts: string[] = []
76
- let index = 0
77
-
78
- while (index < input.length) {
79
- const char = input[index]
80
-
81
- if (char === undefined) break
82
-
83
- if (/\s/.test(char)) {
84
- index += 1
85
- continue
86
- }
87
-
88
- if (char !== '<' && char !== '[') {
89
- throw declarationError(`无法识别的字符 "${char}"`)
90
- }
91
-
92
- const close = char === '<' ? '>' : ']'
93
- let end = index + 1
94
- let quote: string | null = null
95
-
96
- for (; end < input.length; end += 1) {
97
- const current = input[end]
98
-
99
- if (current === undefined) break
100
-
101
- if (quote !== null) {
102
- if (current === '\\') {
103
- end += 1
104
- continue
105
- }
106
-
107
- if (current === quote) {
108
- quote = null
109
- }
110
-
111
- continue
112
- }
113
-
114
- if (current === '"' || current === "'") {
115
- quote = current
116
- continue
117
- }
118
-
119
- if (current === close) break
120
- }
121
-
122
- if (end >= input.length || input[end] !== close) {
123
- throw declarationError(`参数 "${input.slice(index)}" 缺少 ${close}`)
124
- }
125
-
126
- parts.push(input.slice(index, end + 1))
127
- index = end + 1
128
- }
129
-
130
- return parts
131
- }
132
-
133
- function unquote(value: string): string {
134
- const trimmed = value.trim()
135
- const quote = trimmed[0]
136
-
137
- if (quote !== '"' && quote !== "'") {
138
- return trimmed
139
- }
140
-
141
- if (trimmed.length < 2 || trimmed[trimmed.length - 1] !== quote) {
142
- throw declarationError('默认值引号未闭合')
143
- }
144
-
145
- let result = ''
146
-
147
- for (let index = 1; index < trimmed.length - 1; index += 1) {
148
- const char = trimmed[index]
149
-
150
- if (char === undefined) break
151
-
152
- if (char === '\\') {
153
- const next = trimmed[index + 1]
154
-
155
- if (next !== undefined) {
156
- result += next
157
- index += 1
158
- }
159
-
160
- continue
161
- }
162
-
163
- result += char
164
- }
165
-
166
- return result
167
- }
168
-
169
- function parseBoolean(value: string): boolean | null {
170
- const normalized = value.trim().toLowerCase()
171
-
172
- if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) {
173
- return true
174
- }
175
-
176
- if (['false', '0', 'no', 'n', 'off'].includes(normalized)) {
177
- return false
178
- }
179
-
180
- return null
181
- }
182
-
183
- function parseAt(value: string, name: string, usage: string): number {
184
- const raw = value.trim()
185
- const segments = parseSegments(raw)
186
- const segment = segments[0]
187
-
188
- if (segments.length === 1 && segment?.type === 'at') {
189
- const qq = segment.data.qq
190
-
191
- if (qq !== 'all') {
192
- const parsed = Number(qq)
193
-
194
- if (Number.isSafeInteger(parsed) && parsed > 0) {
195
- return parsed
196
- }
197
- }
198
- } else if (/^\d+$/.test(raw)) {
199
- const parsed = Number(raw)
200
-
201
- if (Number.isSafeInteger(parsed) && parsed > 0) {
202
- return parsed
203
- }
204
- }
205
-
206
- throw new CommandParamsError(
207
- `参数 ${name} 需要 @ 一个用户或填写 QQ 号`,
208
- usage
209
- )
210
- }
211
-
212
- function parseDefaultValue(
213
- type: CommandParamType,
214
- raw: string,
215
- name: string
216
- ): unknown {
217
- const value = unquote(raw)
218
-
219
- switch (type) {
220
- case 'string':
221
- case 'text':
222
- return value
223
- case 'number': {
224
- const number = value.trim() === '' ? Number.NaN : Number(value)
225
-
226
- if (!Number.isFinite(number)) {
227
- throw declarationError(`参数 ${name} 的默认值不是数字`)
228
- }
229
-
230
- return number
231
- }
232
- case 'integer': {
233
- const number = value.trim() === '' ? Number.NaN : Number(value)
234
-
235
- if (!Number.isInteger(number)) {
236
- throw declarationError(`参数 ${name} 的默认值不是整数`)
237
- }
238
-
239
- return number
240
- }
241
- case 'boolean': {
242
- const boolean = parseBoolean(value)
243
-
244
- if (boolean === null) {
245
- throw declarationError(`参数 ${name} 的默认值不是布尔值`)
246
- }
247
-
248
- return boolean
249
- }
250
- case 'at':
251
- throw declarationError(`参数 ${name} 不支持默认值`)
252
- }
253
- }
254
-
255
- function parseToken(token: string): ParamSpec {
256
- const open = token[0]
257
- const close = token[token.length - 1]
258
-
259
- if (
260
- (open !== '<' && open !== '[') ||
261
- (close !== '>' && close !== ']') ||
262
- (open === '<') !== (close === '>')
263
- ) {
264
- throw declarationError(`非法的参数声明 "${token}"`)
265
- }
266
-
267
- const required = open === '<'
268
- const inner = token.slice(1, -1).trim()
269
-
270
- if (!inner) {
271
- throw declarationError('参数名不能为空')
272
- }
273
-
274
- if (inner.startsWith('...')) {
275
- if (required) {
276
- throw declarationError('变长参数必须使用 [ ]')
277
- }
278
-
279
- const name = validateName(inner.slice(3))
280
-
281
- return {
282
- name,
283
- type: 'string',
284
- required: false,
285
- rest: true,
286
- hasDefault: false,
287
- }
288
- }
289
-
290
- if (inner.includes('...')) {
291
- throw declarationError(`非法的参数声明 "${token}"`)
292
- }
293
-
294
- const equals = inner.indexOf('=')
295
- const head = equals >= 0 ? inner.slice(0, equals).trim() : inner.trim()
296
- const defaultRaw = equals >= 0 ? inner.slice(equals + 1).trim() : undefined
297
- const colon = head.indexOf(':')
298
- const name = validateName(colon >= 0 ? head.slice(0, colon) : head)
299
- const type = parseType(colon >= 0 ? head.slice(colon + 1).trim() : 'string')
300
-
301
- if (required && defaultRaw !== undefined) {
302
- throw declarationError(`必填参数不能有默认值:${token}`)
303
- }
304
-
305
- if (defaultRaw !== undefined) {
306
- return {
307
- name,
308
- type,
309
- required,
310
- rest: false,
311
- hasDefault: true,
312
- defaultValue: parseDefaultValue(type, defaultRaw, name),
313
- }
314
- }
315
-
316
- return {
317
- name,
318
- type,
319
- required,
320
- rest: false,
321
- hasDefault: false,
322
- }
323
- }
324
-
325
- function validateSpecs(specs: ParamSpec[]): void {
326
- const names = new Set<string>()
327
- let seenOptional = false
328
- let seenRest = false
329
- let seenText = false
330
-
331
- for (let index = 0; index < specs.length; index += 1) {
332
- const spec = specs[index]
333
-
334
- if (!spec) continue
335
-
336
- if (names.has(spec.name)) {
337
- throw declarationError(`参数名重复 "${spec.name}"`)
338
- }
339
-
340
- names.add(spec.name)
341
-
342
- if (seenRest) {
343
- throw declarationError('变长参数后面不能再有参数')
344
- }
345
-
346
- if (seenText) {
347
- throw declarationError('文本参数后面不能再有参数')
348
- }
349
-
350
- if (spec.rest) {
351
- if (index !== specs.length - 1) {
352
- throw declarationError('变长参数必须放在最后')
353
- }
354
-
355
- seenRest = true
356
- continue
357
- }
358
-
359
- if (spec.type === 'text') {
360
- if (index !== specs.length - 1) {
361
- throw declarationError('文本参数必须放在最后')
362
- }
363
-
364
- seenText = true
365
- }
366
-
367
- if (spec.required) {
368
- if (seenOptional) {
369
- throw declarationError('必填参数不能出现在可选参数后面')
370
- }
371
- } else {
372
- seenOptional = true
373
- }
374
- }
375
- }
376
-
377
- function usageOf(specs: ParamSpec[]): string {
378
- return specs
379
- .map((spec) => {
380
- if (spec.rest) {
381
- return `[...${spec.name}]`
382
- }
383
-
384
- const name = spec.type === 'text' ? `${spec.name}...` : spec.name
385
-
386
- return spec.required ? `<${name}>` : `[${name}]`
387
- })
388
- .join(' ')
389
- }
390
-
391
- function tokenizeInput(input: string, usage: string): InputToken[] {
392
- const tokens: InputToken[] = []
393
- let index = 0
394
-
395
- while (index < input.length) {
396
- const char = input[index]
397
-
398
- if (char === undefined) break
399
-
400
- if (/\s/.test(char)) {
401
- index += 1
402
- continue
403
- }
404
-
405
- const start = index
406
- let value = ''
407
- let quote: string | null = null
408
-
409
- while (index < input.length) {
410
- const current = input[index]
411
-
412
- if (current === undefined) break
413
-
414
- if (quote !== null) {
415
- if (current === '\\') {
416
- const next = input[index + 1]
417
-
418
- if (next !== undefined) {
419
- value += next
420
- index += 2
421
- continue
422
- }
423
-
424
- value += current
425
- index += 1
426
- continue
427
- }
428
-
429
- if (current === quote) {
430
- quote = null
431
- index += 1
432
- continue
433
- }
434
-
435
- value += current
436
- index += 1
437
- continue
438
- }
439
-
440
- if (/\s/.test(current)) break
441
-
442
- if (current === '[' && input.startsWith('[CQ:', index)) {
443
- const close = input.indexOf(']', index + 4)
444
-
445
- if (close >= 0) {
446
- value += input.slice(index, close + 1)
447
- index = close + 1
448
- continue
449
- }
450
- }
451
-
452
- if (current === '"' || current === "'") {
453
- quote = current
454
- index += 1
455
- continue
456
- }
457
-
458
- if (current === '\\') {
459
- const next = input[index + 1]
460
-
461
- if (next !== undefined) {
462
- value += next
463
- index += 2
464
- continue
465
- }
466
-
467
- value += current
468
- index += 1
469
- continue
470
- }
471
-
472
- value += current
473
- index += 1
474
- }
475
-
476
- if (quote !== null) {
477
- throw new CommandParamsError('参数引号未闭合', usage)
478
- }
479
-
480
- tokens.push({ value, start })
481
- }
482
-
483
- return tokens
484
- }
485
-
486
- function parseValue(spec: ParamSpec, raw: string, usage: string): unknown {
487
- switch (spec.type) {
488
- case 'string':
489
- case 'text':
490
- return raw
491
- case 'number': {
492
- const value = raw.trim() === '' ? Number.NaN : Number(raw)
493
-
494
- if (!Number.isFinite(value)) {
495
- throw new CommandParamsError(
496
- `参数 ${spec.name} 需要是数字`,
497
- usage
498
- )
499
- }
500
-
501
- return value
502
- }
503
- case 'integer': {
504
- const value = raw.trim() === '' ? Number.NaN : Number(raw)
505
-
506
- if (!Number.isInteger(value)) {
507
- throw new CommandParamsError(
508
- `参数 ${spec.name} 需要是整数`,
509
- usage
510
- )
511
- }
512
-
513
- return value
514
- }
515
- case 'boolean': {
516
- const value = parseBoolean(raw)
517
-
518
- if (value === null) {
519
- throw new CommandParamsError(
520
- `参数 ${spec.name} 需要是布尔值`,
521
- usage
522
- )
523
- }
524
-
525
- return value
526
- }
527
- case 'at':
528
- return parseAt(raw, spec.name, usage)
529
- }
530
- }
531
-
532
- function parseInput(
533
- specs: ParamSpec[],
534
- input: string,
535
- usage: string
536
- ): ParsedCommandParams {
537
- const tokens = tokenizeInput(input, usage)
538
- const args: unknown[] = []
539
- let index = 0
540
-
541
- for (const spec of specs) {
542
- if (spec.rest) {
543
- for (
544
- let tokenIndex = index;
545
- tokenIndex < tokens.length;
546
- tokenIndex += 1
547
- ) {
548
- const token = tokens[tokenIndex]
549
-
550
- if (token) {
551
- args.push(token.value)
552
- }
553
- }
554
-
555
- index = tokens.length
556
- continue
557
- }
558
-
559
- if (spec.type === 'text') {
560
- const token = tokens[index]
561
-
562
- if (!token) {
563
- if (spec.hasDefault) {
564
- args.push(spec.defaultValue)
565
- } else if (spec.required) {
566
- throw new CommandParamsError(
567
- `缺少参数:${spec.name}`,
568
- usage
569
- )
570
- } else {
571
- args.push(undefined)
572
- }
573
-
574
- continue
575
- }
576
-
577
- const remaining = tokens.length - index
578
- const value =
579
- remaining === 1 ? token.value : input.slice(token.start).trim()
580
-
581
- args.push(value)
582
- index = tokens.length
583
- continue
584
- }
585
-
586
- const token = tokens[index]
587
-
588
- if (!token) {
589
- if (spec.hasDefault) {
590
- args.push(spec.defaultValue)
591
- } else if (spec.required) {
592
- throw new CommandParamsError(`缺少参数:${spec.name}`, usage)
593
- } else {
594
- args.push(undefined)
595
- }
596
-
597
- continue
598
- }
599
-
600
- index += 1
601
- args.push(parseValue(spec, token.value, usage))
602
- }
603
-
604
- while (args.length > 0 && args[args.length - 1] === undefined) {
605
- args.pop()
606
- }
607
-
608
- const ignored =
609
- index < tokens.length
610
- ? tokens.slice(index).map((token) => token.value)
611
- : []
612
-
613
- return { args, ignored }
614
- }
615
-
616
- export function compileCommandParams(
617
- declaration: string
618
- ): CompiledCommandParams {
619
- const trimmed = declaration.trim()
620
-
621
- if (!trimmed) {
622
- throw declarationError('参数声明不能为空')
623
- }
624
-
625
- const specs = splitDeclaration(trimmed).map(parseToken)
626
-
627
- if (specs.length === 0) {
628
- throw declarationError('参数声明不能为空')
629
- }
630
-
631
- validateSpecs(specs)
632
-
633
- const usage = usageOf(specs)
634
-
635
- return {
636
- usage,
637
- parse(input: string): ParsedCommandParams {
638
- return parseInput(specs, input, usage)
639
- },
640
- }
641
- }
642
-
643
- type Whitespace = ' ' | '\n' | '\r' | '\t' | '\v' | '\f'
644
-
645
- type TrimLeft<Source extends string> =
646
- Source extends `${Whitespace}${infer Rest}` ? TrimLeft<Rest> : Source
647
-
648
- type TrimRight<Source extends string> =
649
- Source extends `${infer Rest}${Whitespace}` ? TrimRight<Rest> : Source
650
-
651
- type Trim<Source extends string> = TrimLeft<TrimRight<Source>>
652
-
653
- type ParamValue<Type extends string> =
654
- Trim<Type> extends 'string' | 'text'
655
- ? string
656
- : Trim<Type> extends 'number' | 'integer' | 'at'
657
- ? number
658
- : Trim<Type> extends 'boolean'
659
- ? boolean
660
- : unknown
661
-
662
- interface VariadicParam {
663
- readonly variadic: true
664
- }
665
-
666
- type ParamType<Head extends string> = Head extends `${string}:${infer Type}`
667
- ? ParamValue<Type>
668
- : string
669
-
670
- type ParamElement<Body extends string, Required extends boolean> =
671
- Trim<Body> extends `...${string}`
672
- ? VariadicParam
673
- : Trim<Body> extends `${infer Head}=${string}`
674
- ? ParamType<Head>
675
- : Required extends true
676
- ? ParamType<Body>
677
- : ParamType<Body> | undefined
678
-
679
- type AppendParam<Acc extends unknown[], Element> = [Element] extends [
680
- VariadicParam,
681
- ]
682
- ? [...Acc, ...string[]]
683
- : [...Acc, Element]
684
-
685
- type ParseCommandParams<Source extends string, Acc extends unknown[] = []> =
686
- Trim<Source> extends ''
687
- ? Acc
688
- : Trim<Source> extends `<${infer Body}>${infer Rest}`
689
- ? ParamElement<Body, true> extends VariadicParam
690
- ? any[]
691
- : ParseCommandParams<
692
- Rest,
693
- AppendParam<Acc, ParamElement<Body, true>>
694
- >
695
- : Trim<Source> extends `[${infer Body}]${infer Rest}`
696
- ? ParamElement<Body, false> extends VariadicParam
697
- ? Trim<Rest> extends ''
698
- ? [...Acc, ...string[]]
699
- : any[]
700
- : ParseCommandParams<
701
- Rest,
702
- AppendParam<Acc, ParamElement<Body, false>>
703
- >
704
- : any[]
705
-
706
- /**
707
- * 把 `params` 声明字符串推导成 handler 的位置参数 tuple。
708
- *
709
- * 非字面量声明(例如运行时拼接的 `string`)或无法解析的声明会回退为
710
- * `any[]`,以保证现有插件仍然可以编译。
711
- */
712
- export type CommandParamArgs<Declaration extends string> =
713
- string extends Declaration ? any[] : ParseCommandParams<Declaration>
1
+ export {
2
+ CommandParamsError,
3
+ type CommandParamType,
4
+ type CompiledCommandParams,
5
+ type ParsedCommandParams,
6
+ } from './params/types.js'
7
+ export { compileCommandParams } from './params/compile.js'
8
+ export type { CommandParamArgs } from './params/type-level.js'