@talex-touch/utils 1.0.18 → 1.0.20

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 (80) hide show
  1. package/channel/index.ts +49 -1
  2. package/common/index.ts +2 -0
  3. package/common/search/gather.ts +45 -0
  4. package/common/search/index.ts +67 -0
  5. package/common/storage/constants.ts +16 -2
  6. package/common/storage/entity/index.ts +2 -1
  7. package/common/storage/entity/openers.ts +32 -0
  8. package/common/storage/entity/shortcut-settings.ts +22 -0
  9. package/common/storage/shortcut-storage.ts +58 -0
  10. package/common/utils/file.ts +62 -0
  11. package/common/{utils.ts → utils/index.ts} +14 -2
  12. package/common/utils/polling.ts +184 -0
  13. package/common/utils/task-queue.ts +108 -0
  14. package/common/utils/time.ts +374 -0
  15. package/core-box/README.md +8 -8
  16. package/core-box/builder/index.ts +6 -0
  17. package/core-box/builder/tuff-builder.example.ts.bak +258 -0
  18. package/core-box/builder/tuff-builder.ts +1162 -0
  19. package/core-box/index.ts +5 -2
  20. package/core-box/run-tests.sh +7 -0
  21. package/core-box/search.ts +1 -536
  22. package/core-box/tuff/index.ts +6 -0
  23. package/core-box/tuff/tuff-dsl.ts +1412 -0
  24. package/electron/clipboard-helper.ts +199 -0
  25. package/electron/env-tool.ts +36 -2
  26. package/electron/file-parsers/index.ts +8 -0
  27. package/electron/file-parsers/parsers/text-parser.ts +109 -0
  28. package/electron/file-parsers/registry.ts +92 -0
  29. package/electron/file-parsers/types.ts +58 -0
  30. package/electron/index.ts +3 -0
  31. package/eventbus/index.ts +0 -7
  32. package/index.ts +3 -1
  33. package/package.json +4 -29
  34. package/plugin/channel.ts +48 -16
  35. package/plugin/index.ts +194 -30
  36. package/plugin/log/types.ts +11 -0
  37. package/plugin/node/index.ts +4 -0
  38. package/plugin/node/logger-manager.ts +113 -0
  39. package/plugin/{log → node}/logger.ts +41 -7
  40. package/plugin/plugin-source.ts +74 -0
  41. package/plugin/preload.ts +5 -15
  42. package/plugin/providers/index.ts +2 -0
  43. package/plugin/providers/registry.ts +47 -0
  44. package/plugin/providers/types.ts +54 -0
  45. package/plugin/risk/index.ts +1 -0
  46. package/plugin/risk/types.ts +20 -0
  47. package/plugin/sdk/enum/bridge-event.ts +4 -0
  48. package/plugin/sdk/enum/index.ts +1 -0
  49. package/plugin/sdk/hooks/bridge.ts +68 -0
  50. package/plugin/sdk/hooks/index.ts +2 -1
  51. package/plugin/sdk/hooks/life-cycle.ts +2 -4
  52. package/plugin/sdk/index.ts +2 -0
  53. package/plugin/sdk/storage.ts +84 -0
  54. package/plugin/sdk/types.ts +2 -2
  55. package/plugin/sdk/window/index.ts +5 -3
  56. package/preload/index.ts +2 -0
  57. package/preload/loading.ts +15 -0
  58. package/preload/renderer.ts +41 -0
  59. package/renderer/hooks/arg-mapper.ts +79 -0
  60. package/renderer/hooks/index.ts +2 -0
  61. package/renderer/hooks/initialize.ts +198 -0
  62. package/renderer/index.ts +3 -0
  63. package/renderer/storage/app-settings.ts +2 -0
  64. package/renderer/storage/base-storage.ts +1 -0
  65. package/renderer/storage/openers.ts +11 -0
  66. package/renderer/touch-sdk/env.ts +106 -0
  67. package/renderer/touch-sdk/index.ts +108 -0
  68. package/renderer/touch-sdk/terminal.ts +85 -0
  69. package/renderer/touch-sdk/utils.ts +61 -0
  70. package/search/levenshtein-utils.ts +39 -0
  71. package/search/types.ts +16 -16
  72. package/types/index.ts +2 -1
  73. package/types/modules/base.ts +146 -0
  74. package/types/modules/index.ts +4 -0
  75. package/types/modules/module-lifecycle.ts +148 -0
  76. package/types/modules/module-manager.ts +99 -0
  77. package/types/modules/module.ts +112 -0
  78. package/types/touch-app-core.ts +16 -93
  79. package/core-box/types.ts +0 -384
  80. package/plugin/log/logger-manager.ts +0 -60
package/plugin/channel.ts CHANGED
@@ -1,6 +1,4 @@
1
- // import { ipcRenderer } from 'electron';
2
- // const { ipcRenderer, IpcMainEvent } = require("electron");
3
- import { IpcRenderer, type IpcRendererEvent } from "electron";
1
+ import type { IpcRenderer, IpcRendererEvent } from 'electron'
4
2
  import {
5
3
  ChannelType,
6
4
  DataCode,
@@ -10,14 +8,41 @@ import {
10
8
  StandardChannelData,
11
9
  } from "../channel";
12
10
 
13
- let ipcRenderer: IpcRenderer
11
+ let cachedIpcRenderer: IpcRenderer | null = null
14
12
 
15
- if (typeof window === 'undefined') {
16
- ipcRenderer = require('electron').ipcRenderer
17
- } else {
18
- ipcRenderer = window.electron.ipcRenderer as unknown as IpcRenderer
13
+ // 使用惰性解析避免在打包阶段静态引入 electron
14
+ const resolveIpcRenderer = (): IpcRenderer | null => {
15
+ if (typeof window !== 'undefined') {
16
+ const bridge = (window as any)?.electron
17
+ if (bridge?.ipcRenderer) return bridge.ipcRenderer as IpcRenderer
18
+ }
19
+
20
+ try {
21
+ const electron = (globalThis as any)?.electron ?? (eval('require') as any)?.('electron')
22
+ if (electron?.ipcRenderer) return electron.ipcRenderer as IpcRenderer
23
+ } catch (error) {
24
+ // ignore – will throw below if no ipcRenderer is resolved
25
+ }
26
+
27
+ return null
28
+ }
29
+
30
+ const ensureIpcRenderer = (): IpcRenderer => {
31
+ if (!cachedIpcRenderer) {
32
+ cachedIpcRenderer = resolveIpcRenderer()
33
+ }
34
+
35
+ if (!cachedIpcRenderer) {
36
+ throw new Error('ipcRenderer is not available in the current runtime environment')
37
+ }
38
+
39
+ return cachedIpcRenderer
19
40
  }
20
41
 
42
+ /**
43
+ * @deprecated This class is deprecated and will be removed in the future.
44
+ * Due to the new secret system, ipc message transmission should unique Key, and will inject when ui view attached.
45
+ */
21
46
  class TouchChannel implements ITouchClientChannel {
22
47
  channelMap: Map<string, Function[]> = new Map();
23
48
 
@@ -25,10 +50,12 @@ class TouchChannel implements ITouchClientChannel {
25
50
 
26
51
  plugin: string;
27
52
 
53
+ private ipcRenderer: IpcRenderer
54
+
28
55
  constructor(pluginName: string) {
29
56
  this.plugin = pluginName;
30
-
31
- ipcRenderer.on("@plugin-process-message", this.__handle_main.bind(this));
57
+ this.ipcRenderer = ensureIpcRenderer()
58
+ this.ipcRenderer.on("@plugin-process-message", this.__handle_main.bind(this));
32
59
  }
33
60
 
34
61
  __parse_raw_data(e: IpcRendererEvent | undefined, arg: any): RawStandardChannelData | null {
@@ -160,7 +187,7 @@ class TouchChannel implements ITouchClientChannel {
160
187
 
161
188
  return new Promise((resolve) => {
162
189
 
163
- ipcRenderer.send("@plugin-process-message", data);
190
+ this.ipcRenderer.send("@plugin-process-message", data);
164
191
 
165
192
  this.pendingMap.set(uniqueId, (res: any) => {
166
193
  this.pendingMap.delete(uniqueId);
@@ -182,7 +209,7 @@ class TouchChannel implements ITouchClientChannel {
182
209
  },
183
210
  } as RawStandardChannelData;
184
211
 
185
- const res = this.__parse_raw_data(void 0, ipcRenderer.sendSync("@plugin-process-message", data))!
212
+ const res = this.__parse_raw_data(void 0, this.ipcRenderer.sendSync("@plugin-process-message", data))!
186
213
 
187
214
  if ( res.header.status === 'reply' ) return res.data;
188
215
 
@@ -191,12 +218,17 @@ class TouchChannel implements ITouchClientChannel {
191
218
  }
192
219
  }
193
220
 
194
- let touchChannel: ITouchClientChannel
221
+ let touchChannel: ITouchClientChannel | null = null
195
222
 
196
- export function genChannel() {
223
+ export function genChannel(): ITouchClientChannel {
197
224
  if (!touchChannel) {
198
- // @ts-ignore
199
- touchChannel = window.$channel = new TouchChannel(window.$plugin.name)
225
+ if (typeof window === 'undefined' || !(window as any)?.$plugin?.name) {
226
+ throw new Error('TouchChannel cannot be initialized outside plugin renderer context')
227
+ }
228
+
229
+ const pluginName = (window as any).$plugin.name as string
230
+ touchChannel = new TouchChannel(pluginName)
231
+ ;(window as any).$channel = touchChannel
200
232
  }
201
233
 
202
234
  return touchChannel
package/plugin/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Arch, SupportOS } from './../base/index';
2
- import { PluginLogger } from './log/logger';
2
+ import type { IPluginLogger } from './log/types';
3
+ import type { PluginInstallRequest, PluginInstallSummary } from './providers';
3
4
 
4
5
  export enum PluginStatus {
5
6
  DISABLED,
@@ -12,11 +13,23 @@ export enum PluginStatus {
12
13
 
13
14
  LOADING,
14
15
  LOADED,
16
+ LOAD_FAILED,
17
+ }
18
+
19
+ export interface PluginIssue {
20
+ type: 'error' | 'warning'
21
+ message: string
22
+ source?: string // e.g., 'manifest.json', 'feature:feature-id', 'icon'
23
+ meta?: any
24
+ code?: string
25
+ suggestion?: string
26
+ timestamp?: number
15
27
  }
16
28
 
17
29
  export interface IPluginIcon {
18
30
  type: string | 'remixicon' | 'class'
19
31
  value: any
32
+ _value?: string
20
33
 
21
34
  init(): Promise<void>
22
35
  }
@@ -40,24 +53,21 @@ export interface IPluginBaseInfo {
40
53
  desc: string
41
54
  icon: IPluginIcon
42
55
  platforms: IPlatform
56
+ _uniqueChannelKey: string
43
57
  }
44
58
 
45
59
  export interface IPluginDev {
46
60
  enable: boolean
47
61
  address: string
62
+ source?: string // 修改此处,允许 source 为字符串或 undefined
48
63
  }
49
64
 
50
- export interface IPluginWebview {
51
- }
52
-
53
-
54
-
55
65
  export interface ITouchPlugin extends IPluginBaseInfo {
56
66
  dev: IPluginDev
57
- webViewInit: boolean
58
- logger: PluginLogger
59
- webview: IPluginWebview
67
+ pluginPath: string
68
+ logger: IPluginLogger<any>
60
69
  features: IPluginFeature[]
70
+ issues: PluginIssue[]
61
71
 
62
72
  addFeature(feature: IPluginFeature): boolean
63
73
  delFeature(featureId: string): boolean
@@ -87,6 +97,15 @@ export interface IPluginFeature {
87
97
  push: boolean
88
98
  platform: IPlatform
89
99
  commands: IFeatureCommand[]
100
+ interaction?: IFeatureInteraction
101
+ }
102
+
103
+ export type IFeatureInteraction = {
104
+ type: 'webcontent' | 'widget'
105
+ /**
106
+ * The relative path to the html file from the plugin root.
107
+ */
108
+ path?: string
90
109
  }
91
110
 
92
111
  /**
@@ -106,8 +125,9 @@ export interface IFeatureLifeCycle {
106
125
  * @param id - Feature ID
107
126
  * @param data - The triggering payload
108
127
  * @param feature - The full feature definition
128
+ * @param signal - An AbortSignal to cancel the operation
109
129
  */
110
- onFeatureTriggered(id: string, data: any, feature: IPluginFeature): void
130
+ onFeatureTriggered(id: string, data: any, feature: IPluginFeature, signal?: AbortSignal): void
111
131
 
112
132
  /**
113
133
  * Called when user input changes within this feature’s input box.
@@ -130,6 +150,14 @@ export interface IFeatureLifeCycle {
130
150
  * @param feature - The feature instance being closed
131
151
  */
132
152
  onClose?(feature: IPluginFeature): void
153
+
154
+ /**
155
+ * Called when an item generated by this feature is executed.
156
+ * This is used for handling actions on the items themselves,
157
+ * rather than triggering a new feature.
158
+ * @param item The TuffItem that was executed.
159
+ */
160
+ onItemAction?(item: any): Promise<any>
133
161
  }
134
162
 
135
163
  /**
@@ -172,45 +200,181 @@ export interface ITargetFeatureLifeCycle {
172
200
  * @param feature - The feature instance being closed
173
201
  */
174
202
  onClose?(feature: IPluginFeature): void
203
+
204
+ /**
205
+ * Called when an item generated by this feature is executed.
206
+ * This is used for handling actions on the items themselves,
207
+ * rather than triggering a new feature.
208
+ * @param item The TuffItem that was executed.
209
+ */
210
+ onItemAction?(item: any): Promise<any>
211
+ }
212
+
213
+ /**
214
+ * Defines the types of plugin sources supported by the system.
215
+ * This enum allows for clear identification and selection of different plugin resolution and download mechanisms.
216
+ */
217
+ export enum PluginSourceType {
218
+ /**
219
+ * Represents the proprietary TPEX plugin format, typically a compressed package.
220
+ */
221
+ TPEX = 'tpex',
222
+ /**
223
+ * Represents plugins distributed as standard npm packages.
224
+ */
225
+ NPM = 'npm',
226
+ /**
227
+ * Represents a local file system plugin source, typically used for development or manual installation.
228
+ */
229
+ FILE_SYSTEM = 'file_system',
175
230
  }
176
231
 
232
+ import type { FSWatcher } from 'chokidar'
233
+
177
234
  export interface IPluginManager {
178
235
  plugins: Map<string, ITouchPlugin>
179
236
  active: string | null
237
+ reloadingPlugins: Set<string>
238
+ enabledPlugins: Set<string>
239
+ dbUtils: any // Temporarily any, as DbUtils is internal to core-app
240
+ initialLoadPromises: Promise<boolean>[]
180
241
  pluginPath: string
242
+ watcher: FSWatcher | null
243
+ devWatcher: any // Temporarily any, as DevPluginWatcher is internal to core-app
181
244
 
245
+ getPluginList(): Array<object>
182
246
  setActivePlugin(pluginName: string): boolean
183
-
247
+ hasPlugin(name: string): boolean
248
+ getPluginByName(name: string): ITouchPlugin | undefined
249
+ reloadPlugin(pluginName: string): Promise<void>
250
+ persistEnabledPlugins(): Promise<void>
251
+ listPlugins(): Promise<Array<string>>
184
252
  loadPlugin(pluginName: string): Promise<boolean>
185
253
  unloadPlugin(pluginName: string): Promise<boolean>
254
+ installFromSource(request: PluginInstallRequest): Promise<PluginInstallSummary>
186
255
  }
187
256
 
257
+ /**
258
+ * Interface representing a unified plugin manifest.
259
+ * This interface combines fields from the original `IManifest` and `IPluginManifest`
260
+ * to provide a comprehensive and consistent metadata structure for all plugin types (tpex and npm).
261
+ */
188
262
  export interface IManifest {
189
- name: string
190
- version: string
191
- description: string
263
+ /**
264
+ * Unique identifier for the plugin.
265
+ * This is typically the package name for npm plugins or a unique string for tpex plugins.
266
+ */
267
+ id: string;
268
+ /**
269
+ * Display name of the plugin.
270
+ * This is the human-readable name shown to users.
271
+ */
272
+ name: string;
273
+ /**
274
+ * Version of the plugin, following semantic versioning (e.g., "1.0.0").
275
+ */
276
+ version: string;
277
+ /**
278
+ * Short description of the plugin's functionality.
279
+ */
280
+ description: string;
281
+ /**
282
+ * Author of the plugin, typically a name or email.
283
+ */
284
+ author: string;
285
+ /**
286
+ * Main entry file for the plugin logic, relative to the plugin's root directory.
287
+ * This file will be loaded when the plugin is activated.
288
+ */
289
+ main: string;
290
+ /**
291
+ * Optional icon path or definition for the plugin.
292
+ * This could be a file path to an image or a specific icon class/identifier.
293
+ */
294
+ icon?: string;
295
+ /**
296
+ * Optional keywords for activating the plugin, e.g., for search or command matching.
297
+ * These keywords help users discover and launch the plugin.
298
+ */
299
+ activationKeywords?: string[];
300
+ /**
301
+ * Optional digital signature of the plugin package, used for verification.
302
+ */
303
+ _signature?: string;
304
+ /**
305
+ * Optional list of files included in the plugin package.
306
+ * This can be used for integrity checks or resource management.
307
+ */
308
+ _files?: string[];
309
+ /**
310
+ * Development-specific configuration for the plugin.
311
+ * This section is used during plugin development and might not be present in production builds.
312
+ */
192
313
  plugin?: {
193
314
  dev: {
194
- enable: boolean
195
- address: string
196
- }
197
- }
315
+ /**
316
+ * Whether development mode is enabled for the plugin.
317
+ * If true, specific development features or debugging tools might be activated.
318
+ */
319
+ enable: boolean;
320
+ /**
321
+ * Address for development server or resources.
322
+ * For example, a local URL where the plugin's frontend assets are served during development.
323
+ */
324
+ address: string;
325
+ };
326
+ };
327
+ /**
328
+ * Build-specific configuration for the plugin.
329
+ * This section defines how the plugin is built, packaged, and verified.
330
+ */
198
331
  build?: {
199
- files: string[]
332
+ /**
333
+ * List of files to include in the build.
334
+ */
335
+ files: string[];
336
+ /**
337
+ * Secret configuration for the build process.
338
+ */
200
339
  secret: {
201
- pos: string
202
- addon: string[]
203
- }
340
+ pos: string;
341
+ addon: string[];
342
+ };
343
+ /**
344
+ * Verification settings for the plugin build.
345
+ * Defines how the authenticity and integrity of the plugin are checked.
346
+ */
204
347
  verify?: {
205
- enable: boolean
206
- online: 'custom' | 'always' | 'once'
207
- }
348
+ /**
349
+ * Whether online verification is enabled.
350
+ */
351
+ enable: boolean;
352
+ /**
353
+ * Online verification strategy.
354
+ */
355
+ online: 'custom' | 'always' | 'once';
356
+ };
357
+ /**
358
+ * Version update settings for the plugin.
359
+ * Defines how the plugin handles updates and downgrades.
360
+ */
208
361
  version?: {
209
- update: 'auto' | 'ask' | 'readable'
210
- downgrade: boolean
211
- }
212
- }
362
+ /**
363
+ * Update strategy for the plugin:
364
+ * - 'auto': Automatically updates the plugin.
365
+ * - 'ask': Prompts the user before updating.
366
+ * - 'readable': Provides a readable update notification.
367
+ */
368
+ update: 'auto' | 'ask' | 'readable';
369
+ /**
370
+ * Whether downgrading the plugin version is allowed.
371
+ */
372
+ downgrade: boolean;
373
+ };
374
+ };
213
375
  }
214
376
 
215
- export * from './log/logger'
377
+ export type { LogLevel, LogItem, LogDataType, IPluginLogger } from './log/types'
216
378
  export * from './sdk/index'
379
+ export * from './providers'
380
+ export * from './risk'
@@ -25,3 +25,14 @@ export interface LogItem {
25
25
  /** Additional log data (parameters, configs, responses) */
26
26
  data: LogDataType[]
27
27
  }
28
+
29
+ /**
30
+ * Minimal contract for plugin loggers so web 端只依赖接口定义
31
+ */
32
+ export interface IPluginLogger<TManager = unknown> {
33
+ info(...args: LogDataType[]): void
34
+ warn(...args: LogDataType[]): void
35
+ error(...args: LogDataType[]): void
36
+ debug(...args: LogDataType[]): void
37
+ getManager(): TManager
38
+ }
@@ -0,0 +1,4 @@
1
+ export * from '..'
2
+ export { PluginLogger } from './logger'
3
+ export { PluginLoggerManager } from './logger-manager'
4
+ export type { LogLevel, LogItem, LogDataType, IPluginLogger } from '../log/types'
@@ -0,0 +1,113 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { LogItem } from '../log/types'
4
+ import { ITouchPlugin } from '..'
5
+ import { structuredStrictStringify } from '@talex-touch/utils'
6
+
7
+ /**
8
+ * PluginLoggerManager is responsible for managing and writing logs for a specific plugin.
9
+ */
10
+ export class PluginLoggerManager {
11
+ private readonly pluginInfo: ITouchPlugin
12
+ private readonly pluginLogDir: string
13
+ private readonly sessionLogPath: string
14
+ private readonly pluginInfoPath: string
15
+ private buffer: LogItem[] = []
16
+ private flushInterval: NodeJS.Timeout
17
+ private onLogAppend?: (log: LogItem) => void
18
+
19
+ /**
20
+ * Initializes a new PluginLoggerManager instance.
21
+ * @param baseDir - Base directory to store logs.
22
+ * @param pluginInfo - Plugin information for logging context.
23
+ * @param onLogAppend - Optional callback to be invoked when a log is appended.
24
+ */
25
+ constructor(baseDir: string, pluginInfo: ITouchPlugin, onLogAppend?: (log: LogItem) => void) {
26
+ this.pluginInfo = pluginInfo
27
+ this.onLogAppend = onLogAppend
28
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
29
+ const sessionFolder = `${timestamp}_${pluginInfo.name.replace(/[^a-zA-Z0-9-_]/g, '_')}`
30
+
31
+ this.pluginLogDir = path.resolve(baseDir, 'logs', sessionFolder)
32
+ this.sessionLogPath = path.resolve(this.pluginLogDir, 'session.log')
33
+ this.pluginInfoPath = path.resolve(this.pluginLogDir, 'touch-plugin.info')
34
+
35
+ this.ensureDirectory()
36
+ this.createPluginInfoFile()
37
+ this.flushInterval = setInterval(() => this.flush(), 5000)
38
+ }
39
+
40
+ /**
41
+ * Appends a new log item to the buffer.
42
+ * @param log - The log entry to append.
43
+ */
44
+ append(log: LogItem): void {
45
+ this.buffer.push(log)
46
+ this.onLogAppend?.(log)
47
+ }
48
+
49
+ /**
50
+ * Flushes all buffered log items to the current session log file.
51
+ */
52
+ flush(): void {
53
+ if (this.buffer.length === 0) return
54
+ const lines = this.buffer.map((item) => structuredStrictStringify(item)).join('\n') + '\n'
55
+ fs.appendFileSync(this.sessionLogPath, lines)
56
+ this.buffer = []
57
+ }
58
+
59
+ /**
60
+ * Returns the path to the current session log file.
61
+ * @returns The path to the session log.
62
+ */
63
+ public getSessionLogPath(): string {
64
+ return this.sessionLogPath
65
+ }
66
+
67
+ /**
68
+ * Returns the current log buffer.
69
+ * @returns An array of log items.
70
+ */
71
+ public getBuffer(): LogItem[] {
72
+ return this.buffer
73
+ }
74
+
75
+ /**
76
+ * Stops the flush interval and ensures remaining logs are written.
77
+ */
78
+ destroy(): void {
79
+ clearInterval(this.flushInterval)
80
+ this.flush()
81
+ }
82
+
83
+ /**
84
+ * Creates the touch-plugin.info file with plugin information.
85
+ */
86
+ private createPluginInfoFile(): void {
87
+ const pluginInfo = {
88
+ name: this.pluginInfo.name,
89
+ version: this.pluginInfo.version,
90
+ description: this.pluginInfo.desc,
91
+ sessionStart: new Date().toISOString(),
92
+ icon: this.pluginInfo.icon,
93
+ platforms: this.pluginInfo.platforms,
94
+ status: this.pluginInfo.status,
95
+ features: this.pluginInfo.features.map(feature => ({
96
+ id: feature.id,
97
+ name: feature.name,
98
+ desc: feature.desc
99
+ }))
100
+ }
101
+
102
+ fs.writeFileSync(this.pluginInfoPath, JSON.stringify(pluginInfo, null, 2))
103
+ }
104
+
105
+ /**
106
+ * Ensures the log directory exists.
107
+ */
108
+ private ensureDirectory(): void {
109
+ if (!fs.existsSync(this.pluginLogDir)) {
110
+ fs.mkdirSync(this.pluginLogDir, { recursive: true })
111
+ }
112
+ }
113
+ }
@@ -1,24 +1,25 @@
1
- import { LogLevel, LogItem, LogDataType } from './types'
2
- import { LoggerManager } from './logger-manager'
1
+ import { IPluginLogger, LogLevel, LogItem, LogDataType } from '../log/types'
2
+ import { PluginLoggerManager } from './logger-manager'
3
+ import chalk from 'chalk'
3
4
 
4
5
  /**
5
6
  * PluginLogger provides structured logging capabilities for individual plugins.
6
7
  */
7
- export class PluginLogger {
8
+ export class PluginLogger implements IPluginLogger<PluginLoggerManager> {
8
9
  private readonly pluginName: string
9
- private readonly manager: LoggerManager
10
+ private readonly manager: PluginLoggerManager
10
11
 
11
12
  /**
12
13
  * Get logger manager
13
14
  */
14
- getManager(): LoggerManager { return this.manager }
15
+ getManager(): PluginLoggerManager { return this.manager }
15
16
 
16
17
  /**
17
18
  * Creates an instance of PluginLogger.
18
19
  * @param pluginName - The name of the plugin.
19
20
  * @param manager - The logger manager instance controlling log file storage.
20
21
  */
21
- constructor(pluginName: string, manager: LoggerManager) {
22
+ constructor(pluginName: string, manager: PluginLoggerManager) {
22
23
  this.pluginName = pluginName
23
24
  this.manager = manager
24
25
  }
@@ -62,14 +63,47 @@ export class PluginLogger {
62
63
  */
63
64
  private log(level: LogLevel, ...args: LogDataType[]): void {
64
65
  const [message, ...data] = args
66
+
67
+ const normalizedLevel = (typeof level === 'string' ? level.toUpperCase() : level) as string
68
+ const allowedLevels: LogLevel[] = ['INFO', 'WARN', 'ERROR', 'DEBUG']
69
+ const resolvedLevel = (allowedLevels.includes(normalizedLevel as LogLevel)
70
+ ? (normalizedLevel as LogLevel)
71
+ : 'INFO')
72
+ if (resolvedLevel === 'INFO' && normalizedLevel !== 'INFO') {
73
+ console.warn(
74
+ `${chalk.bgMagenta('[PluginLog]')} ${chalk.bgYellow('WARN')} ${this.pluginName} - Unknown log level "${String(level)}", fallback to INFO`
75
+ )
76
+ }
77
+
78
+ const levelColorMap: Record<LogLevel, (input: string) => string> = {
79
+ INFO: chalk.bgBlue,
80
+ WARN: chalk.bgYellow,
81
+ ERROR: chalk.bgRed,
82
+ DEBUG: chalk.bgGray
83
+ }
84
+ const colorize = levelColorMap[resolvedLevel] ?? ((input: string) => input)
85
+
65
86
  const log: LogItem = {
66
87
  timestamp: new Date().toISOString(),
67
- level,
88
+ level: resolvedLevel,
68
89
  plugin: this.pluginName,
69
90
  message: String(message),
70
91
  tags: [],
71
92
  data,
72
93
  }
73
94
  this.manager.append(log)
95
+
96
+ if (resolvedLevel === 'DEBUG') {
97
+ console.debug(
98
+ `${chalk.bgMagenta('[PluginLog]')} ${colorize(resolvedLevel)} ${this.pluginName} - ${message}`,
99
+ ...data
100
+ )
101
+ } else {
102
+ console.log(
103
+ `${chalk.bgMagenta('[PluginLog]')} ${colorize(resolvedLevel)} ${this.pluginName} - ${message}`,
104
+ ...data
105
+ )
106
+ }
107
+
74
108
  }
75
109
  }