@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.
- package/README.md +23 -27
- package/dist/app.js +14 -40
- package/dist/app.js.map +1 -1
- package/dist/core/config/schema.d.ts +0 -2
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +0 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/dispatch/command.d.ts +24 -2
- package/dist/core/dispatch/command.d.ts.map +1 -1
- package/dist/core/dispatch/command.js +297 -60
- package/dist/core/dispatch/command.js.map +1 -1
- package/dist/core/dispatch/router.d.ts +17 -3
- package/dist/core/dispatch/router.d.ts.map +1 -1
- package/dist/core/dispatch/router.js +38 -2
- package/dist/core/dispatch/router.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/kernel.d.ts.map +1 -1
- package/dist/core/kernel.js +5 -1
- package/dist/core/kernel.js.map +1 -1
- package/dist/core/plugin/command-params.d.ts +37 -0
- package/dist/core/plugin/command-params.d.ts.map +1 -0
- package/dist/core/plugin/command-params.js +427 -0
- package/dist/core/plugin/command-params.js.map +1 -0
- package/dist/core/plugin/context.d.ts.map +1 -1
- package/dist/core/plugin/context.js +27 -0
- package/dist/core/plugin/context.js.map +1 -1
- package/dist/core/plugin/install-dir.d.ts +6 -9
- package/dist/core/plugin/install-dir.d.ts.map +1 -1
- package/dist/core/plugin/install-dir.js +99 -79
- package/dist/core/plugin/install-dir.js.map +1 -1
- package/dist/core/plugin/install-validate.d.ts +2 -1
- package/dist/core/plugin/install-validate.d.ts.map +1 -1
- package/dist/core/plugin/install-validate.js +25 -17
- package/dist/core/plugin/install-validate.js.map +1 -1
- package/dist/core/plugin/installer.d.ts +3 -3
- package/dist/core/plugin/installer.d.ts.map +1 -1
- package/dist/core/plugin/installer.js +143 -140
- package/dist/core/plugin/installer.js.map +1 -1
- package/dist/core/plugin/loader.d.ts +13 -1
- package/dist/core/plugin/loader.d.ts.map +1 -1
- package/dist/core/plugin/loader.js +106 -2
- package/dist/core/plugin/loader.js.map +1 -1
- package/dist/core/plugin/types.d.ts +14 -5
- package/dist/core/plugin/types.d.ts.map +1 -1
- package/dist/core/plugin/types.js.map +1 -1
- package/dist/plugins/admin/index.d.ts.map +1 -1
- package/dist/plugins/admin/index.js +119 -86
- package/dist/plugins/admin/index.js.map +1 -1
- package/dist/plugins/core/index.d.ts.map +1 -1
- package/dist/plugins/core/index.js +5 -2
- package/dist/plugins/core/index.js.map +1 -1
- package/docs/architecture.md +22 -24
- package/docs/configuration.md +19 -23
- package/docs/feature-parity.md +4 -4
- package/docs/migration.md +13 -13
- package/docs/plugin-api.md +164 -27
- package/docs/plugin-development.md +400 -0
- package/docs/release.md +2 -2
- package/examples/plugins/hello/README.md +10 -5
- package/examples/plugins/hello/index.js +3 -6
- package/package.json +4 -1
- package/src/app.ts +18 -55
- package/src/core/config/schema.ts +0 -1
- package/src/core/dispatch/command.ts +478 -70
- package/src/core/dispatch/router.ts +77 -3
- package/src/core/index.ts +3 -0
- package/src/core/kernel.ts +8 -1
- package/src/core/plugin/command-params.ts +667 -0
- package/src/core/plugin/context.ts +51 -2
- package/src/core/plugin/install-dir.ts +117 -111
- package/src/core/plugin/install-validate.ts +36 -34
- package/src/core/plugin/installer.ts +228 -184
- package/src/core/plugin/loader.ts +180 -3
- package/src/core/plugin/types.ts +31 -5
- package/src/plugins/admin/index.ts +302 -233
- package/src/plugins/core/index.ts +6 -2
- package/dist/core/plugin/installed.d.ts +0 -9
- package/dist/core/plugin/installed.d.ts.map +0 -1
- package/dist/core/plugin/installed.js +0 -113
- package/dist/core/plugin/installed.js.map +0 -1
- 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
|
+
}
|
package/src/core/plugin/types.ts
CHANGED
|
@@ -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
|