@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.
- package/README.md +8 -5
- package/dist/app/cli.d.ts +10 -0
- package/dist/app/cli.d.ts.map +1 -0
- package/dist/app/cli.js +37 -0
- package/dist/app/cli.js.map +1 -0
- package/dist/app/runtime.d.ts +121 -0
- package/dist/app/runtime.d.ts.map +1 -0
- package/dist/app/runtime.js +296 -0
- package/dist/app/runtime.js.map +1 -0
- package/dist/app.js +63 -273
- package/dist/app.js.map +1 -1
- package/dist/core/dispatch/command.d.ts.map +1 -1
- package/dist/core/dispatch/command.js +5 -1
- package/dist/core/dispatch/command.js.map +1 -1
- package/dist/core/dispatch/router.d.ts.map +1 -1
- package/dist/core/dispatch/router.js +1 -1
- package/dist/core/dispatch/router.js.map +1 -1
- package/dist/core/plugin/command-params.d.ts +3 -36
- package/dist/core/plugin/command-params.d.ts.map +1 -1
- package/dist/core/plugin/command-params.js +2 -461
- package/dist/core/plugin/command-params.js.map +1 -1
- package/dist/core/plugin/install/paths.d.ts +7 -0
- package/dist/core/plugin/install/paths.d.ts.map +1 -0
- package/dist/core/plugin/install/paths.js +49 -0
- package/dist/core/plugin/install/paths.js.map +1 -0
- package/dist/core/plugin/install/state.d.ts +23 -0
- package/dist/core/plugin/install/state.d.ts.map +1 -0
- package/dist/core/plugin/install/state.js +129 -0
- package/dist/core/plugin/install/state.js.map +1 -0
- package/dist/core/plugin/install/validate-script.d.ts +3 -0
- package/dist/core/plugin/install/validate-script.d.ts.map +1 -0
- package/dist/core/plugin/install/validate-script.js +154 -0
- package/dist/core/plugin/install/validate-script.js.map +1 -0
- package/dist/core/plugin/install-dir.d.ts +3 -28
- package/dist/core/plugin/install-dir.d.ts.map +1 -1
- package/dist/core/plugin/install-dir.js +3 -326
- package/dist/core/plugin/install-dir.js.map +1 -1
- package/dist/core/plugin/installer/checks.d.ts +6 -0
- package/dist/core/plugin/installer/checks.d.ts.map +1 -0
- package/dist/core/plugin/installer/checks.js +99 -0
- package/dist/core/plugin/installer/checks.js.map +1 -0
- package/dist/core/plugin/installer/support.d.ts +37 -0
- package/dist/core/plugin/installer/support.d.ts.map +1 -0
- package/dist/core/plugin/installer/support.js +67 -0
- package/dist/core/plugin/installer/support.js.map +1 -0
- package/dist/core/plugin/installer/warnings.d.ts +5 -0
- package/dist/core/plugin/installer/warnings.d.ts.map +1 -0
- package/dist/core/plugin/installer/warnings.js +28 -0
- package/dist/core/plugin/installer/warnings.js.map +1 -0
- package/dist/core/plugin/installer.d.ts +4 -30
- package/dist/core/plugin/installer.d.ts.map +1 -1
- package/dist/core/plugin/installer.js +21 -202
- package/dist/core/plugin/installer.js.map +1 -1
- package/dist/core/plugin/params/compile.d.ts +3 -0
- package/dist/core/plugin/params/compile.d.ts.map +1 -0
- package/dist/core/plugin/params/compile.js +23 -0
- package/dist/core/plugin/params/compile.js.map +1 -0
- package/dist/core/plugin/params/declaration.d.ts +9 -0
- package/dist/core/plugin/params/declaration.d.ts.map +1 -0
- package/dist/core/plugin/params/declaration.js +223 -0
- package/dist/core/plugin/params/declaration.js.map +1 -0
- package/dist/core/plugin/params/input.d.ts +5 -0
- package/dist/core/plugin/params/input.d.ts.map +1 -0
- package/dist/core/plugin/params/input.js +181 -0
- package/dist/core/plugin/params/input.js.map +1 -0
- package/dist/core/plugin/params/reply.d.ts +3 -0
- package/dist/core/plugin/params/reply.d.ts.map +1 -0
- package/dist/core/plugin/params/reply.js +49 -0
- package/dist/core/plugin/params/reply.js.map +1 -0
- package/dist/core/plugin/params/type-level.d.ts +23 -0
- package/dist/core/plugin/params/type-level.d.ts.map +1 -0
- package/dist/core/plugin/params/type-level.js +2 -0
- package/dist/core/plugin/params/type-level.js.map +1 -0
- package/dist/core/plugin/params/types.d.ts +28 -0
- package/dist/core/plugin/params/types.d.ts.map +1 -0
- package/dist/core/plugin/params/types.js +12 -0
- package/dist/core/plugin/params/types.js.map +1 -0
- package/dist/core/plugin/params/values.d.ts +6 -0
- package/dist/core/plugin/params/values.d.ts.map +1 -0
- package/dist/core/plugin/params/values.js +108 -0
- package/dist/core/plugin/params/values.js.map +1 -0
- package/dist/core/web/api.d.ts +2 -14
- package/dist/core/web/api.d.ts.map +1 -1
- package/dist/core/web/api.js +8 -500
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/routes/actions.d.ts +4 -0
- package/dist/core/web/routes/actions.d.ts.map +1 -0
- package/dist/core/web/routes/actions.js +33 -0
- package/dist/core/web/routes/actions.js.map +1 -0
- package/dist/core/web/routes/config.d.ts +5 -0
- package/dist/core/web/routes/config.d.ts.map +1 -0
- package/dist/core/web/routes/config.js +30 -0
- package/dist/core/web/routes/config.js.map +1 -0
- package/dist/core/web/routes/kv.d.ts +6 -0
- package/dist/core/web/routes/kv.d.ts.map +1 -0
- package/dist/core/web/routes/kv.js +80 -0
- package/dist/core/web/routes/kv.js.map +1 -0
- package/dist/core/web/routes/plugins.d.ts +5 -0
- package/dist/core/web/routes/plugins.d.ts.map +1 -0
- package/dist/core/web/routes/plugins.js +98 -0
- package/dist/core/web/routes/plugins.js.map +1 -0
- package/dist/core/web/routes/sessions.d.ts +8 -0
- package/dist/core/web/routes/sessions.d.ts.map +1 -0
- package/dist/core/web/routes/sessions.js +130 -0
- package/dist/core/web/routes/sessions.js.map +1 -0
- package/dist/core/web/routes/shared.d.ts +18 -0
- package/dist/core/web/routes/shared.d.ts.map +1 -0
- package/dist/core/web/routes/shared.js +19 -0
- package/dist/core/web/routes/shared.js.map +1 -0
- package/dist/core/web/routes/status.d.ts +6 -0
- package/dist/core/web/routes/status.d.ts.map +1 -0
- package/dist/core/web/routes/status.js +132 -0
- package/dist/core/web/routes/status.js.map +1 -0
- package/dist/core/web/server/auth.d.ts +17 -0
- package/dist/core/web/server/auth.d.ts.map +1 -0
- package/dist/core/web/server/auth.js +68 -0
- package/dist/core/web/server/auth.js.map +1 -0
- package/dist/core/web/server/csp.d.ts +11 -0
- package/dist/core/web/server/csp.d.ts.map +1 -0
- package/dist/core/web/server/csp.js +31 -0
- package/dist/core/web/server/csp.js.map +1 -0
- package/dist/core/web/server/request.d.ts +6 -0
- package/dist/core/web/server/request.d.ts.map +1 -0
- package/dist/core/web/server/request.js +33 -0
- package/dist/core/web/server/request.js.map +1 -0
- package/dist/core/web/server/security.d.ts +4 -0
- package/dist/core/web/server/security.d.ts.map +1 -0
- package/dist/core/web/server/security.js +12 -0
- package/dist/core/web/server/security.js.map +1 -0
- package/dist/core/web/server/sse.d.ts +18 -0
- package/dist/core/web/server/sse.d.ts.map +1 -0
- package/dist/core/web/server/sse.js +49 -0
- package/dist/core/web/server/sse.js.map +1 -0
- package/dist/core/web/server/static.d.ts +16 -0
- package/dist/core/web/server/static.d.ts.map +1 -0
- package/dist/core/web/server/static.js +80 -0
- package/dist/core/web/server/static.js.map +1 -0
- package/dist/core/web/server.d.ts +0 -7
- package/dist/core/web/server.d.ts.map +1 -1
- package/dist/core/web/server.js +41 -263
- package/dist/core/web/server.js.map +1 -1
- package/docs/architecture.md +28 -11
- package/docs/configuration.md +1 -1
- package/docs/migration.md +2 -2
- package/docs/plugin-api.md +3 -1
- package/docs/plugin-development.md +1 -1
- package/package.json +19 -17
- package/src/app/cli.ts +47 -0
- package/src/app/runtime.ts +522 -0
- package/src/app.ts +87 -395
- package/src/core/dispatch/command.ts +7 -1
- package/src/core/dispatch/router.ts +2 -1
- package/src/core/plugin/command-params.ts +8 -713
- package/src/core/plugin/install/paths.ts +72 -0
- package/src/core/plugin/install/state.ts +196 -0
- package/src/core/plugin/install/validate-script.ts +154 -0
- package/src/core/plugin/install-dir.ts +19 -427
- package/src/core/plugin/installer/checks.ts +137 -0
- package/src/core/plugin/installer/support.ts +120 -0
- package/src/core/plugin/installer/warnings.ts +52 -0
- package/src/core/plugin/installer.ts +52 -301
- package/src/core/plugin/params/compile.ts +41 -0
- package/src/core/plugin/params/declaration.ts +286 -0
- package/src/core/plugin/params/input.ts +254 -0
- package/src/core/plugin/params/reply.ts +59 -0
- package/src/core/plugin/params/type-level.ts +71 -0
- package/src/core/plugin/params/types.ts +49 -0
- package/src/core/plugin/params/values.ts +146 -0
- package/src/core/web/api.ts +33 -759
- package/src/core/web/routes/actions.ts +43 -0
- package/src/core/web/routes/config.ts +42 -0
- package/src/core/web/routes/kv.ts +122 -0
- package/src/core/web/routes/plugins.ts +151 -0
- package/src/core/web/routes/sessions.ts +188 -0
- package/src/core/web/routes/shared.ts +42 -0
- package/src/core/web/routes/status.ts +191 -0
- package/src/core/web/server/auth.ts +126 -0
- package/src/core/web/server/csp.ts +33 -0
- package/src/core/web/server/request.ts +38 -0
- package/src/core/web/server/security.ts +14 -0
- package/src/core/web/server/sse.ts +74 -0
- package/src/core/web/server/static.ts +112 -0
- package/src/core/web/server.ts +41 -383
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { InstalledPackageRecord, InstallState } from '../install-dir.js'
|
|
2
|
+
|
|
3
|
+
export function configuredWarnings(
|
|
4
|
+
pluginIds: readonly string[],
|
|
5
|
+
configured: ReadonlySet<string>
|
|
6
|
+
): string[] {
|
|
7
|
+
return pluginIds
|
|
8
|
+
.filter((id) => configured.has(id))
|
|
9
|
+
.map(
|
|
10
|
+
(id) =>
|
|
11
|
+
`配置里的 plugins.${id} 仍然存在,重启前请删除,否则会因未知插件 ID 启动失败`
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function removalWarnings(
|
|
16
|
+
packageName: string,
|
|
17
|
+
record: InstalledPackageRecord | undefined,
|
|
18
|
+
state: InstallState,
|
|
19
|
+
configured: ReadonlySet<string>
|
|
20
|
+
): string[] {
|
|
21
|
+
return [
|
|
22
|
+
...configuredWarnings(record?.pluginIds ?? [], configured),
|
|
23
|
+
...dependentWarnings(packageName, record, state),
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function dependentWarnings(
|
|
28
|
+
packageName: string,
|
|
29
|
+
record: InstalledPackageRecord | undefined,
|
|
30
|
+
state: InstallState
|
|
31
|
+
): string[] {
|
|
32
|
+
const removedIds = new Set(record?.pluginIds ?? [])
|
|
33
|
+
const warnings: string[] = []
|
|
34
|
+
|
|
35
|
+
for (const [name, other] of Object.entries(state.packages)) {
|
|
36
|
+
if (name === packageName) continue
|
|
37
|
+
|
|
38
|
+
for (const [pluginId, dependencies] of Object.entries(
|
|
39
|
+
other.dependencies
|
|
40
|
+
)) {
|
|
41
|
+
for (const dependency of dependencies) {
|
|
42
|
+
if (!removedIds.has(dependency)) continue
|
|
43
|
+
|
|
44
|
+
warnings.push(
|
|
45
|
+
`插件包 ${name} 的插件 ${pluginId} 依赖已卸载的插件 ${dependency}`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return warnings
|
|
52
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
1
|
import { cp, mkdir, rename, rm, writeFile } from 'node:fs/promises'
|
|
3
2
|
import { join } from 'node:path'
|
|
4
3
|
import type { InstallerConfig } from '../config/schema.js'
|
|
@@ -6,7 +5,6 @@ import type { Logger } from '../logging/logger.js'
|
|
|
6
5
|
import {
|
|
7
6
|
ensureCoreLink,
|
|
8
7
|
ensureInstallRoot,
|
|
9
|
-
packageDirectoryName,
|
|
10
8
|
pathExists,
|
|
11
9
|
readInstallState,
|
|
12
10
|
readJsonFile,
|
|
@@ -19,15 +17,32 @@ import {
|
|
|
19
17
|
import {
|
|
20
18
|
validateInstalledPackage,
|
|
21
19
|
type PackageValidator,
|
|
22
|
-
type ValidatedPlugin,
|
|
23
20
|
} from './install-validate.js'
|
|
24
|
-
import { NpmCliRunner, type NpmRunner } from './npm-runner.js'
|
|
25
21
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
assertTargetAvailable,
|
|
23
|
+
checkPlugins,
|
|
24
|
+
resolveTarget,
|
|
25
|
+
} from './installer/checks.js'
|
|
26
|
+
import {
|
|
27
|
+
asInstallerError,
|
|
28
|
+
assertInstallerEnabled,
|
|
29
|
+
createTaskSerializer,
|
|
30
|
+
installDirectoryName,
|
|
31
|
+
InstallerError,
|
|
32
|
+
messageOf,
|
|
33
|
+
NPM_FLAGS,
|
|
34
|
+
parseInstallerSpec,
|
|
35
|
+
STAGING_MANIFEST,
|
|
36
|
+
temporaryPath,
|
|
37
|
+
type InstallerOptions,
|
|
38
|
+
} from './installer/support.js'
|
|
39
|
+
import {
|
|
40
|
+
configuredWarnings,
|
|
41
|
+
dependentWarnings,
|
|
42
|
+
removalWarnings,
|
|
43
|
+
} from './installer/warnings.js'
|
|
44
|
+
import { NpmCliRunner, type NpmRunner } from './npm-runner.js'
|
|
45
|
+
import type { PackageSpec } from './spec.js'
|
|
31
46
|
import type {
|
|
32
47
|
InstalledPackageInfo,
|
|
33
48
|
PluginInstallResult,
|
|
@@ -36,56 +51,8 @@ import type {
|
|
|
36
51
|
PluginUpdateResult,
|
|
37
52
|
} from './types.js'
|
|
38
53
|
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
super(message, options)
|
|
42
|
-
this.name = 'InstallerError'
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface InstallerOptions {
|
|
47
|
-
/** Plugins root. Installed packages are placed in subdirectories here. */
|
|
48
|
-
dir: string
|
|
49
|
-
config: InstallerConfig
|
|
50
|
-
logger: Logger
|
|
51
|
-
runner?: NpmRunner | undefined
|
|
52
|
-
validate?: PackageValidator | undefined
|
|
53
|
-
reservedIds?: (() => ReadonlySet<string>) | undefined
|
|
54
|
-
configuredPluginIds?: (() => ReadonlySet<string>) | undefined
|
|
55
|
-
now?: (() => Date) | undefined
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i
|
|
59
|
-
const NPM_FLAGS = ['--no-audit', '--no-fund', '--loglevel=error']
|
|
60
|
-
const STAGING_MANIFEST = {
|
|
61
|
-
name: '@jsbot-tellus/plugin-staging',
|
|
62
|
-
private: true,
|
|
63
|
-
version: '0.0.0',
|
|
64
|
-
type: 'module',
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function messageOf(error: unknown): string {
|
|
68
|
-
return error instanceof Error ? error.message : String(error)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function asInstallerError(error: unknown): InstallerError {
|
|
72
|
-
if (error instanceof InstallerError) return error
|
|
73
|
-
|
|
74
|
-
if (error instanceof PackageSpecError) {
|
|
75
|
-
return new InstallerError(error.message, { cause: error })
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return new InstallerError(messageOf(error), { cause: error })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function installDirectoryName(
|
|
82
|
-
packageName: string,
|
|
83
|
-
pluginIds: readonly string[]
|
|
84
|
-
): string {
|
|
85
|
-
const only = pluginIds.length === 1 ? pluginIds[0] : undefined
|
|
86
|
-
|
|
87
|
-
return only ?? packageDirectoryName(packageName)
|
|
88
|
-
}
|
|
54
|
+
export { createDisabledInstaller, InstallerError } from './installer/support.js'
|
|
55
|
+
export type { InstallerOptions } from './installer/support.js'
|
|
89
56
|
|
|
90
57
|
export class NpmPluginInstaller implements PluginInstaller {
|
|
91
58
|
readonly enabled: boolean
|
|
@@ -98,7 +65,7 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
98
65
|
private readonly reservedIds: () => ReadonlySet<string>
|
|
99
66
|
private readonly configuredPluginIds: () => ReadonlySet<string>
|
|
100
67
|
private readonly now: () => Date
|
|
101
|
-
private
|
|
68
|
+
private readonly serialize = createTaskSerializer()
|
|
102
69
|
|
|
103
70
|
constructor(options: InstallerOptions) {
|
|
104
71
|
this.dir = options.dir
|
|
@@ -147,8 +114,8 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
147
114
|
signal?: AbortSignal | undefined
|
|
148
115
|
): Promise<PluginInstallResult> {
|
|
149
116
|
try {
|
|
150
|
-
this.
|
|
151
|
-
const parsed = this.
|
|
117
|
+
assertInstallerEnabled(this.enabled)
|
|
118
|
+
const parsed = parseInstallerSpec(spec, this.config.allowedPackages)
|
|
152
119
|
|
|
153
120
|
return this.serialize(() => this.installSpec(parsed, signal))
|
|
154
121
|
} catch (error) {
|
|
@@ -162,22 +129,27 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
162
129
|
options?: { force?: boolean | undefined } | undefined
|
|
163
130
|
): Promise<PluginRemoveResult> {
|
|
164
131
|
try {
|
|
165
|
-
this.
|
|
132
|
+
assertInstallerEnabled(this.enabled)
|
|
166
133
|
} catch (error) {
|
|
167
134
|
return Promise.reject(asInstallerError(error))
|
|
168
135
|
}
|
|
169
136
|
|
|
170
137
|
return this.serialize(async () => {
|
|
171
138
|
const state = await readInstallState(this.dir)
|
|
172
|
-
const name =
|
|
139
|
+
const name = resolveTarget(target, state)
|
|
173
140
|
const record = state.packages[name]
|
|
174
141
|
|
|
175
142
|
if (!record) {
|
|
176
143
|
throw new InstallerError(`未找到已安装的插件包: ${name}`)
|
|
177
144
|
}
|
|
178
145
|
|
|
179
|
-
const warnings =
|
|
180
|
-
|
|
146
|
+
const warnings = removalWarnings(
|
|
147
|
+
name,
|
|
148
|
+
record,
|
|
149
|
+
state,
|
|
150
|
+
this.configuredPluginIds()
|
|
151
|
+
)
|
|
152
|
+
const dependents = dependentWarnings(name, record, state)
|
|
181
153
|
|
|
182
154
|
if (dependents.length > 0 && options?.force !== true) {
|
|
183
155
|
throw new InstallerError(
|
|
@@ -190,7 +162,7 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
190
162
|
}
|
|
191
163
|
|
|
192
164
|
const destination = join(this.dir, record.dir)
|
|
193
|
-
const backup = this.
|
|
165
|
+
const backup = temporaryPath(this.dir, 'backup')
|
|
194
166
|
const existed = await pathExists(destination)
|
|
195
167
|
|
|
196
168
|
try {
|
|
@@ -243,7 +215,7 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
243
215
|
signal?: AbortSignal | undefined
|
|
244
216
|
): Promise<PluginUpdateResult> {
|
|
245
217
|
try {
|
|
246
|
-
this.
|
|
218
|
+
assertInstallerEnabled(this.enabled)
|
|
247
219
|
} catch (error) {
|
|
248
220
|
return Promise.reject(asInstallerError(error))
|
|
249
221
|
}
|
|
@@ -253,7 +225,7 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
253
225
|
const names =
|
|
254
226
|
target === undefined || target.trim().length === 0
|
|
255
227
|
? Object.keys(state.packages).sort()
|
|
256
|
-
: [
|
|
228
|
+
: [resolveTarget(target, state)]
|
|
257
229
|
|
|
258
230
|
const updated: PluginInstallResult[] = []
|
|
259
231
|
const failed: Array<{ packageName: string; message: string }> = []
|
|
@@ -265,7 +237,10 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
265
237
|
}
|
|
266
238
|
|
|
267
239
|
try {
|
|
268
|
-
const parsed =
|
|
240
|
+
const parsed = parseInstallerSpec(
|
|
241
|
+
name,
|
|
242
|
+
this.config.allowedPackages
|
|
243
|
+
)
|
|
269
244
|
|
|
270
245
|
updated.push(
|
|
271
246
|
await this.installSpec(
|
|
@@ -297,8 +272,8 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
297
272
|
|
|
298
273
|
const state = await readInstallState(this.dir)
|
|
299
274
|
const previous = state.packages[spec.name]
|
|
300
|
-
const staging = this.
|
|
301
|
-
const backup = this.
|
|
275
|
+
const staging = temporaryPath(this.dir, 'staging')
|
|
276
|
+
const backup = temporaryPath(this.dir, 'backup')
|
|
302
277
|
let target: string | null = null
|
|
303
278
|
let movedTarget = false
|
|
304
279
|
let movedBackup = false
|
|
@@ -360,7 +335,7 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
360
335
|
script: join(this.dir, '.jsbot', VALIDATE_SCRIPT_FILE),
|
|
361
336
|
})
|
|
362
337
|
|
|
363
|
-
|
|
338
|
+
checkPlugins(spec.name, validated, state, () => this.reservedIds())
|
|
364
339
|
|
|
365
340
|
const version = await readPackageVersion(flat)
|
|
366
341
|
|
|
@@ -374,7 +349,7 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
374
349
|
|
|
375
350
|
target = join(this.dir, directory)
|
|
376
351
|
|
|
377
|
-
await
|
|
352
|
+
await assertTargetAvailable(
|
|
378
353
|
state,
|
|
379
354
|
spec.name,
|
|
380
355
|
directory,
|
|
@@ -382,10 +357,11 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
382
357
|
previous
|
|
383
358
|
)
|
|
384
359
|
|
|
385
|
-
const warnings =
|
|
360
|
+
const warnings = configuredWarnings(
|
|
386
361
|
(previous?.pluginIds ?? []).filter(
|
|
387
362
|
(id) => !pluginIds.includes(id)
|
|
388
|
-
)
|
|
363
|
+
),
|
|
364
|
+
this.configuredPluginIds()
|
|
389
365
|
)
|
|
390
366
|
|
|
391
367
|
if (await pathExists(target)) {
|
|
@@ -467,229 +443,4 @@ export class NpmPluginInstaller implements PluginInstaller {
|
|
|
467
443
|
)
|
|
468
444
|
}
|
|
469
445
|
}
|
|
470
|
-
|
|
471
|
-
private async assertTargetAvailable(
|
|
472
|
-
state: InstallState,
|
|
473
|
-
packageName: string,
|
|
474
|
-
directory: string,
|
|
475
|
-
target: string,
|
|
476
|
-
previous: InstalledPackageRecord | undefined
|
|
477
|
-
): Promise<void> {
|
|
478
|
-
for (const [name, record] of Object.entries(state.packages)) {
|
|
479
|
-
if (name === packageName) continue
|
|
480
|
-
if (record.dir !== directory) continue
|
|
481
|
-
|
|
482
|
-
throw new InstallerError(
|
|
483
|
-
`插件目录已被其它已安装包使用: ${record.dir}(${name})`
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (previous !== undefined) return
|
|
488
|
-
|
|
489
|
-
if (await pathExists(target)) {
|
|
490
|
-
throw new InstallerError(
|
|
491
|
-
`安装目录已存在且不是由安装器管理: ${target}。请先移除该本地插件或为其改名。`
|
|
492
|
-
)
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
private checkPlugins(
|
|
497
|
-
packageName: string,
|
|
498
|
-
plugins: ValidatedPlugin[],
|
|
499
|
-
state: InstallState
|
|
500
|
-
): void {
|
|
501
|
-
const ownIds = new Set(state.packages[packageName]?.pluginIds ?? [])
|
|
502
|
-
const ids = new Set<string>()
|
|
503
|
-
const taken = new Set<string>()
|
|
504
|
-
|
|
505
|
-
for (const id of this.reservedIds()) {
|
|
506
|
-
if (!ownIds.has(id)) taken.add(id)
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
for (const [name, record] of Object.entries(state.packages)) {
|
|
510
|
-
if (name === packageName) continue
|
|
511
|
-
|
|
512
|
-
for (const id of record.pluginIds) taken.add(id)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
for (const plugin of plugins) {
|
|
516
|
-
if (!ID_PATTERN.test(plugin.id)) {
|
|
517
|
-
throw new InstallerError(`插件 id 无效: ${plugin.id}`)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (ids.has(plugin.id)) {
|
|
521
|
-
throw new InstallerError(`插件 id 重复: ${plugin.id}`)
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (taken.has(plugin.id)) {
|
|
525
|
-
throw new InstallerError(
|
|
526
|
-
`插件 id 已被占用: ${plugin.id}(内置插件或其他已安装包)`
|
|
527
|
-
)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
ids.add(plugin.id)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const graph = new Map<string, string[]>()
|
|
534
|
-
|
|
535
|
-
for (const [name, record] of Object.entries(state.packages)) {
|
|
536
|
-
if (name === packageName) continue
|
|
537
|
-
|
|
538
|
-
for (const id of record.pluginIds) {
|
|
539
|
-
graph.set(id, record.dependencies[id] ?? [])
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
for (const plugin of plugins) {
|
|
544
|
-
graph.set(plugin.id, plugin.dependencies)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const reserved = this.reservedIds()
|
|
548
|
-
const available = new Set(graph.keys())
|
|
549
|
-
|
|
550
|
-
for (const id of reserved) {
|
|
551
|
-
if (!ownIds.has(id)) available.add(id)
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
for (const [id, dependencies] of graph) {
|
|
555
|
-
for (const dependency of dependencies) {
|
|
556
|
-
if (!available.has(dependency)) {
|
|
557
|
-
throw new InstallerError(
|
|
558
|
-
`插件 ${id} 依赖未加载的插件: ${dependency}`
|
|
559
|
-
)
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const visiting = new Set<string>()
|
|
565
|
-
const visited = new Set<string>()
|
|
566
|
-
|
|
567
|
-
const visit = (id: string): void => {
|
|
568
|
-
if (visited.has(id)) return
|
|
569
|
-
|
|
570
|
-
if (visiting.has(id)) {
|
|
571
|
-
throw new InstallerError(`插件依赖存在循环: ${id}`)
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
visiting.add(id)
|
|
575
|
-
|
|
576
|
-
for (const dependency of graph.get(id) ?? []) {
|
|
577
|
-
if (graph.has(dependency)) visit(dependency)
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
visiting.delete(id)
|
|
581
|
-
visited.add(id)
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
for (const id of graph.keys()) visit(id)
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
private resolveTarget(target: string, state: InstallState): string {
|
|
588
|
-
const value = target.trim()
|
|
589
|
-
|
|
590
|
-
if (value.length === 0) {
|
|
591
|
-
throw new InstallerError('请提供包名或插件 id')
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (state.packages[value] !== undefined) return value
|
|
595
|
-
|
|
596
|
-
for (const [name, record] of Object.entries(state.packages)) {
|
|
597
|
-
if (record.pluginIds.includes(value)) return name
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
throw new InstallerError(`未找到已安装的插件包: ${value}`)
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
private removalWarnings(
|
|
604
|
-
packageName: string,
|
|
605
|
-
record: InstalledPackageRecord | undefined,
|
|
606
|
-
state: InstallState
|
|
607
|
-
): string[] {
|
|
608
|
-
return [
|
|
609
|
-
...this.configuredWarnings(record?.pluginIds ?? []),
|
|
610
|
-
...this.dependentWarnings(packageName, record, state),
|
|
611
|
-
]
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
private configuredWarnings(pluginIds: readonly string[]): string[] {
|
|
615
|
-
const configured = this.configuredPluginIds()
|
|
616
|
-
|
|
617
|
-
return pluginIds
|
|
618
|
-
.filter((id) => configured.has(id))
|
|
619
|
-
.map(
|
|
620
|
-
(id) =>
|
|
621
|
-
`配置里的 plugins.${id} 仍然存在,重启前请删除,否则会因未知插件 ID 启动失败`
|
|
622
|
-
)
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
private dependentWarnings(
|
|
626
|
-
packageName: string,
|
|
627
|
-
record: InstalledPackageRecord | undefined,
|
|
628
|
-
state: InstallState
|
|
629
|
-
): string[] {
|
|
630
|
-
const removedIds = new Set(record?.pluginIds ?? [])
|
|
631
|
-
const warnings: string[] = []
|
|
632
|
-
|
|
633
|
-
for (const [name, other] of Object.entries(state.packages)) {
|
|
634
|
-
if (name === packageName) continue
|
|
635
|
-
|
|
636
|
-
for (const [pluginId, dependencies] of Object.entries(
|
|
637
|
-
other.dependencies
|
|
638
|
-
)) {
|
|
639
|
-
for (const dependency of dependencies) {
|
|
640
|
-
if (!removedIds.has(dependency)) continue
|
|
641
|
-
|
|
642
|
-
warnings.push(
|
|
643
|
-
`插件包 ${name} 的插件 ${pluginId} 依赖已卸载的插件 ${dependency}`
|
|
644
|
-
)
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return warnings
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
private parse(spec: string): PackageSpec {
|
|
653
|
-
const parsed = parsePackageSpec(spec)
|
|
654
|
-
|
|
655
|
-
if (!isPackageAllowed(parsed.name, this.config.allowedPackages)) {
|
|
656
|
-
throw new InstallerError(`包不在允许列表: ${parsed.name}`)
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
return parsed
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
private temporaryPath(kind: 'staging' | 'backup'): string {
|
|
663
|
-
return join(this.dir, `.${kind}-${randomUUID()}`)
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
private assertEnabled(): void {
|
|
667
|
-
if (!this.enabled) {
|
|
668
|
-
throw new InstallerError(
|
|
669
|
-
'插件安装功能未启用(installer.enabled=false)'
|
|
670
|
-
)
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
private serialize<T>(task: () => Promise<T>): Promise<T> {
|
|
675
|
-
const run = this.tail.then(task, task)
|
|
676
|
-
this.tail = run.then(
|
|
677
|
-
() => undefined,
|
|
678
|
-
() => undefined
|
|
679
|
-
)
|
|
680
|
-
return run
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
export function createDisabledInstaller(): PluginInstaller {
|
|
685
|
-
const fail = (): Promise<never> =>
|
|
686
|
-
Promise.reject(new InstallerError('插件安装功能未启用'))
|
|
687
|
-
|
|
688
|
-
return {
|
|
689
|
-
enabled: false,
|
|
690
|
-
list: () => Promise.resolve([]),
|
|
691
|
-
install: fail,
|
|
692
|
-
remove: fail,
|
|
693
|
-
update: fail,
|
|
694
|
-
}
|
|
695
446
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { OneBotMessageEvent } from '@snowluma/sdk'
|
|
2
|
+
import {
|
|
3
|
+
declarationError,
|
|
4
|
+
type CompiledCommandParams,
|
|
5
|
+
type ParsedCommandParams,
|
|
6
|
+
} from './types.js'
|
|
7
|
+
import {
|
|
8
|
+
splitDeclaration,
|
|
9
|
+
parseToken,
|
|
10
|
+
usageOf,
|
|
11
|
+
validateSpecs,
|
|
12
|
+
} from './declaration.js'
|
|
13
|
+
import { parseInput } from './input.js'
|
|
14
|
+
import { quoteIdOf } from './reply.js'
|
|
15
|
+
|
|
16
|
+
export function compileCommandParams(
|
|
17
|
+
declaration: string
|
|
18
|
+
): CompiledCommandParams {
|
|
19
|
+
const trimmed = declaration.trim()
|
|
20
|
+
|
|
21
|
+
if (!trimmed) {
|
|
22
|
+
throw declarationError('参数声明不能为空')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const specs = splitDeclaration(trimmed).map(parseToken)
|
|
26
|
+
|
|
27
|
+
if (specs.length === 0) {
|
|
28
|
+
throw declarationError('参数声明不能为空')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
validateSpecs(specs)
|
|
32
|
+
|
|
33
|
+
const usage = usageOf(specs)
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
usage,
|
|
37
|
+
parse(input: string, event?: OneBotMessageEvent): ParsedCommandParams {
|
|
38
|
+
return parseInput(specs, input, usage, quoteIdOf(event))
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|