claw-dashboard 1.8.4 → 2.0.0
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/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5331 -512
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -5
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1941 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1057 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- package/tests/worker-pool.test.js +522 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Error Module
|
|
3
|
+
* Provides enhanced error messages with helpful suggestions for common plugin mistakes
|
|
4
|
+
* @module plugin-errors
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DashboardError } from './errors.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin error codes for common mistakes
|
|
11
|
+
*/
|
|
12
|
+
export const PLUGIN_ERROR_CODES = {
|
|
13
|
+
// Manifest errors
|
|
14
|
+
MANIFEST_NOT_FOUND: 'PLUGIN_MANIFEST_NOT_FOUND',
|
|
15
|
+
MANIFEST_INVALID_JSON: 'PLUGIN_MANIFEST_INVALID_JSON',
|
|
16
|
+
MANIFEST_MISSING_FIELD: 'PLUGIN_MANIFEST_MISSING_FIELD',
|
|
17
|
+
MANIFEST_INVALID_FIELD: 'PLUGIN_MANIFEST_INVALID_FIELD',
|
|
18
|
+
MANIFEST_SCHEMA_ERROR: 'PLUGIN_MANIFEST_SCHEMA_ERROR',
|
|
19
|
+
|
|
20
|
+
// Entry point errors
|
|
21
|
+
ENTRY_NOT_FOUND: 'PLUGIN_ENTRY_NOT_FOUND',
|
|
22
|
+
ENTRY_NO_EXPORT: 'PLUGIN_ENTRY_NO_EXPORT',
|
|
23
|
+
ENTRY_INVALID_EXPORT: 'PLUGIN_ENTRY_INVALID_EXPORT',
|
|
24
|
+
ENTRY_RUNTIME_ERROR: 'PLUGIN_ENTRY_RUNTIME_ERROR',
|
|
25
|
+
|
|
26
|
+
// Widget class errors
|
|
27
|
+
WIDGET_MISSING_METHODS: 'PLUGIN_WIDGET_MISSING_METHODS',
|
|
28
|
+
WIDGET_NOT_A_CLASS: 'PLUGIN_WIDGET_NOT_A_CLASS',
|
|
29
|
+
WIDGET_CONSTRUCTOR_ERROR: 'PLUGIN_WIDGET_CONSTRUCTOR_ERROR',
|
|
30
|
+
|
|
31
|
+
// Security errors
|
|
32
|
+
PATH_INVALID: 'PLUGIN_PATH_INVALID',
|
|
33
|
+
NAME_INVALID: 'PLUGIN_NAME_INVALID',
|
|
34
|
+
|
|
35
|
+
// Config errors
|
|
36
|
+
CONFIG_INVALID: 'PLUGIN_CONFIG_INVALID',
|
|
37
|
+
CONFIG_PROCESSING_ERROR: 'PLUGIN_CONFIG_PROCESSING_ERROR',
|
|
38
|
+
|
|
39
|
+
// Dependency errors
|
|
40
|
+
DEPENDENCY_MISSING: 'PLUGIN_DEPENDENCY_MISSING',
|
|
41
|
+
DEPENDENCY_VERSION_MISMATCH: 'PLUGIN_DEPENDENCY_VERSION_MISMATCH',
|
|
42
|
+
DEPENDENCY_CIRCULAR: 'PLUGIN_DEPENDENCY_CIRCULAR',
|
|
43
|
+
|
|
44
|
+
// General errors
|
|
45
|
+
PLUGIN_LOAD_ERROR: 'PLUGIN_LOAD_ERROR',
|
|
46
|
+
PLUGIN_INIT_ERROR: 'PLUGIN_INIT_ERROR',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Error suggestions database - maps error patterns to helpful messages
|
|
51
|
+
*/
|
|
52
|
+
const ERROR_SUGGESTIONS = {
|
|
53
|
+
// Manifest suggestions
|
|
54
|
+
[PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND]: {
|
|
55
|
+
suggestion: 'Create a plugin.json file in your plugin directory',
|
|
56
|
+
docs: 'https://github.com/spleck/claw-dashboard/blob/main/docs/PLUGINS.md#plugin-structure',
|
|
57
|
+
example: `{
|
|
58
|
+
"id": "my-widget",
|
|
59
|
+
"name": "My Widget",
|
|
60
|
+
"description": "A custom widget",
|
|
61
|
+
"version": "1.0.0",
|
|
62
|
+
"type": "widget",
|
|
63
|
+
"category": "custom"
|
|
64
|
+
}`,
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
[PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON]: {
|
|
68
|
+
suggestion: 'Fix the JSON syntax in your plugin.json file',
|
|
69
|
+
commonCauses: [
|
|
70
|
+
'Trailing commas after the last property',
|
|
71
|
+
'Missing quotes around property names or string values',
|
|
72
|
+
'Unclosed brackets or braces',
|
|
73
|
+
'Comments (JSON does not support comments)',
|
|
74
|
+
],
|
|
75
|
+
fix: 'Use a JSON linter or validator to find the syntax error',
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
[PLUGIN_ERROR_CODES.MANIFEST_MISSING_FIELD]: {
|
|
79
|
+
suggestion: 'Add the required field to your plugin.json',
|
|
80
|
+
requiredFields: ['id', 'name', 'version', 'type'],
|
|
81
|
+
docs: 'https://github.com/spleck/claw-dashboard/blob/main/docs/PLUGINS.md#manifest-schema',
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
[PLUGIN_ERROR_CODES.MANIFEST_INVALID_FIELD]: {
|
|
85
|
+
suggestion: 'Correct the invalid field in your plugin.json',
|
|
86
|
+
commonFixes: {
|
|
87
|
+
id: 'Must contain only letters, numbers, hyphens, and underscores (cannot start/end with hyphen/underscore)',
|
|
88
|
+
version: 'Must follow semantic versioning (e.g., "1.0.0", "2.1.0-beta.1")',
|
|
89
|
+
type: 'Must be "widget" (currently the only supported type)',
|
|
90
|
+
category: 'Must be one of: system, monitoring, custom, example',
|
|
91
|
+
priority: 'Must be a number between 0 and 1000',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Entry point suggestions
|
|
96
|
+
[PLUGIN_ERROR_CODES.ENTRY_NOT_FOUND]: {
|
|
97
|
+
suggestion: 'Create an index.js file in your plugin directory',
|
|
98
|
+
docs: 'https://github.com/spleck/claw-dashboard/blob/main/docs/PLUGINS.md#widget-structure',
|
|
99
|
+
example: `import { BaseWidget } from 'claw-dashboard/widgets';
|
|
100
|
+
|
|
101
|
+
export default class MyWidget extends BaseWidget {
|
|
102
|
+
async init() { return true; }
|
|
103
|
+
async create(screen, theme) { /* create UI */ }
|
|
104
|
+
async getData() { return { value: 42 }; }
|
|
105
|
+
render(data) { /* render data */ }
|
|
106
|
+
async destroy() { /* cleanup */ }
|
|
107
|
+
}`,
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
[PLUGIN_ERROR_CODES.ENTRY_NO_EXPORT]: {
|
|
111
|
+
suggestion: 'Export your widget class from index.js',
|
|
112
|
+
options: [
|
|
113
|
+
'Use default export: export default class MyWidget extends BaseWidget { ... }',
|
|
114
|
+
'Use named export: export class Widget extends BaseWidget { ... }',
|
|
115
|
+
],
|
|
116
|
+
docs: 'https://github.com/spleck/claw-dashboard/blob/main/docs/PLUGINS.md#export-formats',
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
[PLUGIN_ERROR_CODES.ENTRY_INVALID_EXPORT]: {
|
|
120
|
+
suggestion: 'Your index.js must export a valid class or constructor function',
|
|
121
|
+
commonMistakes: [
|
|
122
|
+
'Exporting an object literal instead of a class',
|
|
123
|
+
'Forgetting to import BaseWidget',
|
|
124
|
+
'Exporting a plain function instead of a class',
|
|
125
|
+
],
|
|
126
|
+
fix: 'Ensure you export a class that extends BaseWidget',
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
[PLUGIN_ERROR_CODES.ENTRY_RUNTIME_ERROR]: {
|
|
130
|
+
suggestion: 'Fix the runtime error in your widget code',
|
|
131
|
+
tips: [
|
|
132
|
+
'Check for syntax errors in your JavaScript',
|
|
133
|
+
'Ensure all imported modules are installed: npm install <dependency>',
|
|
134
|
+
'Check for undefined variables or misspelled function names',
|
|
135
|
+
'Make sure you are using ES modules syntax (import/export)',
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Widget class suggestions
|
|
140
|
+
[PLUGIN_ERROR_CODES.WIDGET_MISSING_METHODS]: {
|
|
141
|
+
suggestion: 'Add the required methods to your widget class',
|
|
142
|
+
requiredMethods: ['render', 'getData'],
|
|
143
|
+
optionalMethods: ['init', 'create', 'destroy'],
|
|
144
|
+
example: `class MyWidget extends BaseWidget {
|
|
145
|
+
// Required
|
|
146
|
+
async getData() {
|
|
147
|
+
return { value: 123 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
render(data) {
|
|
151
|
+
if (this.box) {
|
|
152
|
+
this.box.setContent(String(data.value));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Optional but recommended
|
|
157
|
+
async init() { return true; }
|
|
158
|
+
async create(screen, theme) { /* create blessed elements */ }
|
|
159
|
+
async destroy() { /* cleanup */ }
|
|
160
|
+
}`,
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
[PLUGIN_ERROR_CODES.WIDGET_NOT_A_CLASS]: {
|
|
164
|
+
suggestion: 'Your widget must be a class that extends BaseWidget',
|
|
165
|
+
example: `import { BaseWidget } from 'claw-dashboard/widgets';
|
|
166
|
+
|
|
167
|
+
export default class MyWidget extends BaseWidget {
|
|
168
|
+
constructor(options) {
|
|
169
|
+
super(options);
|
|
170
|
+
// your initialization
|
|
171
|
+
}
|
|
172
|
+
}`,
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
[PLUGIN_ERROR_CODES.WIDGET_CONSTRUCTOR_ERROR]: {
|
|
176
|
+
suggestion: 'Fix the error in your widget constructor',
|
|
177
|
+
tips: [
|
|
178
|
+
'Remember to call super(options) before accessing this',
|
|
179
|
+
'Ensure constructor arguments match the expected signature',
|
|
180
|
+
'Check for null/undefined values in your constructor logic',
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Security suggestions
|
|
185
|
+
[PLUGIN_ERROR_CODES.PATH_INVALID]: {
|
|
186
|
+
suggestion: 'Use a valid plugin path within the allowed directory',
|
|
187
|
+
rules: [
|
|
188
|
+
'Plugin paths cannot contain ".." (directory traversal)',
|
|
189
|
+
'Plugin paths must be within ~/.openclaw/plugins/ or the configured plugins directory',
|
|
190
|
+
'Plugin names must be alphanumeric with hyphens/underscores only',
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
[PLUGIN_ERROR_CODES.NAME_INVALID]: {
|
|
195
|
+
suggestion: 'Use a valid plugin name',
|
|
196
|
+
rules: [
|
|
197
|
+
'Must start and end with alphanumeric character',
|
|
198
|
+
'Can contain letters, numbers, hyphens (-), and underscores (_)',
|
|
199
|
+
'Cannot contain spaces or special characters',
|
|
200
|
+
'Examples: "my-widget", "cpu_monitor", "plugin1"',
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Config suggestions
|
|
205
|
+
[PLUGIN_ERROR_CODES.CONFIG_INVALID]: {
|
|
206
|
+
suggestion: 'Fix the config in your plugin.json',
|
|
207
|
+
tips: [
|
|
208
|
+
'Config must be a valid JSON object',
|
|
209
|
+
'Property names must be quoted in JSON',
|
|
210
|
+
'Check for proper nesting of objects and arrays',
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// Dependency suggestions
|
|
215
|
+
[PLUGIN_ERROR_CODES.DEPENDENCY_MISSING]: {
|
|
216
|
+
suggestion: 'Install the missing dependency',
|
|
217
|
+
options: [
|
|
218
|
+
'Install the missing plugin to ~/.openclaw/plugins/',
|
|
219
|
+
'Add the dependency to your plugin\'s dependencies array in plugin.json',
|
|
220
|
+
'Remove the dependency from your plugin if not needed',
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
[PLUGIN_ERROR_CODES.DEPENDENCY_CIRCULAR]: {
|
|
225
|
+
suggestion: 'Remove circular dependencies between plugins',
|
|
226
|
+
example: 'If Plugin A depends on Plugin B, Plugin B cannot depend on Plugin A',
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// General suggestions
|
|
230
|
+
[PLUGIN_ERROR_CODES.PLUGIN_LOAD_ERROR]: {
|
|
231
|
+
suggestion: 'Check the plugin documentation and examples',
|
|
232
|
+
docs: 'https://github.com/spleck/claw-dashboard/blob/main/docs/PLUGINS.md',
|
|
233
|
+
examples: 'See example plugins in examples/plugins/ directory',
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* PluginError - Enhanced error class with helpful suggestions
|
|
239
|
+
* @extends DashboardError
|
|
240
|
+
*/
|
|
241
|
+
export class PluginError extends DashboardError {
|
|
242
|
+
constructor(code, message, details = {}) {
|
|
243
|
+
super(message, code, details);
|
|
244
|
+
this.name = 'PluginError';
|
|
245
|
+
this.code = code;
|
|
246
|
+
this.pluginId = details.pluginId || details.id || 'unknown';
|
|
247
|
+
this.suggestion = this._getSuggestion();
|
|
248
|
+
this.docs = this._getDocs();
|
|
249
|
+
this.fix = this._getFix();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get the suggestion for this error code
|
|
254
|
+
* @private
|
|
255
|
+
*/
|
|
256
|
+
_getSuggestion() {
|
|
257
|
+
const info = ERROR_SUGGESTIONS[this.code];
|
|
258
|
+
return info?.suggestion || 'Check the plugin documentation for more information';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get documentation URL for this error
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
_getDocs() {
|
|
266
|
+
const info = ERROR_SUGGESTIONS[this.code];
|
|
267
|
+
return info?.docs || null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get fix instructions for this error
|
|
272
|
+
* @private
|
|
273
|
+
*/
|
|
274
|
+
_getFix() {
|
|
275
|
+
const info = ERROR_SUGGESTIONS[this.code];
|
|
276
|
+
return info?.fix || info?.tips || info?.commonCauses || info?.rules || info?.options || null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get a formatted error message with suggestion
|
|
281
|
+
* @returns {string} Formatted error message
|
|
282
|
+
*/
|
|
283
|
+
getFormattedMessage() {
|
|
284
|
+
const lines = [
|
|
285
|
+
`Plugin Error [${this.code}]: ${this.message}`,
|
|
286
|
+
'',
|
|
287
|
+
`Plugin: ${this.pluginId}`,
|
|
288
|
+
'',
|
|
289
|
+
`💡 Suggestion: ${this.suggestion}`,
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
if (this.docs) {
|
|
293
|
+
lines.push('', `📚 Documentation: ${this.docs}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (this.fix) {
|
|
297
|
+
if (Array.isArray(this.fix)) {
|
|
298
|
+
lines.push('', '🔧 Possible fixes:');
|
|
299
|
+
this.fix.forEach((f, i) => lines.push(` ${i + 1}. ${f}`));
|
|
300
|
+
} else {
|
|
301
|
+
lines.push('', `🔧 Fix: ${this.fix}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const info = ERROR_SUGGESTIONS[this.code];
|
|
306
|
+
if (info?.example) {
|
|
307
|
+
lines.push('', '💻 Example:', ...info.example.split('\n').map(l => ` ${l}`));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return lines.join('\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get a short hint for console display
|
|
315
|
+
* @returns {string} Short hint message
|
|
316
|
+
*/
|
|
317
|
+
getHint() {
|
|
318
|
+
return `${this.suggestion} (see docs: ${this.docs || 'PLUGINS.md'})`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
toJSON() {
|
|
322
|
+
return {
|
|
323
|
+
...super.toJSON(),
|
|
324
|
+
pluginId: this.pluginId,
|
|
325
|
+
suggestion: this.suggestion,
|
|
326
|
+
docs: this.docs,
|
|
327
|
+
fix: this.fix,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* PluginErrorAnalyzer - Analyzes errors and creates PluginError instances
|
|
334
|
+
*/
|
|
335
|
+
export class PluginErrorAnalyzer {
|
|
336
|
+
/**
|
|
337
|
+
* Analyze an error and create a PluginError with helpful suggestions
|
|
338
|
+
* @param {Error} originalError - The original error
|
|
339
|
+
* @param {string} pluginId - Plugin ID or path
|
|
340
|
+
* @param {Object} context - Additional context
|
|
341
|
+
* @returns {PluginError} Enhanced plugin error
|
|
342
|
+
*/
|
|
343
|
+
static analyze(originalError, pluginId, context = {}) {
|
|
344
|
+
const { phase = 'unknown', manifest = null } = context;
|
|
345
|
+
|
|
346
|
+
// Determine error code based on error message and phase
|
|
347
|
+
const code = this._determineErrorCode(originalError, phase);
|
|
348
|
+
const message = this._createMessage(code, originalError, pluginId, context);
|
|
349
|
+
|
|
350
|
+
return new PluginError(code, message, {
|
|
351
|
+
pluginId,
|
|
352
|
+
originalError: originalError?.message || originalError,
|
|
353
|
+
phase,
|
|
354
|
+
manifest,
|
|
355
|
+
stack: originalError?.stack,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Determine the error code from the error and phase
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
static _determineErrorCode(error, phase) {
|
|
364
|
+
const msg = (error?.message || String(error)).toLowerCase();
|
|
365
|
+
|
|
366
|
+
// Manifest phase errors
|
|
367
|
+
if (phase === 'manifest') {
|
|
368
|
+
if (msg.includes('enoent') || msg.includes('not found')) {
|
|
369
|
+
return PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND;
|
|
370
|
+
}
|
|
371
|
+
if (msg.includes('json') && (msg.includes('parse') || msg.includes('syntax') || msg.includes('unexpected'))) {
|
|
372
|
+
return PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON;
|
|
373
|
+
}
|
|
374
|
+
if (msg.includes('missing') || msg.includes('required')) {
|
|
375
|
+
return PLUGIN_ERROR_CODES.MANIFEST_MISSING_FIELD;
|
|
376
|
+
}
|
|
377
|
+
if (msg.includes('invalid')) {
|
|
378
|
+
return PLUGIN_ERROR_CODES.MANIFEST_INVALID_FIELD;
|
|
379
|
+
}
|
|
380
|
+
return PLUGIN_ERROR_CODES.MANIFEST_SCHEMA_ERROR;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Entry point phase errors
|
|
384
|
+
if (phase === 'entry') {
|
|
385
|
+
if (msg.includes('enoent') || msg.includes('not found') || msg.includes('cannot find module')) {
|
|
386
|
+
return PLUGIN_ERROR_CODES.ENTRY_NOT_FOUND;
|
|
387
|
+
}
|
|
388
|
+
if (msg.includes('export') || msg.includes('does not provide')) {
|
|
389
|
+
return PLUGIN_ERROR_CODES.ENTRY_NO_EXPORT;
|
|
390
|
+
}
|
|
391
|
+
return PLUGIN_ERROR_CODES.ENTRY_RUNTIME_ERROR;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Widget class errors
|
|
395
|
+
if (phase === 'widget') {
|
|
396
|
+
if (msg.includes('method') || msg.includes('render') || msg.includes('getdata')) {
|
|
397
|
+
return PLUGIN_ERROR_CODES.WIDGET_MISSING_METHODS;
|
|
398
|
+
}
|
|
399
|
+
if (msg.includes('class') || msg.includes('constructor')) {
|
|
400
|
+
return PLUGIN_ERROR_CODES.WIDGET_NOT_A_CLASS;
|
|
401
|
+
}
|
|
402
|
+
if (msg.includes('super') || msg.includes('this')) {
|
|
403
|
+
return PLUGIN_ERROR_CODES.WIDGET_CONSTRUCTOR_ERROR;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Config errors
|
|
408
|
+
if (phase === 'config') {
|
|
409
|
+
return PLUGIN_ERROR_CODES.CONFIG_INVALID;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Path/Security errors
|
|
413
|
+
if (msg.includes('path') || msg.includes('traversal') || msg.includes('unsafe')) {
|
|
414
|
+
return PLUGIN_ERROR_CODES.PATH_INVALID;
|
|
415
|
+
}
|
|
416
|
+
if (msg.includes('name') && (msg.includes('invalid') || msg.includes('format'))) {
|
|
417
|
+
return PLUGIN_ERROR_CODES.NAME_INVALID;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Dependency errors
|
|
421
|
+
if (msg.includes('dependency') || msg.includes('depends')) {
|
|
422
|
+
if (msg.includes('circular')) {
|
|
423
|
+
return PLUGIN_ERROR_CODES.DEPENDENCY_CIRCULAR;
|
|
424
|
+
}
|
|
425
|
+
return PLUGIN_ERROR_CODES.DEPENDENCY_MISSING;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return PLUGIN_ERROR_CODES.PLUGIN_LOAD_ERROR;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Create a descriptive message for the error
|
|
433
|
+
* @private
|
|
434
|
+
*/
|
|
435
|
+
static _createMessage(code, error, pluginId, context) {
|
|
436
|
+
const originalMsg = error?.message || String(error);
|
|
437
|
+
|
|
438
|
+
switch (code) {
|
|
439
|
+
case PLUGIN_ERROR_CODES.MANIFEST_NOT_FOUND:
|
|
440
|
+
return `Plugin "${pluginId}" is missing a plugin.json manifest file`;
|
|
441
|
+
case PLUGIN_ERROR_CODES.MANIFEST_INVALID_JSON:
|
|
442
|
+
return `Plugin "${pluginId}" has invalid JSON in plugin.json: ${originalMsg}`;
|
|
443
|
+
case PLUGIN_ERROR_CODES.MANIFEST_MISSING_FIELD:
|
|
444
|
+
return `Plugin "${pluginId}" manifest is missing required fields: ${originalMsg}`;
|
|
445
|
+
case PLUGIN_ERROR_CODES.MANIFEST_INVALID_FIELD:
|
|
446
|
+
return `Invalid plugin manifest for "${pluginId}": ${originalMsg}`;
|
|
447
|
+
case PLUGIN_ERROR_CODES.ENTRY_NOT_FOUND:
|
|
448
|
+
return `Plugin "${pluginId}" is missing its entry point (index.js)`;
|
|
449
|
+
case PLUGIN_ERROR_CODES.ENTRY_NO_EXPORT:
|
|
450
|
+
return `Plugin "${pluginId}" index.js does not export a widget class`;
|
|
451
|
+
case PLUGIN_ERROR_CODES.ENTRY_INVALID_EXPORT:
|
|
452
|
+
return `Plugin "${pluginId}" exports an invalid widget class: ${originalMsg}`;
|
|
453
|
+
case PLUGIN_ERROR_CODES.WIDGET_MISSING_METHODS:
|
|
454
|
+
return `Plugin "${pluginId}" widget is missing required methods: ${originalMsg}`;
|
|
455
|
+
case PLUGIN_ERROR_CODES.WIDGET_NOT_A_CLASS:
|
|
456
|
+
return `Plugin "${pluginId}" must export a class that extends BaseWidget`;
|
|
457
|
+
case PLUGIN_ERROR_CODES.WIDGET_CONSTRUCTOR_ERROR:
|
|
458
|
+
return `Plugin "${pluginId}" widget failed to construct: ${originalMsg}`;
|
|
459
|
+
case PLUGIN_ERROR_CODES.PATH_INVALID:
|
|
460
|
+
return `Plugin "${pluginId}" has an invalid path: ${originalMsg}`;
|
|
461
|
+
case PLUGIN_ERROR_CODES.NAME_INVALID:
|
|
462
|
+
return `Plugin "${pluginId}" has an invalid name format`;
|
|
463
|
+
case PLUGIN_ERROR_CODES.DEPENDENCY_MISSING:
|
|
464
|
+
return `Plugin "${pluginId}" is missing a dependency: ${originalMsg}`;
|
|
465
|
+
case PLUGIN_ERROR_CODES.DEPENDENCY_CIRCULAR:
|
|
466
|
+
return `Plugin "${pluginId}" has circular dependencies: ${originalMsg}`;
|
|
467
|
+
default:
|
|
468
|
+
return `Failed to load plugin "${pluginId}": ${originalMsg}`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Check if an error is a common plugin mistake
|
|
474
|
+
* @param {Error} error - The error to check
|
|
475
|
+
* @returns {Object|null} Analysis result or null
|
|
476
|
+
*/
|
|
477
|
+
static checkCommonMistakes(error) {
|
|
478
|
+
const msg = (error?.message || '').toLowerCase();
|
|
479
|
+
const stack = (error?.stack || '').toLowerCase();
|
|
480
|
+
|
|
481
|
+
// Check for specific common mistakes
|
|
482
|
+
const checks = [
|
|
483
|
+
{
|
|
484
|
+
pattern: /super\s*\(/,
|
|
485
|
+
check: () => stack.includes('super') && stack.includes('constructor'),
|
|
486
|
+
mistake: 'Missing super() call in constructor',
|
|
487
|
+
fix: 'Add super(options) as the first line of your constructor',
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
pattern: /cannot find module/,
|
|
491
|
+
check: () => msg.includes('cannot find module'),
|
|
492
|
+
mistake: 'Missing import/module',
|
|
493
|
+
fix: 'Install the missing module with npm install or check the import path',
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
pattern: /is not a function/,
|
|
497
|
+
check: () => msg.includes('is not a function'),
|
|
498
|
+
mistake: 'Calling a non-function',
|
|
499
|
+
fix: 'Check that the variable is a function before calling it, or verify the import',
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
pattern: /cannot read propert/,
|
|
503
|
+
check: () => msg.includes('cannot read property') || msg.includes('cannot read properties'),
|
|
504
|
+
mistake: 'Accessing property of undefined/null',
|
|
505
|
+
fix: 'Add null checks before accessing properties: obj?.property',
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
pattern: /trailing comma/,
|
|
509
|
+
check: () => msg.includes('trailing comma') || msg.includes('unexpected token }'),
|
|
510
|
+
mistake: 'Trailing comma in JSON',
|
|
511
|
+
fix: 'Remove the comma after the last property in your JSON file',
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
pattern: /unexpected token/i,
|
|
515
|
+
check: () => msg.includes('unexpected token') && msg.includes('json'),
|
|
516
|
+
mistake: 'Invalid JSON syntax',
|
|
517
|
+
fix: 'Validate your JSON syntax - check for quotes, brackets, and commas',
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
for (const check of checks) {
|
|
522
|
+
if (check.check()) {
|
|
523
|
+
return {
|
|
524
|
+
mistake: check.mistake,
|
|
525
|
+
fix: check.fix,
|
|
526
|
+
pattern: check.pattern,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Create a formatted error message for logging
|
|
537
|
+
* @param {PluginError} error - PluginError instance
|
|
538
|
+
* @param {Object} options - Formatting options
|
|
539
|
+
* @returns {string} Formatted message
|
|
540
|
+
*/
|
|
541
|
+
export function formatPluginError(error, options = {}) {
|
|
542
|
+
const { compact = false, colors = true } = options;
|
|
543
|
+
|
|
544
|
+
if (compact) {
|
|
545
|
+
return `[${error.code}] ${error.message} - ${error.getHint()}`;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return error.getFormattedMessage();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Extract helpful information from a plugin error
|
|
553
|
+
* @param {Error} error - Any error
|
|
554
|
+
* @returns {Object} Extracted information
|
|
555
|
+
*/
|
|
556
|
+
export function extractErrorInfo(error) {
|
|
557
|
+
if (error instanceof PluginError) {
|
|
558
|
+
return {
|
|
559
|
+
isPluginError: true,
|
|
560
|
+
code: error.code,
|
|
561
|
+
pluginId: error.pluginId,
|
|
562
|
+
suggestion: error.suggestion,
|
|
563
|
+
docs: error.docs,
|
|
564
|
+
hasFix: !!error.fix,
|
|
565
|
+
formatted: error.getFormattedMessage(),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Try to analyze non-PluginError instances
|
|
570
|
+
const analysis = PluginErrorAnalyzer.checkCommonMistakes(error);
|
|
571
|
+
return {
|
|
572
|
+
isPluginError: false,
|
|
573
|
+
message: error?.message,
|
|
574
|
+
commonMistake: analysis,
|
|
575
|
+
stack: error?.stack,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export default {
|
|
580
|
+
PluginError,
|
|
581
|
+
PluginErrorAnalyzer,
|
|
582
|
+
PLUGIN_ERROR_CODES,
|
|
583
|
+
formatPluginError,
|
|
584
|
+
extractErrorInfo,
|
|
585
|
+
ERROR_SUGGESTIONS,
|
|
586
|
+
};
|