@thinkbun/plugin 1.0.5 → 1.0.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;AAoDA;;cAAa,cAAc;;;;;AAA3B;;cC4Ha,cAAc"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;AAoDA;;cAAa,cAAc;;;;;AAA3B;;cCoIa,cAAc"}
package/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@ import { LOG_COLORS, invariant } from "@thinkbun/core";
2
2
  import pc from "picocolors";
3
3
  import cluster from "node:cluster";
4
4
  import os from "node:os";
5
+ import { merge } from "lodash-es";
5
6
 
6
7
  //#region src/logger.ts
7
8
  /**
@@ -37,17 +38,19 @@ const createLogMethod = (level) => {
37
38
  * 日志插件,为应用添加统一的日志功能
38
39
  * 支持不同作用域的彩色日志输出
39
40
  */
40
- const LoggerPlugin = {
41
- name: "LoggerPlugin",
42
- enforce: "pre",
43
- setup(app) {
44
- app.logger = {
45
- info: createLogMethod("info"),
46
- error: createLogMethod("error"),
47
- debug: createLogMethod("debug"),
48
- warn: createLogMethod("warn")
49
- };
50
- }
41
+ const LoggerPlugin = () => {
42
+ return {
43
+ name: "LoggerPlugin",
44
+ enforce: "pre",
45
+ setup(app) {
46
+ app.logger = {
47
+ info: createLogMethod("info"),
48
+ error: createLogMethod("error"),
49
+ debug: createLogMethod("debug"),
50
+ warn: createLogMethod("warn")
51
+ };
52
+ }
53
+ };
51
54
  };
52
55
 
53
56
  //#endregion
@@ -100,15 +103,16 @@ const setupWorkerRestart = (shuttingDownRef) => {
100
103
  /**
101
104
  * 启动 HTTP 服务器
102
105
  * @param app - 应用实例
106
+ * @param options
103
107
  */
104
- const startHttpServer = (app) => {
108
+ const startHttpServer = (app, options) => {
105
109
  try {
106
- app.server = Bun.serve({
110
+ app.server = Bun.serve(merge({}, {
107
111
  port: app.loader.config.port,
108
112
  hostname: app.loader.config.host,
109
113
  reusePort: true,
110
114
  routes: app.routes
111
- });
115
+ }, options));
112
116
  } catch (error) {
113
117
  console.error("Failed to start server:", error);
114
118
  process.exit(1);
@@ -175,57 +179,59 @@ const gracefulShutdownWorkers = () => {
175
179
  * 服务器插件,提供集群管理和 HTTP 服务功能
176
180
  * 支持多进程模式和工作进程自动重启
177
181
  */
178
- const ServerPlugin = {
179
- name: "ServerPlugin",
180
- enforce: "post",
181
- run(app) {
182
- const isPrimaryProcess = cluster.isPrimary;
183
- const config = app.loader.config;
184
- invariant(typeof config.workers === "number", "config.workers must be a number");
185
- const workerCount = calculateWorkerCount(config.workers);
186
- const shuttingDownRef = { value: false };
187
- if (isPrimaryProcess && workerCount > 1) {
188
- console.log(`Starting ${workerCount} workers...`);
189
- startWorkers(workerCount);
190
- setupWorkerRestart(shuttingDownRef);
191
- } else {
192
- startHttpServer(app);
193
- setupWorkerMessageHandler(app);
194
- }
195
- this.isPrimary = isPrimaryProcess;
196
- this.shuttingDownRef = shuttingDownRef;
197
- },
198
- async close(app) {
199
- const isTest = isTestEnvironment();
200
- const isPrimary = this.isPrimary;
201
- const shuttingDownRef = this.shuttingDownRef || { value: false };
202
- if (isTest) {
203
- try {
204
- await app.server?.stop?.();
205
- } catch (error) {
206
- console.error("Error stopping server in test mode:", error);
182
+ const ServerPlugin = (options) => {
183
+ return {
184
+ name: "ServerPlugin",
185
+ enforce: "post",
186
+ run(app) {
187
+ const isPrimaryProcess = cluster.isPrimary;
188
+ const config = app.loader.config;
189
+ invariant(typeof config.workers === "number", "config.workers must be a number");
190
+ const workerCount = calculateWorkerCount(config.workers);
191
+ const shuttingDownRef = { value: false };
192
+ if (isPrimaryProcess && workerCount > 1) {
193
+ console.log(`Starting ${workerCount} workers...`);
194
+ startWorkers(workerCount);
195
+ setupWorkerRestart(shuttingDownRef);
196
+ } else {
197
+ startHttpServer(app, options);
198
+ setupWorkerMessageHandler(app);
207
199
  }
208
- return;
209
- }
210
- if (!isPrimary) {
200
+ this.isPrimary = isPrimaryProcess;
201
+ this.shuttingDownRef = shuttingDownRef;
202
+ },
203
+ async close(app) {
204
+ const isTest = isTestEnvironment();
205
+ const isPrimary = this.isPrimary;
206
+ const shuttingDownRef = this.shuttingDownRef || { value: false };
207
+ if (isTest) {
208
+ try {
209
+ await app.server?.stop?.();
210
+ } catch (error) {
211
+ console.error("Error stopping server in test mode:", error);
212
+ }
213
+ return;
214
+ }
215
+ if (!isPrimary) {
216
+ try {
217
+ await app.server?.stop?.();
218
+ } catch (error) {
219
+ console.error("Error stopping worker server:", error);
220
+ }
221
+ process.exit(0);
222
+ }
223
+ if (shuttingDownRef.value) return;
224
+ shuttingDownRef.value = true;
225
+ console.log("Shutting down server cluster...");
211
226
  try {
212
- await app.server?.stop?.();
227
+ await gracefulShutdownWorkers();
228
+ console.log("All workers shut down successfully");
213
229
  } catch (error) {
214
- console.error("Error stopping worker server:", error);
230
+ console.error("Error during cluster shutdown:", error);
215
231
  }
216
232
  process.exit(0);
217
233
  }
218
- if (shuttingDownRef.value) return;
219
- shuttingDownRef.value = true;
220
- console.log("Shutting down server cluster...");
221
- try {
222
- await gracefulShutdownWorkers();
223
- console.log("All workers shut down successfully");
224
- } catch (error) {
225
- console.error("Error during cluster shutdown:", error);
226
- }
227
- process.exit(0);
228
- }
234
+ };
229
235
  };
230
236
 
231
237
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["LoggerPlugin: Plugin","ServerPlugin: Plugin"],"sources":["../src/logger.ts","../src/server.ts"],"sourcesContent":["import { type Plugin, type Application, type LogScope, LOG_COLORS, type LogLevel } from '@thinkbun/core';\nimport pc from 'picocolors';\n\n/**\n * 生成作用域前缀\n * @param scope - 日志作用域\n * @returns 格式化的作用域前缀\n */\nconst generateScopePrefix = (scope: LogScope | LogScope[]): string => {\n if (Array.isArray(scope)) {\n return scope.map((item) => `[${item}]`).join('');\n }\n return `[${scope}]`;\n};\n\n/**\n * 获取日志颜色函数\n * @param scope - 日志作用域\n * @returns 颜色函数\n */\nconst getLogColor = (scope: LogScope) => {\n const colorName = LOG_COLORS[scope as keyof typeof LOG_COLORS] || 'yellow';\n return pc[colorName];\n};\n\n/**\n * 创建统一的日志方法\n * @param level - 日志级别\n * @returns 日志方法函数\n */\nconst createLogMethod = (level: LogLevel) => {\n return (msg: string, scope: LogScope = 'app'): void => {\n const consoleMethod =\n level === 'info'\n ? console.log\n : level === 'error'\n ? console.error\n : level === 'debug'\n ? console.debug\n : console.warn;\n\n const scopePrefix = generateScopePrefix(scope);\n const colorFn = getLogColor(scope);\n\n consoleMethod(colorFn(scopePrefix), msg);\n };\n};\n\n/**\n * 日志插件,为应用添加统一的日志功能\n * 支持不同作用域的彩色日志输出\n */\nexport const LoggerPlugin: Plugin = {\n name: 'LoggerPlugin',\n enforce: 'pre',\n setup(app: Application) {\n app.logger = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n debug: createLogMethod('debug'),\n warn: createLogMethod('warn'),\n };\n },\n};\n","import cluster from 'node:cluster';\nimport os from 'node:os';\n\nimport { invariant, type Plugin, type Application } from '@thinkbun/core';\n\n/**\n * 集群关闭超时时间(毫秒)\n */\nconst CLUSTER_SHUTDOWN_TIMEOUT = 5000;\n\n/**\n * 集群消息类型\n */\ninterface ClusterMessage {\n type: 'shutdown';\n}\n\n/**\n * 服务器配置接口\n */\ninterface ServerConfig {\n workers: number;\n port: number;\n host: string;\n}\n\n/**\n * 检查是否为测试环境\n */\nconst isTestEnvironment = (): boolean => {\n return typeof Bun !== 'undefined' && Bun.env.NODE_ENV === 'test';\n};\n\n/**\n * 计算合适的工作进程数量\n * @param configWorkers - 配置中的工作进程数量\n * @returns 实际使用的工作进程数量\n */\nconst calculateWorkerCount = (configWorkers: number): number => {\n const cpuCount = os.cpus().length;\n return configWorkers === 0 ? cpuCount : Math.max(1, Math.min(cpuCount, configWorkers));\n};\n\n/**\n * 启动工作进程\n * @param count - 工作进程数量\n */\nconst startWorkers = (count: number): void => {\n for (let i = 0; i < count; i++) {\n try {\n cluster.fork();\n } catch (error) {\n console.error(`Failed to fork worker ${i}:`, error);\n }\n }\n};\n\n/**\n * 设置工作进程重启逻辑\n * @param shuttingDownRef - 关闭状态引用\n */\nconst setupWorkerRestart = (shuttingDownRef: { value: boolean }): void => {\n (cluster as any).on('exit', (worker: any, code: number, signal: string) => {\n if (shuttingDownRef.value) {\n return;\n }\n\n console.log(`Worker ${worker.process.pid} died (${code}, ${signal}). Restarting...`);\n\n try {\n cluster.fork();\n } catch (error) {\n console.error('Failed to restart worker:', error);\n }\n });\n};\n\n/**\n * 启动 HTTP 服务器\n * @param app - 应用实例\n */\nconst startHttpServer = (app: Application): void => {\n try {\n app.server = Bun.serve({\n port: app.loader.config.port,\n hostname: app.loader.config.host,\n reusePort: true,\n routes: app.routes,\n });\n } catch (error) {\n console.error('Failed to start server:', error);\n process.exit(1);\n }\n};\n\n/**\n * 设置工作进程的消息处理\n * @param app - 应用实例\n */\nconst setupWorkerMessageHandler = (app: Application): void => {\n process.on('message', async (msg: ClusterMessage) => {\n if (typeof msg === 'object' && msg !== null && msg.type === 'shutdown') {\n try {\n await app.server?.stop?.();\n process.exit(0);\n } catch (error) {\n console.error('Error during worker shutdown:', error);\n process.exit(1);\n }\n }\n });\n};\n\n/**\n * 优雅关闭工作进程\n */\nconst gracefulShutdownWorkers = (): Promise<void> => {\n return new Promise<void>((resolve) => {\n const workers = cluster.workers || {};\n const workerCount = Object.keys(workers).length;\n\n if (workerCount === 0) {\n resolve();\n return;\n }\n\n let exitedWorkers = 0;\n\n // 设置强制关闭定时器\n const forceKillTimer = setTimeout(() => {\n console.log('Force killing remaining workers...');\n for (const id in workers) {\n try {\n workers[id]?.kill('SIGKILL');\n } catch (error) {\n console.error(`Failed to kill worker ${id}:`, error);\n }\n }\n resolve();\n }, CLUSTER_SHUTDOWN_TIMEOUT);\n\n // 监听工作进程退出\n const onWorkerExit = () => {\n exitedWorkers++;\n if (exitedWorkers >= workerCount) {\n clearTimeout(forceKillTimer);\n resolve();\n }\n };\n\n (cluster as any).on('exit', onWorkerExit);\n\n // 发送关闭信号\n for (const id in workers) {\n try {\n workers[id]?.send({ type: 'shutdown' });\n } catch (error) {\n console.error(`Failed to send shutdown message to worker ${id}:`, error);\n // 如果发送失败,直接视为已退出\n exitedWorkers++;\n }\n }\n\n // 检查是否所有工作进程都已经退出\n if (exitedWorkers >= workerCount) {\n clearTimeout(forceKillTimer);\n (cluster as any).removeListener('exit', onWorkerExit);\n resolve();\n }\n });\n};\n\n/**\n * 服务器插件,提供集群管理和 HTTP 服务功能\n * 支持多进程模式和工作进程自动重启\n */\nexport const ServerPlugin: Plugin = {\n name: 'ServerPlugin',\n enforce: 'post',\n\n /**\n * 插件运行逻辑\n * @param app - 应用实例\n */\n run(app: Application) {\n const isPrimaryProcess = cluster.isPrimary;\n const config = app.loader.config as ServerConfig;\n\n // 验证配置\n invariant(typeof config.workers === 'number', 'config.workers must be a number');\n\n const workerCount = calculateWorkerCount(config.workers);\n const shuttingDownRef = { value: false };\n\n if (isPrimaryProcess && workerCount > 1) {\n // 主进程:启动工作进程\n console.log(`Starting ${workerCount} workers...`);\n startWorkers(workerCount);\n setupWorkerRestart(shuttingDownRef);\n } else {\n // 工作进程或单进程模式:启动 HTTP 服务器\n startHttpServer(app);\n setupWorkerMessageHandler(app);\n }\n\n // 保存状态供关闭时使用\n (this as any).isPrimary = isPrimaryProcess;\n (this as any).shuttingDownRef = shuttingDownRef;\n },\n\n /**\n * 插件关闭逻辑\n * @param app - 应用实例\n */\n async close(app: Application) {\n const isTest = isTestEnvironment();\n const isPrimary = (this as any).isPrimary;\n const shuttingDownRef = (this as any).shuttingDownRef || { value: false };\n\n if (isTest) {\n // 测试环境:只关闭服务器,不退出进程\n try {\n await app.server?.stop?.();\n } catch (error) {\n console.error('Error stopping server in test mode:', error);\n }\n return;\n }\n\n // 非主进程工作节点:关闭服务器并退出\n if (!isPrimary) {\n try {\n await app.server?.stop?.();\n } catch (error) {\n console.error('Error stopping worker server:', error);\n }\n process.exit(0);\n }\n\n // 主进程:优雅关闭所有工作进程\n if (shuttingDownRef.value) {\n return;\n }\n\n shuttingDownRef.value = true;\n console.log('Shutting down server cluster...');\n\n try {\n await gracefulShutdownWorkers();\n console.log('All workers shut down successfully');\n } catch (error) {\n console.error('Error during cluster shutdown:', error);\n }\n\n process.exit(0);\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["LoggerPlugin: Plugin","ServerPlugin: Plugin"],"sources":["../src/logger.ts","../src/server.ts"],"sourcesContent":["import { type Plugin, type Application, type LogScope, LOG_COLORS, type LogLevel } from '@thinkbun/core';\nimport pc from 'picocolors';\n\n/**\n * 生成作用域前缀\n * @param scope - 日志作用域\n * @returns 格式化的作用域前缀\n */\nconst generateScopePrefix = (scope: LogScope | LogScope[]): string => {\n if (Array.isArray(scope)) {\n return scope.map((item) => `[${item}]`).join('');\n }\n return `[${scope}]`;\n};\n\n/**\n * 获取日志颜色函数\n * @param scope - 日志作用域\n * @returns 颜色函数\n */\nconst getLogColor = (scope: LogScope) => {\n const colorName = LOG_COLORS[scope as keyof typeof LOG_COLORS] || 'yellow';\n return pc[colorName];\n};\n\n/**\n * 创建统一的日志方法\n * @param level - 日志级别\n * @returns 日志方法函数\n */\nconst createLogMethod = (level: LogLevel) => {\n return (msg: string, scope: LogScope = 'app'): void => {\n const consoleMethod =\n level === 'info'\n ? console.log\n : level === 'error'\n ? console.error\n : level === 'debug'\n ? console.debug\n : console.warn;\n\n const scopePrefix = generateScopePrefix(scope);\n const colorFn = getLogColor(scope);\n\n consoleMethod(colorFn(scopePrefix), msg);\n };\n};\n\n/**\n * 日志插件,为应用添加统一的日志功能\n * 支持不同作用域的彩色日志输出\n */\nexport const LoggerPlugin: Plugin = () => {\n return {\n name: 'LoggerPlugin',\n enforce: 'pre',\n setup(app: Application) {\n app.logger = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n debug: createLogMethod('debug'),\n warn: createLogMethod('warn'),\n };\n },\n };\n};\n","import cluster from 'node:cluster';\nimport os from 'node:os';\n\nimport { invariant, type Plugin, type Application } from '@thinkbun/core';\nimport { merge } from 'lodash-es';\n\n/**\n * 集群关闭超时时间(毫秒)\n */\nconst CLUSTER_SHUTDOWN_TIMEOUT = 5000;\n\n/**\n * 集群消息类型\n */\ninterface ClusterMessage {\n type: 'shutdown';\n}\n\n/**\n * 服务器配置接口\n */\ninterface ServerConfig {\n workers: number;\n port: number;\n host: string;\n}\n\n/**\n * 检查是否为测试环境\n */\nconst isTestEnvironment = (): boolean => {\n return typeof Bun !== 'undefined' && Bun.env.NODE_ENV === 'test';\n};\n\n/**\n * 计算合适的工作进程数量\n * @param configWorkers - 配置中的工作进程数量\n * @returns 实际使用的工作进程数量\n */\nconst calculateWorkerCount = (configWorkers: number): number => {\n const cpuCount = os.cpus().length;\n return configWorkers === 0 ? cpuCount : Math.max(1, Math.min(cpuCount, configWorkers));\n};\n\n/**\n * 启动工作进程\n * @param count - 工作进程数量\n */\nconst startWorkers = (count: number): void => {\n for (let i = 0; i < count; i++) {\n try {\n cluster.fork();\n } catch (error) {\n console.error(`Failed to fork worker ${i}:`, error);\n }\n }\n};\n\n/**\n * 设置工作进程重启逻辑\n * @param shuttingDownRef - 关闭状态引用\n */\nconst setupWorkerRestart = (shuttingDownRef: { value: boolean }): void => {\n (cluster as any).on('exit', (worker: any, code: number, signal: string) => {\n if (shuttingDownRef.value) {\n return;\n }\n\n console.log(`Worker ${worker.process.pid} died (${code}, ${signal}). Restarting...`);\n\n try {\n cluster.fork();\n } catch (error) {\n console.error('Failed to restart worker:', error);\n }\n });\n};\n\n/**\n * 启动 HTTP 服务器\n * @param app - 应用实例\n * @param options\n */\nconst startHttpServer = (app: Application, options?: Bun.Serve.Options<unknown, string>): void => {\n try {\n app.server = Bun.serve(\n merge(\n {},\n {\n port: app.loader.config.port,\n hostname: app.loader.config.host,\n reusePort: true,\n routes: app.routes,\n },\n options,\n ),\n );\n } catch (error) {\n console.error('Failed to start server:', error);\n process.exit(1);\n }\n};\n\n/**\n * 设置工作进程的消息处理\n * @param app - 应用实例\n */\nconst setupWorkerMessageHandler = (app: Application): void => {\n process.on('message', async (msg: ClusterMessage) => {\n if (typeof msg === 'object' && msg !== null && msg.type === 'shutdown') {\n try {\n await app.server?.stop?.();\n process.exit(0);\n } catch (error) {\n console.error('Error during worker shutdown:', error);\n process.exit(1);\n }\n }\n });\n};\n\n/**\n * 优雅关闭工作进程\n */\nconst gracefulShutdownWorkers = (): Promise<void> => {\n return new Promise<void>((resolve) => {\n const workers = cluster.workers || {};\n const workerCount = Object.keys(workers).length;\n\n if (workerCount === 0) {\n resolve();\n return;\n }\n\n let exitedWorkers = 0;\n\n // 设置强制关闭定时器\n const forceKillTimer = setTimeout(() => {\n console.log('Force killing remaining workers...');\n for (const id in workers) {\n try {\n workers[id]?.kill('SIGKILL');\n } catch (error) {\n console.error(`Failed to kill worker ${id}:`, error);\n }\n }\n resolve();\n }, CLUSTER_SHUTDOWN_TIMEOUT);\n\n // 监听工作进程退出\n const onWorkerExit = () => {\n exitedWorkers++;\n if (exitedWorkers >= workerCount) {\n clearTimeout(forceKillTimer);\n resolve();\n }\n };\n\n (cluster as any).on('exit', onWorkerExit);\n\n // 发送关闭信号\n for (const id in workers) {\n try {\n workers[id]?.send({ type: 'shutdown' });\n } catch (error) {\n console.error(`Failed to send shutdown message to worker ${id}:`, error);\n // 如果发送失败,直接视为已退出\n exitedWorkers++;\n }\n }\n\n // 检查是否所有工作进程都已经退出\n if (exitedWorkers >= workerCount) {\n clearTimeout(forceKillTimer);\n (cluster as any).removeListener('exit', onWorkerExit);\n resolve();\n }\n });\n};\n\n/**\n * 服务器插件,提供集群管理和 HTTP 服务功能\n * 支持多进程模式和工作进程自动重启\n */\nexport const ServerPlugin: Plugin = (options?: Bun.Serve.Options<unknown, string>) => {\n return {\n name: 'ServerPlugin',\n enforce: 'post',\n\n /**\n * 插件运行逻辑\n * @param app - 应用实例\n */\n run(app: Application) {\n const isPrimaryProcess = cluster.isPrimary;\n const config = app.loader.config as ServerConfig;\n\n // 验证配置\n invariant(typeof config.workers === 'number', 'config.workers must be a number');\n\n const workerCount = calculateWorkerCount(config.workers);\n const shuttingDownRef = { value: false };\n\n if (isPrimaryProcess && workerCount > 1) {\n // 主进程:启动工作进程\n console.log(`Starting ${workerCount} workers...`);\n startWorkers(workerCount);\n setupWorkerRestart(shuttingDownRef);\n } else {\n // 工作进程或单进程模式:启动 HTTP 服务器\n startHttpServer(app, options);\n setupWorkerMessageHandler(app);\n }\n\n // 保存状态供关闭时使用\n (this as any).isPrimary = isPrimaryProcess;\n (this as any).shuttingDownRef = shuttingDownRef;\n },\n\n /**\n * 插件关闭逻辑\n * @param app - 应用实例\n */\n async close(app: Application) {\n const isTest = isTestEnvironment();\n const isPrimary = (this as any).isPrimary;\n const shuttingDownRef = (this as any).shuttingDownRef || { value: false };\n\n if (isTest) {\n // 测试环境:只关闭服务器,不退出进程\n try {\n await app.server?.stop?.();\n } catch (error) {\n console.error('Error stopping server in test mode:', error);\n }\n return;\n }\n\n // 非主进程工作节点:关闭服务器并退出\n if (!isPrimary) {\n try {\n await app.server?.stop?.();\n } catch (error) {\n console.error('Error stopping worker server:', error);\n }\n process.exit(0);\n }\n\n // 主进程:优雅关闭所有工作进程\n if (shuttingDownRef.value) {\n return;\n }\n\n shuttingDownRef.value = true;\n console.log('Shutting down server cluster...');\n\n try {\n await gracefulShutdownWorkers();\n console.log('All workers shut down successfully');\n } catch (error) {\n console.error('Error during cluster shutdown:', error);\n }\n\n process.exit(0);\n },\n };\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkbun/plugin",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.mjs",
@@ -19,8 +19,9 @@
19
19
  "typescript": "^5"
20
20
  },
21
21
  "dependencies": {
22
- "picocolors": "^1.1.1",
23
- "@thinkbun/core": "^1.0.4"
22
+ "@thinkbun/core": "^1.0.6",
23
+ "lodash-es": "^4.17.22",
24
+ "picocolors": "^1.1.1"
24
25
  },
25
26
  "files": [
26
27
  "dist"