@jsbot-tellus/core 2.0.3 → 2.2.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.
Files changed (83) hide show
  1. package/README.md +23 -27
  2. package/dist/app.js +14 -40
  3. package/dist/app.js.map +1 -1
  4. package/dist/core/config/schema.d.ts +0 -2
  5. package/dist/core/config/schema.d.ts.map +1 -1
  6. package/dist/core/config/schema.js +0 -1
  7. package/dist/core/config/schema.js.map +1 -1
  8. package/dist/core/dispatch/command.d.ts +24 -2
  9. package/dist/core/dispatch/command.d.ts.map +1 -1
  10. package/dist/core/dispatch/command.js +297 -60
  11. package/dist/core/dispatch/command.js.map +1 -1
  12. package/dist/core/dispatch/router.d.ts +17 -3
  13. package/dist/core/dispatch/router.d.ts.map +1 -1
  14. package/dist/core/dispatch/router.js +38 -2
  15. package/dist/core/dispatch/router.js.map +1 -1
  16. package/dist/core/index.d.ts +2 -1
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/core/kernel.d.ts.map +1 -1
  20. package/dist/core/kernel.js +5 -1
  21. package/dist/core/kernel.js.map +1 -1
  22. package/dist/core/plugin/command-params.d.ts +37 -0
  23. package/dist/core/plugin/command-params.d.ts.map +1 -0
  24. package/dist/core/plugin/command-params.js +427 -0
  25. package/dist/core/plugin/command-params.js.map +1 -0
  26. package/dist/core/plugin/context.d.ts.map +1 -1
  27. package/dist/core/plugin/context.js +27 -0
  28. package/dist/core/plugin/context.js.map +1 -1
  29. package/dist/core/plugin/install-dir.d.ts +6 -9
  30. package/dist/core/plugin/install-dir.d.ts.map +1 -1
  31. package/dist/core/plugin/install-dir.js +99 -79
  32. package/dist/core/plugin/install-dir.js.map +1 -1
  33. package/dist/core/plugin/install-validate.d.ts +2 -1
  34. package/dist/core/plugin/install-validate.d.ts.map +1 -1
  35. package/dist/core/plugin/install-validate.js +25 -17
  36. package/dist/core/plugin/install-validate.js.map +1 -1
  37. package/dist/core/plugin/installer.d.ts +3 -3
  38. package/dist/core/plugin/installer.d.ts.map +1 -1
  39. package/dist/core/plugin/installer.js +143 -140
  40. package/dist/core/plugin/installer.js.map +1 -1
  41. package/dist/core/plugin/loader.d.ts +13 -1
  42. package/dist/core/plugin/loader.d.ts.map +1 -1
  43. package/dist/core/plugin/loader.js +106 -2
  44. package/dist/core/plugin/loader.js.map +1 -1
  45. package/dist/core/plugin/types.d.ts +14 -5
  46. package/dist/core/plugin/types.d.ts.map +1 -1
  47. package/dist/core/plugin/types.js.map +1 -1
  48. package/dist/plugins/admin/index.d.ts.map +1 -1
  49. package/dist/plugins/admin/index.js +119 -86
  50. package/dist/plugins/admin/index.js.map +1 -1
  51. package/dist/plugins/core/index.d.ts.map +1 -1
  52. package/dist/plugins/core/index.js +5 -2
  53. package/dist/plugins/core/index.js.map +1 -1
  54. package/docs/architecture.md +22 -24
  55. package/docs/configuration.md +19 -23
  56. package/docs/feature-parity.md +4 -4
  57. package/docs/migration.md +13 -13
  58. package/docs/plugin-api.md +164 -27
  59. package/docs/plugin-development.md +400 -0
  60. package/docs/release.md +2 -2
  61. package/examples/plugins/hello/README.md +10 -5
  62. package/examples/plugins/hello/index.js +3 -6
  63. package/package.json +4 -1
  64. package/src/app.ts +18 -55
  65. package/src/core/config/schema.ts +0 -1
  66. package/src/core/dispatch/command.ts +478 -70
  67. package/src/core/dispatch/router.ts +77 -3
  68. package/src/core/index.ts +3 -0
  69. package/src/core/kernel.ts +8 -1
  70. package/src/core/plugin/command-params.ts +667 -0
  71. package/src/core/plugin/context.ts +51 -2
  72. package/src/core/plugin/install-dir.ts +117 -111
  73. package/src/core/plugin/install-validate.ts +36 -34
  74. package/src/core/plugin/installer.ts +228 -184
  75. package/src/core/plugin/loader.ts +180 -3
  76. package/src/core/plugin/types.ts +31 -5
  77. package/src/plugins/admin/index.ts +302 -233
  78. package/src/plugins/core/index.ts +6 -2
  79. package/dist/core/plugin/installed.d.ts +0 -9
  80. package/dist/core/plugin/installed.d.ts.map +0 -1
  81. package/dist/core/plugin/installed.js +0 -113
  82. package/dist/core/plugin/installed.js.map +0 -1
  83. package/src/core/plugin/installed.ts +0 -165
@@ -1,12 +1,106 @@
1
- import { stat } from 'node:fs/promises'
2
- import { resolve } from 'node:path'
1
+ import { readdir, stat } from 'node:fs/promises'
2
+ import { join, resolve } from 'node:path'
3
3
  import { pathToFileURL } from 'node:url'
4
4
  import fg from 'fast-glob'
5
5
  import type { Logger } from '../logging/logger.js'
6
6
  import { collectPluginExports } from './exports.js'
7
+ import { readInstallState, readJsonFile } from './install-dir.js'
8
+ import { NpmCliRunner, type NpmRunner } from './npm-runner.js'
7
9
  import type { AnyPluginDefinition } from './types.js'
8
10
 
9
- type LoaderLogger = Pick<Logger, 'warn' | 'error'>
11
+ type LoaderLogger = Pick<Logger, 'warn' | 'error'> & {
12
+ info?: Logger['info'] | undefined
13
+ }
14
+
15
+ export interface LocalPluginLoaderOptions {
16
+ /** Build local plugins that have a build script but no dist. Defaults to true. */
17
+ autoBuild?: boolean | undefined
18
+ /** Timeout for a single local plugin build. Defaults to 120 seconds. */
19
+ buildTimeoutMs?: number | undefined
20
+ /** npm runner override, mainly for tests. */
21
+ runner?: NpmRunner | undefined
22
+ }
23
+
24
+ const DEFAULT_BUILD_TIMEOUT_MS = 120_000
25
+
26
+ function isRecord(value: unknown): value is Record<string, unknown> {
27
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
28
+ }
29
+
30
+ async function directoryExists(path: string): Promise<boolean> {
31
+ const info = await stat(path).catch(() => null)
32
+
33
+ return info?.isDirectory() ?? false
34
+ }
35
+
36
+ async function readBuildScript(directory: string): Promise<string | undefined> {
37
+ const manifest = await readJsonFile(join(directory, 'package.json')).catch(
38
+ () => undefined
39
+ )
40
+
41
+ if (!isRecord(manifest) || !isRecord(manifest['scripts'])) {
42
+ return undefined
43
+ }
44
+
45
+ const build = manifest['scripts']['build']
46
+
47
+ return typeof build === 'string' && build.trim().length > 0
48
+ ? build
49
+ : undefined
50
+ }
51
+
52
+ async function readManagedDirectories(
53
+ root: string
54
+ ): Promise<Set<string> | null> {
55
+ try {
56
+ const state = await readInstallState(root)
57
+
58
+ return new Set(
59
+ Object.values(state.packages).map((record) => record.dir)
60
+ )
61
+ } catch {
62
+ return null
63
+ }
64
+ }
65
+
66
+ async function buildLocalPlugin(
67
+ directory: string,
68
+ name: string,
69
+ logger: LoaderLogger | undefined,
70
+ runner: NpmRunner,
71
+ timeoutMs: number
72
+ ): Promise<boolean> {
73
+ if ((await readBuildScript(directory)) === undefined) {
74
+ logger?.warn(
75
+ `skipping local plugin ${name}: dist directory not found and package.json has no build script`
76
+ )
77
+ return false
78
+ }
79
+
80
+ if (!(await directoryExists(join(directory, 'node_modules')))) {
81
+ logger?.warn(
82
+ `skipping local plugin ${name}: dist directory not found and node_modules is missing; run npm install --prefix ${directory}`
83
+ )
84
+ return false
85
+ }
86
+
87
+ logger?.info?.(`building local plugin ${name}: dist directory not found`)
88
+
89
+ try {
90
+ await runner.run(['run', 'build'], {
91
+ cwd: directory,
92
+ timeoutMs,
93
+ })
94
+ } catch (error) {
95
+ logger?.error(`failed to build local plugin ${name}`, error)
96
+ logger?.warn(`skipping local plugin ${name}: build failed`)
97
+ return false
98
+ }
99
+
100
+ logger?.info?.(`built local plugin ${name}`)
101
+
102
+ return true
103
+ }
10
104
 
11
105
  export async function loadPluginsFromDirectory(
12
106
  directory: string,
@@ -60,3 +154,86 @@ export async function loadPluginsFromDirectory(
60
154
 
61
155
  return plugins
62
156
  }
157
+
158
+ export async function loadLocalPlugins(
159
+ root: string,
160
+ logger?: LoaderLogger | undefined,
161
+ options: LocalPluginLoaderOptions = {}
162
+ ): Promise<AnyPluginDefinition[]> {
163
+ const absolute = resolve(root)
164
+ const info = await stat(absolute).catch(() => null)
165
+
166
+ if (!info?.isDirectory()) return []
167
+
168
+ const entries = (await readdir(absolute, { withFileTypes: true })).sort(
169
+ (a, b) => a.name.localeCompare(b.name)
170
+ )
171
+ const plugins: AnyPluginDefinition[] = []
172
+
173
+ let autoBuild = options.autoBuild !== false
174
+ let managed = new Set<string>()
175
+
176
+ if (autoBuild) {
177
+ const directories = await readManagedDirectories(absolute)
178
+
179
+ if (directories === null) {
180
+ logger?.warn(
181
+ 'cannot read plugin install state; automatic local plugin build is disabled'
182
+ )
183
+ autoBuild = false
184
+ } else {
185
+ managed = directories
186
+ }
187
+ }
188
+
189
+ const runner = options.runner ?? new NpmCliRunner()
190
+ const timeoutMs = options.buildTimeoutMs ?? DEFAULT_BUILD_TIMEOUT_MS
191
+
192
+ for (const entry of entries) {
193
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue
194
+ if (entry.name === 'node_modules') continue
195
+
196
+ const directory = join(absolute, entry.name)
197
+ const dist = join(directory, 'dist')
198
+ let distInfo = await stat(dist).catch(() => null)
199
+
200
+ if (!distInfo?.isDirectory()) {
201
+ if (!autoBuild) {
202
+ logger?.warn(
203
+ `skipping local plugin ${entry.name}: dist directory not found`
204
+ )
205
+ continue
206
+ }
207
+
208
+ if (managed.has(entry.name)) {
209
+ logger?.warn(
210
+ `skipping local plugin ${entry.name}: dist directory not found (installed package is expected to include dist)`
211
+ )
212
+ continue
213
+ }
214
+
215
+ const built = await buildLocalPlugin(
216
+ directory,
217
+ entry.name,
218
+ logger,
219
+ runner,
220
+ timeoutMs
221
+ )
222
+
223
+ if (!built) continue
224
+
225
+ distInfo = await stat(dist).catch(() => null)
226
+
227
+ if (!distInfo?.isDirectory()) {
228
+ logger?.warn(
229
+ `skipping local plugin ${entry.name}: build finished but dist directory not found`
230
+ )
231
+ continue
232
+ }
233
+ }
234
+
235
+ plugins.push(...(await loadPluginsFromDirectory(dist, logger)))
236
+ }
237
+
238
+ return plugins
239
+ }
@@ -13,6 +13,7 @@ import type { SendOptions } from '../messaging/sender.js'
13
13
  import type { PluginKV } from '../plugin/kv.js'
14
14
  import type { Permission } from '../security/permission.js'
15
15
  import type { SessionStore } from '../storage/session.js'
16
+ import type { CommandParamArgs } from './command-params.js'
16
17
 
17
18
  export type EventType = 'message' | 'notice' | 'request' | 'meta'
18
19
 
@@ -28,6 +29,7 @@ export interface CommandInfo {
28
29
  aliases: string[]
29
30
  permission: Permission
30
31
  description?: string | undefined
32
+ usage?: string | undefined
31
33
  pluginId: string
32
34
  }
33
35
 
@@ -44,10 +46,12 @@ export interface EventContext<TEvent extends SnowLumaEvent = SnowLumaEvent> {
44
46
 
45
47
  export type CommandHandler<
46
48
  TEvent extends OneBotMessageEvent = OneBotMessageEvent,
49
+ TArgs extends unknown[] = [],
47
50
  > = (
48
51
  event: TEvent,
49
52
  context: EventContext<TEvent>,
50
- match: CommandMatch
53
+ match: CommandMatch,
54
+ ...args: TArgs
51
55
  ) => void | Promise<void>
52
56
 
53
57
  export type MessageHandler = (
@@ -71,12 +75,35 @@ export type PluginMiddleware = (
71
75
  next: () => Promise<void>
72
76
  ) => void | Promise<void>
73
77
 
74
- export interface CommandOptions {
78
+ export interface CommandOptions<Declaration extends string = string> {
75
79
  name: string
76
80
  aliases?: string[] | undefined
77
81
  prefixes?: string[] | undefined
78
82
  permission?: Permission | undefined
79
83
  description?: string | undefined
84
+ params?: Declaration | undefined
85
+ }
86
+
87
+ export interface CommandGroupOptions<
88
+ Declaration extends string = string,
89
+ > extends CommandOptions<Declaration> {
90
+ handler?:
91
+ | CommandHandler<OneBotMessageEvent, CommandParamArgs<Declaration>>
92
+ | undefined
93
+ }
94
+
95
+ export interface CommandGroupRegistrar {
96
+ register<Declaration extends string = string>(
97
+ options: CommandOptions<Declaration>,
98
+ handler: CommandHandler<
99
+ OneBotMessageEvent,
100
+ CommandParamArgs<Declaration>
101
+ >
102
+ ): () => void
103
+ group<Declaration extends string = string>(
104
+ options: CommandGroupOptions<Declaration>,
105
+ setup: (group: CommandGroupRegistrar) => void
106
+ ): () => void
80
107
  }
81
108
 
82
109
  export interface NoticeOptions {
@@ -94,8 +121,7 @@ export interface MiddlewareOptions {
94
121
  priority?: number | undefined
95
122
  }
96
123
 
97
- export interface CommandRegistrar {
98
- register(options: CommandOptions, handler: CommandHandler): () => void
124
+ export interface CommandRegistrar extends CommandGroupRegistrar {
99
125
  list(): ReadonlyArray<CommandInfo>
100
126
  }
101
127
 
@@ -164,7 +190,7 @@ export interface CoreServices {
164
190
  can(actual: Permission, required: Permission): boolean
165
191
  }
166
192
  commands: {
167
- list(): ReadonlyArray<CommandInfo>
193
+ list(event?: SnowLumaEvent): ReadonlyArray<CommandInfo>
168
194
  }
169
195
  plugins: PluginStatus
170
196
  installer: PluginInstaller