claw-dashboard 1.9.0 → 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 +5236 -566
- 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,359 @@
|
|
|
1
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
const CLI_PATH = join(process.cwd(), 'index.js');
|
|
7
|
+
|
|
8
|
+
describe('validate-plugin CLI', () => {
|
|
9
|
+
let tempDir;
|
|
10
|
+
let pluginDir;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tempDir = mkdtempSync(join(os.tmpdir(), 'validate-plugin-test-'));
|
|
14
|
+
pluginDir = join(tempDir, 'test-widget');
|
|
15
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
try {
|
|
20
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
21
|
+
} catch {}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const runCli = (args) => {
|
|
25
|
+
try {
|
|
26
|
+
const output = execSync(`node ${CLI_PATH} ${args}`, {
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
cwd: process.cwd(),
|
|
29
|
+
});
|
|
30
|
+
return { exitCode: 0, output, stderr: '' };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
exitCode: error.status || 1,
|
|
34
|
+
output: error.stdout || '',
|
|
35
|
+
stderr: error.stderr || error.message,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe('help', () => {
|
|
41
|
+
test('shows help with --help', () => {
|
|
42
|
+
const result = runCli('validate-plugin --help');
|
|
43
|
+
expect(result.exitCode).toBe(0);
|
|
44
|
+
expect(result.output).toContain('Validate Plugin Manifest');
|
|
45
|
+
expect(result.output).toContain('Usage:');
|
|
46
|
+
expect(result.output).toContain('Options:');
|
|
47
|
+
expect(result.output).toContain('--json');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('shows help with -h', () => {
|
|
51
|
+
const result = runCli('validate-plugin -h');
|
|
52
|
+
expect(result.exitCode).toBe(0);
|
|
53
|
+
expect(result.output).toContain('Validate Plugin Manifest');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('validation', () => {
|
|
58
|
+
test('returns error when no path provided', () => {
|
|
59
|
+
const result = runCli('validate-plugin');
|
|
60
|
+
expect(result.exitCode).toBe(1);
|
|
61
|
+
expect(result.stderr).toContain('required');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('returns error for non-existent path', () => {
|
|
65
|
+
const result = runCli('validate-plugin /nonexistent/path/12345');
|
|
66
|
+
expect(result.exitCode).toBe(1);
|
|
67
|
+
expect(result.stderr).toContain('does not exist');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('returns error for directory without plugin.json', () => {
|
|
71
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
72
|
+
expect(result.exitCode).toBe(1);
|
|
73
|
+
expect(result.stderr).toContain('No plugin.json found');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('valid manifests', () => {
|
|
78
|
+
test('validates a valid plugin.json file', () => {
|
|
79
|
+
const validManifest = {
|
|
80
|
+
id: 'test-widget',
|
|
81
|
+
name: 'Test Widget',
|
|
82
|
+
version: '1.0.0',
|
|
83
|
+
type: 'widget',
|
|
84
|
+
category: 'custom',
|
|
85
|
+
};
|
|
86
|
+
const manifestPath = join(pluginDir, 'plugin.json');
|
|
87
|
+
writeFileSync(manifestPath, JSON.stringify(validManifest, null, 2));
|
|
88
|
+
|
|
89
|
+
const result = runCli(`validate-plugin ${manifestPath}`);
|
|
90
|
+
expect(result.exitCode).toBe(0);
|
|
91
|
+
expect(result.output).toContain('Valid plugin manifest');
|
|
92
|
+
expect(result.output).toContain('test-widget');
|
|
93
|
+
expect(result.output).toContain('Test Widget');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('validates a valid plugin directory', () => {
|
|
97
|
+
const validManifest = {
|
|
98
|
+
id: 'test-widget',
|
|
99
|
+
name: 'Test Widget',
|
|
100
|
+
version: '1.0.0',
|
|
101
|
+
type: 'widget',
|
|
102
|
+
category: 'custom',
|
|
103
|
+
};
|
|
104
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(validManifest, null, 2));
|
|
105
|
+
|
|
106
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
107
|
+
expect(result.exitCode).toBe(0);
|
|
108
|
+
expect(result.output).toContain('Valid plugin manifest');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('accepts full manifest with all fields', () => {
|
|
112
|
+
const fullManifest = {
|
|
113
|
+
id: 'system-metrics',
|
|
114
|
+
name: 'System Metrics',
|
|
115
|
+
description: 'Shows system metrics',
|
|
116
|
+
version: '2.1.0-beta.1',
|
|
117
|
+
author: 'Test Author <test@example.com>',
|
|
118
|
+
category: 'system',
|
|
119
|
+
type: 'widget',
|
|
120
|
+
lazyLoad: true,
|
|
121
|
+
priority: 50,
|
|
122
|
+
config: {
|
|
123
|
+
refreshInterval: 5000,
|
|
124
|
+
maxDataPoints: 30,
|
|
125
|
+
},
|
|
126
|
+
permissions: ['network', 'system'],
|
|
127
|
+
dependencies: ['base-widget'],
|
|
128
|
+
__version: 1,
|
|
129
|
+
};
|
|
130
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(fullManifest, null, 2));
|
|
131
|
+
|
|
132
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
133
|
+
expect(result.exitCode).toBe(0);
|
|
134
|
+
expect(result.output).toContain('Valid plugin manifest');
|
|
135
|
+
expect(result.output).toContain('system-metrics');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('invalid manifests', () => {
|
|
140
|
+
test('detects missing required fields', () => {
|
|
141
|
+
const invalidManifest = {
|
|
142
|
+
name: 'Test Widget',
|
|
143
|
+
// missing id, version, type
|
|
144
|
+
};
|
|
145
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
146
|
+
|
|
147
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
148
|
+
expect(result.exitCode).toBe(1);
|
|
149
|
+
expect(result.stderr).toContain('Invalid');
|
|
150
|
+
expect(result.stderr).toContain('Errors:');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('detects invalid id format', () => {
|
|
154
|
+
const invalidManifest = {
|
|
155
|
+
id: '_invalid-id',
|
|
156
|
+
name: 'Test',
|
|
157
|
+
version: '1.0.0',
|
|
158
|
+
type: 'widget',
|
|
159
|
+
};
|
|
160
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
161
|
+
|
|
162
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
163
|
+
expect(result.exitCode).toBe(1);
|
|
164
|
+
expect(result.stderr).toContain('Invalid');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('detects invalid semver version', () => {
|
|
168
|
+
const invalidManifest = {
|
|
169
|
+
id: 'test-widget',
|
|
170
|
+
name: 'Test',
|
|
171
|
+
version: 'not-a-version',
|
|
172
|
+
type: 'widget',
|
|
173
|
+
};
|
|
174
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
175
|
+
|
|
176
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
177
|
+
expect(result.exitCode).toBe(1);
|
|
178
|
+
expect(result.stderr).toContain('semantic version');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('detects invalid type', () => {
|
|
182
|
+
const invalidManifest = {
|
|
183
|
+
id: 'test-widget',
|
|
184
|
+
name: 'Test',
|
|
185
|
+
version: '1.0.0',
|
|
186
|
+
type: 'invalid-type',
|
|
187
|
+
};
|
|
188
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
189
|
+
|
|
190
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
191
|
+
expect(result.exitCode).toBe(1);
|
|
192
|
+
expect(result.stderr).toContain('widget');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('detects invalid category', () => {
|
|
196
|
+
const invalidManifest = {
|
|
197
|
+
id: 'test-widget',
|
|
198
|
+
name: 'Test',
|
|
199
|
+
version: '1.0.0',
|
|
200
|
+
type: 'widget',
|
|
201
|
+
category: 'invalid-category',
|
|
202
|
+
};
|
|
203
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
204
|
+
|
|
205
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
206
|
+
expect(result.exitCode).toBe(1);
|
|
207
|
+
expect(result.stderr).toContain('custom');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('detects priority out of range', () => {
|
|
211
|
+
const invalidManifest = {
|
|
212
|
+
id: 'test-widget',
|
|
213
|
+
name: 'Test',
|
|
214
|
+
version: '1.0.0',
|
|
215
|
+
type: 'widget',
|
|
216
|
+
priority: 2000,
|
|
217
|
+
};
|
|
218
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
219
|
+
|
|
220
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
221
|
+
expect(result.exitCode).toBe(1);
|
|
222
|
+
expect(result.stderr).toContain('at most');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('json output', () => {
|
|
227
|
+
test('outputs valid result as JSON with --json', () => {
|
|
228
|
+
const validManifest = {
|
|
229
|
+
id: 'test-widget',
|
|
230
|
+
name: 'Test Widget',
|
|
231
|
+
version: '1.0.0',
|
|
232
|
+
type: 'widget',
|
|
233
|
+
};
|
|
234
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(validManifest, null, 2));
|
|
235
|
+
|
|
236
|
+
const result = runCli(`validate-plugin ${pluginDir} --json`);
|
|
237
|
+
expect(result.exitCode).toBe(0);
|
|
238
|
+
const json = JSON.parse(result.output);
|
|
239
|
+
expect(json.valid).toBe(true);
|
|
240
|
+
expect(json.id).toBe('test-widget');
|
|
241
|
+
expect(json.name).toBe('Test Widget');
|
|
242
|
+
expect(json.version).toBe('1.0.0');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('outputs invalid result as JSON with -j', () => {
|
|
246
|
+
const invalidManifest = {
|
|
247
|
+
id: 'test-widget',
|
|
248
|
+
// missing required fields
|
|
249
|
+
};
|
|
250
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
251
|
+
|
|
252
|
+
const result = runCli(`validate-plugin ${pluginDir} -j`);
|
|
253
|
+
expect(result.exitCode).toBe(1);
|
|
254
|
+
const json = JSON.parse(result.output);
|
|
255
|
+
expect(json.valid).toBe(false);
|
|
256
|
+
expect(json.errors).toBeDefined();
|
|
257
|
+
expect(json.errors.length).toBeGreaterThan(0);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('includes path in JSON output', () => {
|
|
261
|
+
const validManifest = {
|
|
262
|
+
id: 'test-widget',
|
|
263
|
+
name: 'Test Widget',
|
|
264
|
+
version: '1.0.0',
|
|
265
|
+
type: 'widget',
|
|
266
|
+
};
|
|
267
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(validManifest, null, 2));
|
|
268
|
+
|
|
269
|
+
const result = runCli(`validate-plugin ${pluginDir} --json`);
|
|
270
|
+
const json = JSON.parse(result.output);
|
|
271
|
+
expect(json.path).toContain('test-widget');
|
|
272
|
+
expect(json.path).toContain('plugin.json');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('path handling', () => {
|
|
277
|
+
test('handles absolute paths', () => {
|
|
278
|
+
const validManifest = {
|
|
279
|
+
id: 'test-widget',
|
|
280
|
+
name: 'Test Widget',
|
|
281
|
+
version: '1.0.0',
|
|
282
|
+
type: 'widget',
|
|
283
|
+
};
|
|
284
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(validManifest, null, 2));
|
|
285
|
+
|
|
286
|
+
const result = runCli(`validate-plugin ${pluginDir}/plugin.json`);
|
|
287
|
+
expect(result.exitCode).toBe(0);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('handles ~ in path', () => {
|
|
291
|
+
const validManifest = {
|
|
292
|
+
id: 'test-widget',
|
|
293
|
+
name: 'Test Widget',
|
|
294
|
+
version: '1.0.0',
|
|
295
|
+
type: 'widget',
|
|
296
|
+
};
|
|
297
|
+
const homePluginDir = join(os.homedir(), '.openclaw', 'plugins', 'test-widget');
|
|
298
|
+
mkdirSync(homePluginDir, { recursive: true });
|
|
299
|
+
writeFileSync(join(homePluginDir, 'plugin.json'), JSON.stringify(validManifest, null, 2));
|
|
300
|
+
|
|
301
|
+
const result = runCli('validate-plugin ~/.openclaw/plugins/test-widget');
|
|
302
|
+
expect(result.exitCode).toBe(0);
|
|
303
|
+
|
|
304
|
+
// Cleanup
|
|
305
|
+
try {
|
|
306
|
+
rmSync(join(os.homedir(), '.openclaw', 'plugins', 'test-widget'), { recursive: true, force: true });
|
|
307
|
+
} catch {}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('edge cases', () => {
|
|
312
|
+
test('handles invalid JSON', () => {
|
|
313
|
+
writeFileSync(join(pluginDir, 'plugin.json'), 'not valid json {');
|
|
314
|
+
|
|
315
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
316
|
+
expect(result.exitCode).toBe(1);
|
|
317
|
+
expect(result.stderr).toContain('parse');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('handles empty JSON object', () => {
|
|
321
|
+
writeFileSync(join(pluginDir, 'plugin.json'), '{}');
|
|
322
|
+
|
|
323
|
+
const result = runCli(`validate-plugin ${pluginDir}`);
|
|
324
|
+
expect(result.exitCode).toBe(1);
|
|
325
|
+
expect(result.stderr).toContain('Errors:');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('handles file instead of directory for path with ~', () => {
|
|
329
|
+
// This test ensures path resolution works correctly
|
|
330
|
+
const validManifest = {
|
|
331
|
+
id: 'test-widget',
|
|
332
|
+
name: 'Test Widget',
|
|
333
|
+
version: '1.0.0',
|
|
334
|
+
type: 'widget',
|
|
335
|
+
};
|
|
336
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(validManifest, null, 2));
|
|
337
|
+
|
|
338
|
+
const result = runCli(`validate-plugin ${pluginDir}/plugin.json`);
|
|
339
|
+
expect(result.exitCode).toBe(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('validates multiple errors at once', () => {
|
|
343
|
+
const invalidManifest = {
|
|
344
|
+
id: 'test',
|
|
345
|
+
name: '',
|
|
346
|
+
version: 'invalid',
|
|
347
|
+
type: 'wrong',
|
|
348
|
+
category: 'bad',
|
|
349
|
+
priority: -5,
|
|
350
|
+
};
|
|
351
|
+
writeFileSync(join(pluginDir, 'plugin.json'), JSON.stringify(invalidManifest, null, 2));
|
|
352
|
+
|
|
353
|
+
const result = runCli(`validate-plugin ${pluginDir} --json`);
|
|
354
|
+
expect(result.exitCode).toBe(1);
|
|
355
|
+
const json = JSON.parse(result.output);
|
|
356
|
+
expect(json.errors.length).toBeGreaterThan(1);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|