@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
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { mkdir, realpath, rm, stat, symlink, writeFile } from 'node:fs/promises'
3
+ import { dirname, join } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { VALIDATE_SCRIPT, VALIDATE_SCRIPT_FILE } from './validate-script.js'
6
+
7
+ export const CORE_PACKAGE_NAME = '@jsbot-tellus/core'
8
+
9
+ export function packageDirectoryName(name: string): string {
10
+ return name.startsWith('@') ? name.replace('/', '__') : name
11
+ }
12
+
13
+ export function corePackageRoot(): string {
14
+ let current = dirname(fileURLToPath(import.meta.url))
15
+
16
+ for (;;) {
17
+ try {
18
+ const manifest = JSON.parse(
19
+ readFileSync(join(current, 'package.json'), 'utf8')
20
+ ) as Record<string, unknown>
21
+
22
+ if (manifest['name'] === CORE_PACKAGE_NAME) return current
23
+ } catch {
24
+ // keep walking up
25
+ }
26
+
27
+ const parent = dirname(current)
28
+
29
+ if (parent === current) {
30
+ throw new Error(`cannot locate ${CORE_PACKAGE_NAME} package root`)
31
+ }
32
+
33
+ current = parent
34
+ }
35
+ }
36
+
37
+ export async function ensureCoreLink(dir: string): Promise<void> {
38
+ const target = corePackageRoot()
39
+ const link = join(dir, 'node_modules', '@jsbot-tellus', 'core')
40
+
41
+ await mkdir(dirname(link), { recursive: true })
42
+
43
+ const existing = await realpath(link).catch(() => null)
44
+
45
+ if (existing !== null) {
46
+ const resolvedTarget = await realpath(target).catch(() => target)
47
+
48
+ if (existing === resolvedTarget) return
49
+
50
+ await rm(link, { recursive: true, force: true })
51
+ }
52
+
53
+ await symlink(
54
+ target,
55
+ link,
56
+ process.platform === 'win32' ? 'junction' : 'dir'
57
+ )
58
+ }
59
+
60
+ export async function pathExists(path: string): Promise<boolean> {
61
+ return (await stat(path).catch(() => null)) !== null
62
+ }
63
+
64
+ export async function ensureInstallRoot(root: string): Promise<void> {
65
+ await mkdir(root, { recursive: true })
66
+ await mkdir(join(root, '.jsbot'), { recursive: true })
67
+ await writeFile(
68
+ join(root, '.jsbot', VALIDATE_SCRIPT_FILE),
69
+ VALIDATE_SCRIPT,
70
+ 'utf8'
71
+ )
72
+ }
@@ -0,0 +1,196 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { readFile, rename, rm, writeFile } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+
5
+ export const INSTALL_STATE_FILE = '.jsbot-installed.json'
6
+
7
+ export interface InstalledPackageRecord {
8
+ spec: string
9
+ version: string
10
+ pluginIds: string[]
11
+ dependencies: Record<string, string[]>
12
+ installedAt: string
13
+ /** Directory name under the plugins root. */
14
+ dir: string
15
+ }
16
+
17
+ export interface InstallState {
18
+ version: 2
19
+ packages: Record<string, InstalledPackageRecord>
20
+ }
21
+
22
+ export class InstallStateError extends Error {
23
+ constructor(message: string, options?: ErrorOptions) {
24
+ super(message, options)
25
+ this.name = 'InstallStateError'
26
+ }
27
+ }
28
+
29
+ export async function readJsonFile(path: string): Promise<unknown> {
30
+ try {
31
+ return JSON.parse(await readFile(path, 'utf8'))
32
+ } catch (error) {
33
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined
34
+
35
+ throw error
36
+ }
37
+ }
38
+
39
+ export async function readPackageVersion(
40
+ directory: string
41
+ ): Promise<string | undefined> {
42
+ const manifest = await readJsonFile(join(directory, 'package.json')).catch(
43
+ () => undefined
44
+ )
45
+
46
+ if (typeof manifest !== 'object' || manifest === null) return undefined
47
+
48
+ const version = (manifest as Record<string, unknown>)['version']
49
+
50
+ return typeof version === 'string' ? version : undefined
51
+ }
52
+
53
+ export function isSafeDirectoryName(value: string): boolean {
54
+ return (
55
+ value.length > 0 &&
56
+ value !== '.' &&
57
+ value !== '..' &&
58
+ !value.includes('/') &&
59
+ !value.includes('\\')
60
+ )
61
+ }
62
+
63
+ export async function readInstallState(dir: string): Promise<InstallState> {
64
+ const path = join(dir, INSTALL_STATE_FILE)
65
+ let value: unknown
66
+
67
+ try {
68
+ value = await readJsonFile(path)
69
+ } catch (error) {
70
+ throw new InstallStateError(`安装状态文件无法解析: ${path}`, {
71
+ cause: error,
72
+ })
73
+ }
74
+
75
+ if (value === undefined) {
76
+ return { version: 2, packages: {} }
77
+ }
78
+
79
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
80
+ throw new InstallStateError(`安装状态文件格式无效: ${path}`)
81
+ }
82
+
83
+ if ((value as Record<string, unknown>)['version'] !== 2) {
84
+ throw new InstallStateError(`不支持的安装状态版本,请检查 ${path}`)
85
+ }
86
+
87
+ const packages = (value as Record<string, unknown>)['packages']
88
+
89
+ if (
90
+ typeof packages !== 'object' ||
91
+ packages === null ||
92
+ Array.isArray(packages)
93
+ ) {
94
+ throw new InstallStateError(`安装状态 packages 字段无效: ${path}`)
95
+ }
96
+
97
+ const result: Record<string, InstalledPackageRecord> = {}
98
+ const directories = new Set<string>()
99
+
100
+ for (const [name, rawRecord] of Object.entries(packages)) {
101
+ if (
102
+ typeof rawRecord !== 'object' ||
103
+ rawRecord === null ||
104
+ Array.isArray(rawRecord)
105
+ ) {
106
+ throw new InstallStateError(`安装状态记录无效: ${name}`)
107
+ }
108
+
109
+ const record = rawRecord as Record<string, unknown>
110
+ const pluginIds = record['pluginIds']
111
+ const dependencies = record['dependencies']
112
+ const directory = record['dir']
113
+
114
+ if (
115
+ typeof record['spec'] !== 'string' ||
116
+ typeof record['version'] !== 'string' ||
117
+ typeof record['installedAt'] !== 'string' ||
118
+ typeof directory !== 'string' ||
119
+ !isSafeDirectoryName(directory) ||
120
+ !Array.isArray(pluginIds) ||
121
+ pluginIds.some((item) => typeof item !== 'string')
122
+ ) {
123
+ throw new InstallStateError(`安装状态记录不完整: ${name}`)
124
+ }
125
+
126
+ if (directories.has(directory)) {
127
+ throw new InstallStateError(
128
+ `安装状态存在重复的插件目录: ${directory}`
129
+ )
130
+ }
131
+
132
+ directories.add(directory)
133
+
134
+ if (
135
+ typeof dependencies !== 'object' ||
136
+ dependencies === null ||
137
+ Array.isArray(dependencies)
138
+ ) {
139
+ throw new InstallStateError(`安装状态依赖字段无效: ${name}`)
140
+ }
141
+
142
+ const parsedDependencies: Record<string, string[]> = {}
143
+
144
+ for (const [id, list] of Object.entries(dependencies)) {
145
+ if (
146
+ !Array.isArray(list) ||
147
+ list.some((item) => typeof item !== 'string')
148
+ ) {
149
+ throw new InstallStateError(
150
+ `安装状态依赖记录无效: ${name}.${id}`
151
+ )
152
+ }
153
+
154
+ parsedDependencies[id] = [...list]
155
+ }
156
+
157
+ result[name] = {
158
+ spec: record['spec'],
159
+ version: record['version'],
160
+ pluginIds: [...pluginIds],
161
+ dependencies: parsedDependencies,
162
+ installedAt: record['installedAt'],
163
+ dir: directory,
164
+ }
165
+ }
166
+
167
+ return { version: 2, packages: result }
168
+ }
169
+
170
+ export async function writeInstallState(
171
+ dir: string,
172
+ state: InstallState
173
+ ): Promise<void> {
174
+ const sorted: InstallState = { version: 2, packages: {} }
175
+
176
+ for (const name of Object.keys(state.packages).sort()) {
177
+ const record = state.packages[name]
178
+ if (!record) continue
179
+ sorted.packages[name] = record
180
+ }
181
+
182
+ const path = join(dir, INSTALL_STATE_FILE)
183
+ const temporary = `${path}.${randomUUID()}.tmp`
184
+
185
+ try {
186
+ await writeFile(
187
+ temporary,
188
+ `${JSON.stringify(sorted, null, 4)}\n`,
189
+ 'utf8'
190
+ )
191
+ await rename(temporary, path)
192
+ } catch (error) {
193
+ await rm(temporary, { force: true }).catch(() => undefined)
194
+ throw error
195
+ }
196
+ }
@@ -0,0 +1,154 @@
1
+ export const VALIDATE_SCRIPT_FILE = 'validate.mjs'
2
+
3
+ export const VALIDATE_SCRIPT = `import { readdir } from 'node:fs/promises'
4
+ import { extname, join } from 'node:path'
5
+ import { pathToFileURL } from 'node:url'
6
+
7
+ const directory = process.argv[2]
8
+
9
+ if (!directory) {
10
+ console.error('missing plugin directory')
11
+ process.exit(2)
12
+ }
13
+
14
+ const extensions = new Set(['.js', '.mjs', '.cjs'])
15
+
16
+ async function filesIn(directory) {
17
+ const entries = await readdir(directory, { withFileTypes: true })
18
+ entries.sort((a, b) => a.name.localeCompare(b.name))
19
+
20
+ const files = []
21
+
22
+ for (const entry of entries) {
23
+ if (entry.name.startsWith('.')) continue
24
+
25
+ const path = join(directory, entry.name)
26
+
27
+ if (entry.isDirectory()) {
28
+ if (entry.name === 'node_modules') continue
29
+ files.push(...(await filesIn(path)))
30
+ continue
31
+ }
32
+
33
+ if (
34
+ entry.isFile() &&
35
+ extensions.has(extname(entry.name)) &&
36
+ !/\\.d\\.(ts|mts|cts)$/.test(entry.name)
37
+ ) {
38
+ files.push(path)
39
+ }
40
+ }
41
+
42
+ return files
43
+ }
44
+
45
+ function isPlugin(value) {
46
+ if (typeof value !== 'object' || value === null) return false
47
+ if (!('manifest' in value) || !('setup' in value)) return false
48
+
49
+ const manifest = value.manifest
50
+
51
+ if (
52
+ typeof manifest !== 'object' ||
53
+ manifest === null ||
54
+ typeof manifest.id !== 'string' ||
55
+ typeof manifest.configSchema !== 'object' ||
56
+ manifest.configSchema === null ||
57
+ typeof manifest.configSchema.safeParse !== 'function' ||
58
+ typeof value.setup !== 'function'
59
+ ) {
60
+ return false
61
+ }
62
+
63
+ if (
64
+ manifest.dependencies !== undefined &&
65
+ (!Array.isArray(manifest.dependencies) ||
66
+ manifest.dependencies.some((item) => typeof item !== 'string'))
67
+ ) {
68
+ return false
69
+ }
70
+
71
+ return true
72
+ }
73
+
74
+ function collect(value, result) {
75
+ if (Array.isArray(value)) {
76
+ for (const item of value) collect(item, result)
77
+ return
78
+ }
79
+
80
+ if (isPlugin(value)) {
81
+ result.push(value)
82
+ return
83
+ }
84
+
85
+ if (
86
+ typeof value === 'object' &&
87
+ value !== null &&
88
+ 'plugins' in value &&
89
+ Array.isArray(value.plugins)
90
+ ) {
91
+ for (const item of value.plugins) collect(item, result)
92
+ }
93
+ }
94
+
95
+ const files = await filesIn(directory).catch((error) => {
96
+ console.error(error instanceof Error ? error.message : String(error))
97
+ process.exit(3)
98
+ })
99
+
100
+ if (files.length === 0) {
101
+ console.error('插件 dist 目录中没有可加载的文件')
102
+ process.exit(4)
103
+ }
104
+
105
+ const found = []
106
+ const invalid = []
107
+
108
+ for (const file of files) {
109
+ let imported
110
+
111
+ try {
112
+ imported = await import(pathToFileURL(file).href)
113
+ } catch (error) {
114
+ invalid.push(
115
+ \`failed to import \${file}: \${error instanceof Error ? error.message : String(error)}\`
116
+ )
117
+ continue
118
+ }
119
+
120
+ if (typeof imported !== 'object' || imported === null) continue
121
+
122
+ for (const candidate of [imported.default, imported.plugin, imported.plugins]) {
123
+ collect(candidate, found)
124
+ }
125
+ }
126
+
127
+ const unique = found.filter((plugin, index) => found.indexOf(plugin) === index)
128
+
129
+ if (unique.length === 0) {
130
+ console.error(
131
+ invalid[0] ??
132
+ 'package does not export a JsBot plugin (default / plugin / plugins)'
133
+ )
134
+ process.exit(5)
135
+ }
136
+
137
+ console.log(
138
+ '__JSBOT_PLUGIN__' +
139
+ JSON.stringify(
140
+ unique.map((plugin) => ({
141
+ id: plugin.manifest.id,
142
+ version:
143
+ typeof plugin.manifest.version === 'string'
144
+ ? plugin.manifest.version
145
+ : '0.0.0',
146
+ dependencies: Array.isArray(plugin.manifest.dependencies)
147
+ ? plugin.manifest.dependencies.filter(
148
+ (item) => typeof item === 'string'
149
+ )
150
+ : [],
151
+ }))
152
+ )
153
+ )
154
+ `