claw-dashboard 2.1.0 → 2.2.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/CHANGELOG.md +29 -1
- package/docs/API.md +1 -2
- package/index.js +32 -42
- package/package.json +23 -12
- package/src/auto-save.js +11 -5
- package/src/cli/export-snapshot.js +3 -1
- package/src/cli/import-snapshot.js +3 -1
- package/src/config.js +9 -15
- package/src/errors.js +0 -9
- package/src/security.js +6 -2
- package/src/snapshot.js +73 -26
- package/src/web-server.js +7 -10
- package/src/widgets/builtin-widgets.js +0 -94
- package/src/widgets/index.js +0 -1
- package/.c8rc.json +0 -38
- package/.dockerignore +0 -68
- package/.github/dependabot.yml +0 -45
- package/.github/pull_request_template.md +0 -39
- package/.github/workflows/ci.yml +0 -147
- package/.github/workflows/release.yml +0 -40
- package/.github/workflows/security.yml +0 -84
- package/.husky/pre-commit +0 -1
- package/.planning/codebase/ARCHITECTURE.md +0 -206
- package/.planning/codebase/INTEGRATIONS.md +0 -150
- package/.planning/codebase/STACK.md +0 -122
- package/.planning/codebase/STRUCTURE.md +0 -201
- package/CONTRIBUTING.md +0 -378
- package/Dockerfile +0 -54
- package/FEATURES.md +0 -307
- package/TODO.md +0 -28
- package/ai.openclaw.dashboard.plist +0 -35
- package/build-cjs.js +0 -127
- package/cjs-shim.js +0 -13
- package/dist/clawdash +0 -1729
- package/dist/clawdash.meta.json +0 -2236
- package/dist/widgets.cjs +0 -6511
- package/docker-compose.yml +0 -67
- package/esbuild.config.js +0 -158
- package/eslint.config.js +0 -56
- package/examples/plugins/README.md +0 -122
- package/examples/plugins/api-status/index.js +0 -294
- package/examples/plugins/api-status/plugin.json +0 -19
- package/examples/plugins/hello-world/index.js +0 -104
- package/examples/plugins/hello-world/plugin.json +0 -15
- package/examples/plugins/system-metrics-chart/index.js +0 -339
- package/examples/plugins/system-metrics-chart/plugin.json +0 -18
- package/examples/plugins/weather-widget/index.js +0 -136
- package/examples/plugins/weather-widget/plugin.json +0 -19
- package/index.cjs +0 -23005
- package/jest.config.js +0 -11
- package/scripts/release.js +0 -595
- package/src/database.js +0 -734
- package/tests/alerts.test.js +0 -437
- package/tests/auto-save.test.js +0 -529
- package/tests/cache.test.js +0 -317
- package/tests/cli.test.js +0 -351
- package/tests/command-palette.test.js +0 -320
- package/tests/config-processor.test.js +0 -452
- package/tests/config-validator.test.js +0 -710
- package/tests/config-watcher.test.js +0 -594
- package/tests/config.test.js +0 -755
- package/tests/database.test.js +0 -438
- package/tests/errors.test.js +0 -189
- package/tests/example-plugins.test.js +0 -624
- package/tests/integration.test.js +0 -1321
- package/tests/loading-states.test.js +0 -300
- package/tests/manifest-validation-on-load.test.js +0 -671
- package/tests/performance-monitor.test.js +0 -190
- package/tests/plugin-api-rate-limit.test.js +0 -302
- package/tests/plugin-errors.test.js +0 -311
- package/tests/plugin-lifecycle-e2e.test.js +0 -1036
- package/tests/plugin-reload.test.js +0 -764
- package/tests/rate-limiter.test.js +0 -539
- package/tests/retry.test.js +0 -308
- package/tests/security.test.js +0 -411
- package/tests/settings-modal.test.js +0 -226
- package/tests/theme-selector.test.js +0 -57
- package/tests/utils.js +0 -242
- package/tests/utils.test.js +0 -317
- package/tests/validate-plugin-cli.test.js +0 -359
- package/tests/validation.test.js +0 -837
- package/tests/web-server.test.js +0 -646
- package/tests/widget-arrange-mode.test.js +0 -183
- package/tests/widget-config-hot-reload.test.js +0 -465
- package/tests/widget-dependency.test.js +0 -591
- package/tests/widget-error-boundary.test.js +0 -749
- package/tests/widget-error-isolation.test.js +0 -469
- package/tests/widget-integration.test.js +0 -1147
- package/tests/widget-refresh-intervals.test.js +0 -284
- package/tests/worker-pool.test.js +0 -522
package/tests/cache.test.js
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for cache module
|
|
3
|
-
* TTL cache, getOrFetch, debounce/throttle utilities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
get,
|
|
8
|
-
set,
|
|
9
|
-
getOrFetch,
|
|
10
|
-
invalidate,
|
|
11
|
-
clear,
|
|
12
|
-
getStatus,
|
|
13
|
-
debounce,
|
|
14
|
-
throttle,
|
|
15
|
-
} from '../src/cache.js';
|
|
16
|
-
|
|
17
|
-
describe('cache', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
// Clear cache before each test
|
|
20
|
-
clear();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('get', () => {
|
|
24
|
-
test('should return null for missing key', () => {
|
|
25
|
-
expect(get('nonexistent')).toBeNull();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('should return null for expired entry', async () => {
|
|
29
|
-
set('test', 'value', 10); // Very short TTL
|
|
30
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
31
|
-
expect(get('test')).toBeNull();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('should return value for valid cache entry', () => {
|
|
35
|
-
set('test', 'value', 5000);
|
|
36
|
-
expect(get('test')).toBe('value');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('should handle different value types', () => {
|
|
40
|
-
set('string', 'hello');
|
|
41
|
-
set('number', 42);
|
|
42
|
-
set('boolean', true);
|
|
43
|
-
set('object', { key: 'value' });
|
|
44
|
-
set('array', [1, 2, 3]);
|
|
45
|
-
|
|
46
|
-
expect(get('string')).toBe('hello');
|
|
47
|
-
expect(get('number')).toBe(42);
|
|
48
|
-
expect(get('boolean')).toBe(true);
|
|
49
|
-
expect(get('object')).toEqual({ key: 'value' });
|
|
50
|
-
expect(get('array')).toEqual([1, 2, 3]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('should handle null values', () => {
|
|
54
|
-
set('null', null, 5000);
|
|
55
|
-
expect(get('null')).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('should handle undefined values', () => {
|
|
59
|
-
set('undefined', undefined, 5000);
|
|
60
|
-
expect(get('undefined')).toBeUndefined();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('set', () => {
|
|
65
|
-
test('should store value with TTL', () => {
|
|
66
|
-
set('key', 'value', 1000);
|
|
67
|
-
expect(get('key')).toBe('value');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('should use default TTL from config', () => {
|
|
71
|
-
set('key', 'value'); // No TTL specified
|
|
72
|
-
expect(get('key')).toBe('value');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('should overwrite existing entry', () => {
|
|
76
|
-
set('key', 'old');
|
|
77
|
-
set('key', 'new');
|
|
78
|
-
expect(get('key')).toBe('new');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('getOrFetch', () => {
|
|
83
|
-
test('should return cached value if available', async () => {
|
|
84
|
-
set('key', 'cached', 5000);
|
|
85
|
-
const mockFetcher = () => Promise.resolve('should-not-be-called');
|
|
86
|
-
const result = await getOrFetch('key', mockFetcher);
|
|
87
|
-
expect(result).toBe('cached');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('should fetch data on cache miss', async () => {
|
|
91
|
-
const mockFetcher = () => Promise.resolve('fresh');
|
|
92
|
-
const result = await getOrFetch('key', mockFetcher);
|
|
93
|
-
|
|
94
|
-
expect(result).toBe('fresh');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test('should cache fetched data', async () => {
|
|
98
|
-
let callCount = 0;
|
|
99
|
-
const mockFetcher = () => {
|
|
100
|
-
callCount++;
|
|
101
|
-
return Promise.resolve('fresh');
|
|
102
|
-
};
|
|
103
|
-
await getOrFetch('key', mockFetcher);
|
|
104
|
-
|
|
105
|
-
// Second call should use cache
|
|
106
|
-
const result = await getOrFetch('key', mockFetcher);
|
|
107
|
-
expect(result).toBe('fresh');
|
|
108
|
-
expect(callCount).toBe(1);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('should use custom TTL when provided', async () => {
|
|
112
|
-
let callCount = 0;
|
|
113
|
-
const mockFetcher = () => {
|
|
114
|
-
callCount++;
|
|
115
|
-
return Promise.resolve('value');
|
|
116
|
-
};
|
|
117
|
-
await getOrFetch('key', mockFetcher, 50);
|
|
118
|
-
|
|
119
|
-
// Wait for TTL to expire
|
|
120
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
121
|
-
|
|
122
|
-
// Fetcher should be called again
|
|
123
|
-
const result = await getOrFetch('key', mockFetcher);
|
|
124
|
-
|
|
125
|
-
expect(result).toBe('value');
|
|
126
|
-
expect(callCount).toBe(2);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('should handle async fetcher errors', async () => {
|
|
130
|
-
const mockFetcher = () => Promise.reject(new Error('fetch failed'));
|
|
131
|
-
|
|
132
|
-
await expect(getOrFetch('key', mockFetcher)).rejects.toThrow('fetch failed');
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('invalidate', () => {
|
|
137
|
-
test('should remove specific cache entry', () => {
|
|
138
|
-
set('key1', 'value1');
|
|
139
|
-
set('key2', 'value2');
|
|
140
|
-
|
|
141
|
-
invalidate('key1');
|
|
142
|
-
|
|
143
|
-
expect(get('key1')).toBeNull();
|
|
144
|
-
expect(get('key2')).toBe('value2');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('should handle invalidating non-existent key', () => {
|
|
148
|
-
expect(() => invalidate('nonexistent')).not.toThrow();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe('clear', () => {
|
|
153
|
-
test('should remove all cache entries', () => {
|
|
154
|
-
set('key1', 'value1');
|
|
155
|
-
set('key2', 'value2');
|
|
156
|
-
|
|
157
|
-
clear();
|
|
158
|
-
|
|
159
|
-
expect(get('key1')).toBeNull();
|
|
160
|
-
expect(get('key2')).toBeNull();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('should work on empty cache', () => {
|
|
164
|
-
expect(() => clear()).not.toThrow();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('getStatus', () => {
|
|
169
|
-
test('should return empty object for empty cache', () => {
|
|
170
|
-
const status = getStatus();
|
|
171
|
-
expect(status).toEqual({});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('should return status for cached entries', () => {
|
|
175
|
-
set('key', 'value', 5000);
|
|
176
|
-
|
|
177
|
-
const status = getStatus();
|
|
178
|
-
|
|
179
|
-
expect(status.key).toBeDefined();
|
|
180
|
-
expect(status.key.cached).toBe(true);
|
|
181
|
-
expect(status.key.age).toBeDefined();
|
|
182
|
-
expect(status.key.ttlRemaining).toBeDefined();
|
|
183
|
-
expect(status.key.configTtl).toBeDefined();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('should show remaining TTL', async () => {
|
|
187
|
-
set('key', 'value', 200);
|
|
188
|
-
|
|
189
|
-
const statusBefore = getStatus();
|
|
190
|
-
expect(statusBefore.key.ttlRemaining).toBeGreaterThan(100);
|
|
191
|
-
|
|
192
|
-
await new Promise(resolve => setTimeout(resolve, 150));
|
|
193
|
-
|
|
194
|
-
const statusAfter = getStatus();
|
|
195
|
-
expect(statusAfter.key.ttlRemaining).toBeLessThan(100);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe('debounce', () => {
|
|
201
|
-
test('should delay function execution', async () => {
|
|
202
|
-
let called = false;
|
|
203
|
-
const fn = () => { called = true; };
|
|
204
|
-
const debounced = debounce(fn, 50);
|
|
205
|
-
|
|
206
|
-
debounced();
|
|
207
|
-
|
|
208
|
-
expect(called).toBe(false);
|
|
209
|
-
|
|
210
|
-
await new Promise(resolve => setTimeout(resolve, 60));
|
|
211
|
-
|
|
212
|
-
expect(called).toBe(true);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test('should only call function once for rapid calls', async () => {
|
|
216
|
-
let callCount = 0;
|
|
217
|
-
const fn = () => { callCount++; };
|
|
218
|
-
const debounced = debounce(fn, 50);
|
|
219
|
-
|
|
220
|
-
debounced();
|
|
221
|
-
debounced();
|
|
222
|
-
debounced();
|
|
223
|
-
|
|
224
|
-
await new Promise(resolve => setTimeout(resolve, 60));
|
|
225
|
-
|
|
226
|
-
expect(callCount).toBe(1);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test('should pass arguments to debounced function', async () => {
|
|
230
|
-
let receivedArgs = null;
|
|
231
|
-
const fn = (...args) => { receivedArgs = args; };
|
|
232
|
-
const debounced = debounce(fn, 50);
|
|
233
|
-
|
|
234
|
-
debounced('arg1', 'arg2');
|
|
235
|
-
|
|
236
|
-
await new Promise(resolve => setTimeout(resolve, 60));
|
|
237
|
-
|
|
238
|
-
expect(receivedArgs).toEqual(['arg1', 'arg2']);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test('should reset delay on each call', async () => {
|
|
242
|
-
let callCount = 0;
|
|
243
|
-
const fn = () => { callCount++; };
|
|
244
|
-
const debounced = debounce(fn, 50);
|
|
245
|
-
|
|
246
|
-
debounced();
|
|
247
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
248
|
-
debounced();
|
|
249
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
250
|
-
|
|
251
|
-
expect(callCount).toBe(0);
|
|
252
|
-
|
|
253
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
254
|
-
|
|
255
|
-
expect(callCount).toBe(1);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe('throttle', () => {
|
|
260
|
-
test('should execute function immediately on first call', () => {
|
|
261
|
-
let called = false;
|
|
262
|
-
const fn = () => { called = true; };
|
|
263
|
-
const throttled = throttle(fn, 100);
|
|
264
|
-
|
|
265
|
-
throttled();
|
|
266
|
-
|
|
267
|
-
expect(called).toBe(true);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test('should not execute again within limit period', async () => {
|
|
271
|
-
let callCount = 0;
|
|
272
|
-
const fn = () => { callCount++; };
|
|
273
|
-
const throttled = throttle(fn, 100);
|
|
274
|
-
|
|
275
|
-
throttled();
|
|
276
|
-
throttled();
|
|
277
|
-
throttled();
|
|
278
|
-
|
|
279
|
-
expect(callCount).toBe(1);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test('should execute again after limit period', async () => {
|
|
283
|
-
let callCount = 0;
|
|
284
|
-
const fn = () => { callCount++; };
|
|
285
|
-
const throttled = throttle(fn, 50);
|
|
286
|
-
|
|
287
|
-
throttled();
|
|
288
|
-
await new Promise(resolve => setTimeout(resolve, 60));
|
|
289
|
-
throttled();
|
|
290
|
-
|
|
291
|
-
expect(callCount).toBe(2);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test('should pass arguments to throttled function', () => {
|
|
295
|
-
let receivedArgs = null;
|
|
296
|
-
const fn = (...args) => { receivedArgs = args; };
|
|
297
|
-
const throttled = throttle(fn, 100);
|
|
298
|
-
|
|
299
|
-
throttled('arg1', 'arg2');
|
|
300
|
-
|
|
301
|
-
expect(receivedArgs).toEqual(['arg1', 'arg2']);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test('should execute throttled call after limit period expires', async () => {
|
|
305
|
-
let callCount = 0;
|
|
306
|
-
const fn = () => { callCount++; };
|
|
307
|
-
const throttled = throttle(fn, 50);
|
|
308
|
-
|
|
309
|
-
throttled(); // executed immediately
|
|
310
|
-
throttled(); // within limit - ignored
|
|
311
|
-
|
|
312
|
-
await new Promise(resolve => setTimeout(resolve, 60));
|
|
313
|
-
|
|
314
|
-
// After limit period expires, throttled function executes again
|
|
315
|
-
expect(callCount).toBe(2);
|
|
316
|
-
});
|
|
317
|
-
});
|
package/tests/cli.test.js
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Module Tests
|
|
3
|
-
* Tests for CLI argument parsing, help output, and command handling
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { jest } from '@jest/globals';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import { dirname, join } from 'path';
|
|
9
|
-
import { mkdtempSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
|
10
|
-
import { tmpdir } from 'os';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = dirname(__filename);
|
|
14
|
-
|
|
15
|
-
// Store original argv and env
|
|
16
|
-
const originalArgv = process.argv;
|
|
17
|
-
const originalEnv = process.env;
|
|
18
|
-
|
|
19
|
-
describe('CLI Module Tests', () => {
|
|
20
|
-
let tempDir;
|
|
21
|
-
|
|
22
|
-
beforeEach(async () => {
|
|
23
|
-
// Reset modules before each test
|
|
24
|
-
jest.resetModules();
|
|
25
|
-
|
|
26
|
-
// Create temp directory for file-based tests
|
|
27
|
-
tempDir = mkdtempSync(join(tmpdir(), 'claw-cli-test-'));
|
|
28
|
-
|
|
29
|
-
// Reset process.argv to default
|
|
30
|
-
process.argv = ['node', 'clawdash'];
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
// Restore original values
|
|
35
|
-
process.argv = originalArgv;
|
|
36
|
-
process.env = originalEnv;
|
|
37
|
-
|
|
38
|
-
// Clean up temp directory
|
|
39
|
-
if (existsSync(tempDir)) {
|
|
40
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Clear module cache
|
|
44
|
-
jest.clearAllMocks();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('Argument Parsing', () => {
|
|
48
|
-
it('should parse default options when no arguments provided', async () => {
|
|
49
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
50
|
-
|
|
51
|
-
const options = parseCliArgs();
|
|
52
|
-
|
|
53
|
-
expect(options.help).toBe(false);
|
|
54
|
-
expect(options.version).toBe(false);
|
|
55
|
-
expect(options.debug).toBe(false);
|
|
56
|
-
expect(options.web).toBe(false);
|
|
57
|
-
expect(options.watch).toBe(false);
|
|
58
|
-
expect(options.command).toBeNull();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should parse --help flag', async () => {
|
|
62
|
-
process.argv = ['node', 'clawdash', '--help'];
|
|
63
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
64
|
-
|
|
65
|
-
const options = parseCliArgs();
|
|
66
|
-
|
|
67
|
-
expect(options.help).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should parse -h shorthand', async () => {
|
|
71
|
-
process.argv = ['node', 'clawdash', '-h'];
|
|
72
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
73
|
-
|
|
74
|
-
const options = parseCliArgs();
|
|
75
|
-
|
|
76
|
-
expect(options.help).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should parse --version flag', async () => {
|
|
80
|
-
process.argv = ['node', 'clawdash', '--version'];
|
|
81
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
82
|
-
|
|
83
|
-
const options = parseCliArgs();
|
|
84
|
-
|
|
85
|
-
expect(options.version).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should parse -v shorthand', async () => {
|
|
89
|
-
process.argv = ['node', 'clawdash', '-v'];
|
|
90
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
91
|
-
|
|
92
|
-
const options = parseCliArgs();
|
|
93
|
-
|
|
94
|
-
expect(options.version).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should parse --debug flag', async () => {
|
|
98
|
-
process.argv = ['node', 'clawdash', '--debug'];
|
|
99
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
100
|
-
|
|
101
|
-
const options = parseCliArgs();
|
|
102
|
-
|
|
103
|
-
expect(options.debug).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should parse -d shorthand', async () => {
|
|
107
|
-
process.argv = ['node', 'clawdash', '-d'];
|
|
108
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
109
|
-
|
|
110
|
-
const options = parseCliArgs();
|
|
111
|
-
|
|
112
|
-
expect(options.debug).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should parse --web flag', async () => {
|
|
116
|
-
process.argv = ['node', 'clawdash', '--web'];
|
|
117
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
118
|
-
|
|
119
|
-
const options = parseCliArgs();
|
|
120
|
-
|
|
121
|
-
expect(options.web).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should parse -w shorthand', async () => {
|
|
125
|
-
process.argv = ['node', 'clawdash', '-w'];
|
|
126
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
127
|
-
|
|
128
|
-
const options = parseCliArgs();
|
|
129
|
-
|
|
130
|
-
expect(options.web).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should parse --web-port with valid port', async () => {
|
|
134
|
-
process.argv = ['node', 'clawdash', '--web-port', '8080'];
|
|
135
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
136
|
-
|
|
137
|
-
const options = parseCliArgs();
|
|
138
|
-
|
|
139
|
-
expect(options.web).toBe(true);
|
|
140
|
-
expect(options.webPort).toBe(8080);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should parse -p shorthand with valid port', async () => {
|
|
144
|
-
process.argv = ['node', 'clawdash', '-p', '9000'];
|
|
145
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
146
|
-
|
|
147
|
-
const options = parseCliArgs();
|
|
148
|
-
|
|
149
|
-
expect(options.web).toBe(true);
|
|
150
|
-
expect(options.webPort).toBe(9000);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should ignore invalid port numbers', async () => {
|
|
154
|
-
process.argv = ['node', 'clawdash', '--web-port', 'invalid'];
|
|
155
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
156
|
-
|
|
157
|
-
const options = parseCliArgs();
|
|
158
|
-
|
|
159
|
-
expect(options.web).toBe(true);
|
|
160
|
-
// Should use default port
|
|
161
|
-
expect(options.webPort).toBeGreaterThan(0);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should ignore out-of-range port numbers', async () => {
|
|
165
|
-
process.argv = ['node', 'clawdash', '--web-port', '99999'];
|
|
166
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
167
|
-
|
|
168
|
-
const options = parseCliArgs();
|
|
169
|
-
|
|
170
|
-
expect(options.web).toBe(true);
|
|
171
|
-
// Should use default port
|
|
172
|
-
expect(options.webPort).toBeGreaterThan(0);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('should parse --web-host', async () => {
|
|
176
|
-
process.argv = ['node', 'clawdash', '--web-host', '0.0.0.0'];
|
|
177
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
178
|
-
|
|
179
|
-
const options = parseCliArgs();
|
|
180
|
-
|
|
181
|
-
expect(options.web).toBe(true);
|
|
182
|
-
expect(options.webHost).toBe('0.0.0.0');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('should parse --watch flag', async () => {
|
|
186
|
-
process.argv = ['node', 'clawdash', '--watch'];
|
|
187
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
188
|
-
|
|
189
|
-
const options = parseCliArgs();
|
|
190
|
-
|
|
191
|
-
expect(options.watch).toBe(true);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('should parse -W shorthand', async () => {
|
|
195
|
-
process.argv = ['node', 'clawdash', '-W'];
|
|
196
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
197
|
-
|
|
198
|
-
const options = parseCliArgs();
|
|
199
|
-
|
|
200
|
-
expect(options.watch).toBe(true);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('should parse --watch-plugins flag', async () => {
|
|
204
|
-
process.argv = ['node', 'clawdash', '--watch-plugins'];
|
|
205
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
206
|
-
|
|
207
|
-
const options = parseCliArgs();
|
|
208
|
-
|
|
209
|
-
expect(options.watchPlugins).toBe(true);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should parse multiple flags', async () => {
|
|
213
|
-
process.argv = ['node', 'clawdash', '--debug', '--web', '--watch'];
|
|
214
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
215
|
-
|
|
216
|
-
const options = parseCliArgs();
|
|
217
|
-
|
|
218
|
-
expect(options.debug).toBe(true);
|
|
219
|
-
expect(options.web).toBe(true);
|
|
220
|
-
expect(options.watch).toBe(true);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should parse create-plugin command', async () => {
|
|
224
|
-
process.argv = ['node', 'clawdash', 'create-plugin', 'my-widget'];
|
|
225
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
226
|
-
|
|
227
|
-
const options = parseCliArgs();
|
|
228
|
-
|
|
229
|
-
expect(options.command).toBe('create-plugin');
|
|
230
|
-
expect(options.commandArgs).toEqual(['my-widget']);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should parse validate-plugin command', async () => {
|
|
234
|
-
process.argv = ['node', 'clawdash', 'validate-plugin', './my-plugin'];
|
|
235
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
236
|
-
|
|
237
|
-
const options = parseCliArgs();
|
|
238
|
-
|
|
239
|
-
expect(options.command).toBe('validate-plugin');
|
|
240
|
-
expect(options.commandArgs).toEqual(['./my-plugin']);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('should parse validate-config command', async () => {
|
|
244
|
-
process.argv = ['node', 'clawdash', 'validate-config', './config.json'];
|
|
245
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
246
|
-
|
|
247
|
-
const options = parseCliArgs();
|
|
248
|
-
|
|
249
|
-
expect(options.command).toBe('validate-config');
|
|
250
|
-
expect(options.commandArgs).toEqual(['./config.json']);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('should handle command with multiple args', async () => {
|
|
254
|
-
process.argv = ['node', 'clawdash', 'create-plugin', 'my-widget', '--template', 'basic'];
|
|
255
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
256
|
-
|
|
257
|
-
const options = parseCliArgs();
|
|
258
|
-
|
|
259
|
-
expect(options.command).toBe('create-plugin');
|
|
260
|
-
expect(options.commandArgs).toEqual(['my-widget', '--template', 'basic']);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should handle unknown flags gracefully', async () => {
|
|
264
|
-
process.argv = ['node', 'clawdash', '--unknown-flag'];
|
|
265
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
266
|
-
|
|
267
|
-
// Should not throw
|
|
268
|
-
const options = parseCliArgs();
|
|
269
|
-
|
|
270
|
-
expect(options).toBeDefined();
|
|
271
|
-
expect(options.help).toBe(false);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('should handle empty arguments', async () => {
|
|
275
|
-
process.argv = ['node', 'clawdash'];
|
|
276
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
277
|
-
|
|
278
|
-
const options = parseCliArgs();
|
|
279
|
-
|
|
280
|
-
expect(options.command).toBeNull();
|
|
281
|
-
expect(options.help).toBe(false);
|
|
282
|
-
expect(options.version).toBe(false);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('should handle flags after command', async () => {
|
|
286
|
-
process.argv = ['node', 'clawdash', 'validate-plugin', './plugin', '--verbose'];
|
|
287
|
-
const { parseCliArgs } = await import('../src/cli/args.js');
|
|
288
|
-
|
|
289
|
-
const options = parseCliArgs();
|
|
290
|
-
|
|
291
|
-
// Command parsing should stop after command is detected
|
|
292
|
-
expect(options.command).toBe('validate-plugin');
|
|
293
|
-
expect(options.commandArgs).toEqual(['./plugin', '--verbose']);
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
describe('Help Module', () => {
|
|
298
|
-
it('should export showHelp function', async () => {
|
|
299
|
-
const { showHelp } = await import('../src/cli/help.js');
|
|
300
|
-
|
|
301
|
-
expect(showHelp).toBeDefined();
|
|
302
|
-
expect(typeof showHelp).toBe('function');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('should have default export', async () => {
|
|
306
|
-
const helpModule = await import('../src/cli/help.js');
|
|
307
|
-
|
|
308
|
-
expect(helpModule.default).toBeDefined();
|
|
309
|
-
expect(helpModule.default.showHelp).toBeDefined();
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
describe('Version Module', () => {
|
|
314
|
-
it('should export showVersion function', async () => {
|
|
315
|
-
const { showVersion } = await import('../src/cli/version.js');
|
|
316
|
-
|
|
317
|
-
expect(showVersion).toBeDefined();
|
|
318
|
-
expect(typeof showVersion).toBe('function');
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should have default export', async () => {
|
|
322
|
-
const versionModule = await import('../src/cli/version.js');
|
|
323
|
-
|
|
324
|
-
expect(versionModule.default).toBeDefined();
|
|
325
|
-
expect(versionModule.default.showVersion).toBeDefined();
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
describe('Plugin Scaffolding', () => {
|
|
330
|
-
it('should export createPlugin function', async () => {
|
|
331
|
-
const { createPlugin } = await import('../src/plugin-scaffold.js');
|
|
332
|
-
|
|
333
|
-
expect(createPlugin).toBeDefined();
|
|
334
|
-
expect(typeof createPlugin).toBe('function');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it('should export runScaffoldCli function', async () => {
|
|
338
|
-
const { runScaffoldCli } = await import('../src/plugin-scaffold.js');
|
|
339
|
-
|
|
340
|
-
expect(runScaffoldCli).toBeDefined();
|
|
341
|
-
expect(typeof runScaffoldCli).toBe('function');
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('should export named functions', async () => {
|
|
345
|
-
const scaffoldModule = await import('../src/plugin-scaffold.js');
|
|
346
|
-
|
|
347
|
-
expect(scaffoldModule.createPlugin).toBeDefined();
|
|
348
|
-
expect(scaffoldModule.runScaffoldCli).toBeDefined();
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
});
|