@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.
- package/channel/index.ts +49 -1
- package/common/index.ts +2 -0
- package/common/search/gather.ts +45 -0
- package/common/search/index.ts +67 -0
- package/common/storage/constants.ts +16 -2
- package/common/storage/entity/index.ts +2 -1
- package/common/storage/entity/openers.ts +32 -0
- package/common/storage/entity/shortcut-settings.ts +22 -0
- package/common/storage/shortcut-storage.ts +58 -0
- package/common/utils/file.ts +62 -0
- package/common/{utils.ts → utils/index.ts} +14 -2
- package/common/utils/polling.ts +184 -0
- package/common/utils/task-queue.ts +108 -0
- package/common/utils/time.ts +374 -0
- package/core-box/README.md +8 -8
- package/core-box/builder/index.ts +6 -0
- package/core-box/builder/tuff-builder.example.ts.bak +258 -0
- package/core-box/builder/tuff-builder.ts +1162 -0
- package/core-box/index.ts +5 -2
- package/core-box/run-tests.sh +7 -0
- package/core-box/search.ts +1 -536
- package/core-box/tuff/index.ts +6 -0
- package/core-box/tuff/tuff-dsl.ts +1412 -0
- package/electron/clipboard-helper.ts +199 -0
- package/electron/env-tool.ts +36 -2
- package/electron/file-parsers/index.ts +8 -0
- package/electron/file-parsers/parsers/text-parser.ts +109 -0
- package/electron/file-parsers/registry.ts +92 -0
- package/electron/file-parsers/types.ts +58 -0
- package/electron/index.ts +3 -0
- package/eventbus/index.ts +0 -7
- package/index.ts +3 -1
- package/package.json +4 -29
- package/plugin/channel.ts +48 -16
- package/plugin/index.ts +194 -30
- package/plugin/log/types.ts +11 -0
- package/plugin/node/index.ts +4 -0
- package/plugin/node/logger-manager.ts +113 -0
- package/plugin/{log → node}/logger.ts +41 -7
- package/plugin/plugin-source.ts +74 -0
- package/plugin/preload.ts +5 -15
- package/plugin/providers/index.ts +2 -0
- package/plugin/providers/registry.ts +47 -0
- package/plugin/providers/types.ts +54 -0
- package/plugin/risk/index.ts +1 -0
- package/plugin/risk/types.ts +20 -0
- package/plugin/sdk/enum/bridge-event.ts +4 -0
- package/plugin/sdk/enum/index.ts +1 -0
- package/plugin/sdk/hooks/bridge.ts +68 -0
- package/plugin/sdk/hooks/index.ts +2 -1
- package/plugin/sdk/hooks/life-cycle.ts +2 -4
- package/plugin/sdk/index.ts +2 -0
- package/plugin/sdk/storage.ts +84 -0
- package/plugin/sdk/types.ts +2 -2
- package/plugin/sdk/window/index.ts +5 -3
- package/preload/index.ts +2 -0
- package/preload/loading.ts +15 -0
- package/preload/renderer.ts +41 -0
- package/renderer/hooks/arg-mapper.ts +79 -0
- package/renderer/hooks/index.ts +2 -0
- package/renderer/hooks/initialize.ts +198 -0
- package/renderer/index.ts +3 -0
- package/renderer/storage/app-settings.ts +2 -0
- package/renderer/storage/base-storage.ts +1 -0
- package/renderer/storage/openers.ts +11 -0
- package/renderer/touch-sdk/env.ts +106 -0
- package/renderer/touch-sdk/index.ts +108 -0
- package/renderer/touch-sdk/terminal.ts +85 -0
- package/renderer/touch-sdk/utils.ts +61 -0
- package/search/levenshtein-utils.ts +39 -0
- package/search/types.ts +16 -16
- package/types/index.ts +2 -1
- package/types/modules/base.ts +146 -0
- package/types/modules/index.ts +4 -0
- package/types/modules/module-lifecycle.ts +148 -0
- package/types/modules/module-manager.ts +99 -0
- package/types/modules/module.ts +112 -0
- package/types/touch-app-core.ts +16 -93
- package/core-box/types.ts +0 -384
- package/plugin/log/logger-manager.ts +0 -60
package/plugin/channel.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
11
|
+
let cachedIpcRenderer: IpcRenderer | null = null
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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 {
|
|
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
|
-
|
|
58
|
-
logger:
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
|
377
|
+
export type { LogLevel, LogItem, LogDataType, IPluginLogger } from './log/types'
|
|
216
378
|
export * from './sdk/index'
|
|
379
|
+
export * from './providers'
|
|
380
|
+
export * from './risk'
|
package/plugin/log/types.ts
CHANGED
|
@@ -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,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 '
|
|
2
|
-
import {
|
|
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:
|
|
10
|
+
private readonly manager: PluginLoggerManager
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Get logger manager
|
|
13
14
|
*/
|
|
14
|
-
getManager():
|
|
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:
|
|
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
|
}
|