workon 1.4.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/.history/package_20250807114243.json +43 -0
- package/.history/package_20250807114257.json +43 -0
- package/.history/package_20250807114404.json +43 -0
- package/.history/package_20250807114409.json +43 -0
- package/.history/package_20250807114510.json +43 -0
- package/.history/package_20250807114637.json +43 -0
- package/CHANGELOG.md +15 -1
- package/cli/manage.js +29 -166
- package/cli/open.js +48 -103
- package/commands/base.js +105 -0
- package/commands/core/cwd/index.js +86 -0
- package/commands/core/ide/index.js +84 -0
- package/commands/core/web/index.js +109 -0
- package/commands/extensions/claude/index.js +211 -0
- package/commands/extensions/docker/index.js +167 -0
- package/commands/extensions/npm/index.js +208 -0
- package/commands/registry.js +196 -0
- package/docs/adr/001-command-centric-architecture.md +304 -0
- package/docs/adr/002-positional-command-arguments.md +396 -0
- package/lib/validation.js +14 -68
- package/package.json +2 -2
- package/test-architecture.js +145 -0
- package/test-registry.js +57 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# ADR-002: Positional Command Arguments
|
|
2
|
+
|
|
3
|
+
**Status:** Proposed
|
|
4
|
+
**Date:** 2025-08-06
|
|
5
|
+
**Deciders:** Israel Roldan
|
|
6
|
+
**Related:** ADR-001 (Command-Centric Architecture)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Currently, the workon CLI operates with a "all-or-nothing" approach where running `workon my-project` executes all configured events for that project. However, there are scenarios where users want to execute only specific commands for a project.
|
|
11
|
+
|
|
12
|
+
### Current Behavior
|
|
13
|
+
```bash
|
|
14
|
+
workon my-project # Executes ALL configured events (cwd, ide, claude, npm, etc.)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Desired Behavior
|
|
18
|
+
```bash
|
|
19
|
+
workon my-project # Executes all configured events (current behavior)
|
|
20
|
+
workon my-project cwd # Only changes to project directory
|
|
21
|
+
workon my-project claude # Only opens Claude in project directory
|
|
22
|
+
workon my-project npm # Only runs npm command
|
|
23
|
+
workon my-project cwd claude # Runs cwd + claude (split terminal)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Problems with Current Approach
|
|
27
|
+
|
|
28
|
+
1. **Inflexibility**: Can't run individual commands without modifying project configuration
|
|
29
|
+
2. **Performance**: Sometimes you don't want to start dev server, just open Claude
|
|
30
|
+
3. **Workflow Mismatch**: Different workflows need different command combinations
|
|
31
|
+
4. **Resource Usage**: Starting all services when you only need one is wasteful
|
|
32
|
+
|
|
33
|
+
### Common Use Cases
|
|
34
|
+
|
|
35
|
+
- **Quick directory change**: `workon my-project cwd`
|
|
36
|
+
- **AI assistance only**: `workon my-project claude`
|
|
37
|
+
- **Development startup**: `workon my-project cwd claude npm`
|
|
38
|
+
- **IDE-only launch**: `workon my-project ide`
|
|
39
|
+
- **Terminal + build**: `workon my-project cwd npm`
|
|
40
|
+
|
|
41
|
+
## Decision
|
|
42
|
+
|
|
43
|
+
Implement **positional command arguments** that allow users to specify which commands to execute for a project, while maintaining backward compatibility with the current "execute all" behavior.
|
|
44
|
+
|
|
45
|
+
## Proposed Implementation
|
|
46
|
+
|
|
47
|
+
### 1. CLI Argument Structure
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
workon <project> [command1] [command2] [...commandN] [options]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Examples:**
|
|
54
|
+
```bash
|
|
55
|
+
workon my-project # All configured events
|
|
56
|
+
workon my-project cwd # Just directory change
|
|
57
|
+
workon my-project claude # Just Claude
|
|
58
|
+
workon my-project cwd claude # Directory + Claude
|
|
59
|
+
workon my-project cwd claude npm # Full development setup
|
|
60
|
+
workon my-project ide --shell # IDE with shell output
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Argument Parsing Logic
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// Enhanced argument parsing
|
|
67
|
+
class ProjectArgumentParser {
|
|
68
|
+
static parse(args) {
|
|
69
|
+
const projectName = args[0];
|
|
70
|
+
const commands = args.slice(1).filter(arg => !arg.startsWith('--'));
|
|
71
|
+
const flags = args.slice(1).filter(arg => arg.startsWith('--'));
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
projectName,
|
|
75
|
+
commands: commands.length > 0 ? commands : null, // null means "all configured"
|
|
76
|
+
flags
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static validate(projectName, commands, projectConfig) {
|
|
81
|
+
// Validate that requested commands are configured for the project
|
|
82
|
+
const configuredEvents = Object.keys(projectConfig.events || {});
|
|
83
|
+
const invalidCommands = commands.filter(cmd => !configuredEvents.includes(cmd));
|
|
84
|
+
|
|
85
|
+
if (invalidCommands.length > 0) {
|
|
86
|
+
throw new Error(`Commands not configured for project '${projectName}': ${invalidCommands.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. Execution Logic
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// cli/open.js - Enhanced execution
|
|
96
|
+
class open extends command {
|
|
97
|
+
async processProject(project, requestedCommands = null) {
|
|
98
|
+
const projectConfig = this.getProjectConfig(project);
|
|
99
|
+
const configuredEvents = Object.keys(projectConfig.events || {});
|
|
100
|
+
|
|
101
|
+
// Determine which events to execute
|
|
102
|
+
let eventsToExecute;
|
|
103
|
+
if (requestedCommands) {
|
|
104
|
+
// Validate requested commands are configured
|
|
105
|
+
this.validateRequestedCommands(requestedCommands, configuredEvents, project);
|
|
106
|
+
eventsToExecute = requestedCommands;
|
|
107
|
+
} else {
|
|
108
|
+
// Execute all configured events (current behavior)
|
|
109
|
+
eventsToExecute = configuredEvents.filter(e => projectConfig.events[e]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await this.executeEvents(projectConfig, eventsToExecute);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
validateRequestedCommands(requested, configured, projectName) {
|
|
116
|
+
const invalid = requested.filter(cmd => !configured.includes(cmd));
|
|
117
|
+
if (invalid.length > 0) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Commands not configured for project '${projectName}': ${invalid.join(', ')}\n` +
|
|
120
|
+
`Available commands: ${configured.join(', ')}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 4. Smart Layout Detection
|
|
128
|
+
|
|
129
|
+
The intelligent layout detection from ADR-001 would work with positional arguments:
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// Layout detection based on actual commands being executed
|
|
133
|
+
const layoutDetection = {
|
|
134
|
+
determineLayout(executedCommands, projectConfig) {
|
|
135
|
+
const hasCwd = executedCommands.includes('cwd');
|
|
136
|
+
const hasClaude = executedCommands.includes('claude');
|
|
137
|
+
const hasNpm = executedCommands.includes('npm');
|
|
138
|
+
|
|
139
|
+
if (hasCwd && hasClaude && hasNpm) {
|
|
140
|
+
return 'three-pane'; // Claude + Terminal + NPM
|
|
141
|
+
} else if (hasCwd && hasNpm) {
|
|
142
|
+
return 'two-pane-npm'; // Terminal + NPM
|
|
143
|
+
} else if (hasCwd && hasClaude) {
|
|
144
|
+
return 'two-pane-claude'; // Claude + Terminal
|
|
145
|
+
} else {
|
|
146
|
+
return 'individual'; // Execute commands individually
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 5. Help and Discovery
|
|
153
|
+
|
|
154
|
+
Enhanced help system that shows available commands per project:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
workon my-project --help
|
|
158
|
+
# Output:
|
|
159
|
+
# Available commands for 'my-project':
|
|
160
|
+
# cwd - Change to project directory
|
|
161
|
+
# claude - Open Claude Code with --resume flag
|
|
162
|
+
# npm - Run 'npm run dev'
|
|
163
|
+
# ide - Open in VS Code
|
|
164
|
+
#
|
|
165
|
+
# Usage:
|
|
166
|
+
# workon my-project [command1] [command2] ...
|
|
167
|
+
#
|
|
168
|
+
# Examples:
|
|
169
|
+
# workon my-project cwd claude # Split terminal with Claude
|
|
170
|
+
# workon my-project npm # Just start dev server
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Integration with ADR-001
|
|
174
|
+
|
|
175
|
+
This feature complements the Command-Centric Architecture:
|
|
176
|
+
|
|
177
|
+
### Command Interface Extension
|
|
178
|
+
```javascript
|
|
179
|
+
class NPMCommand {
|
|
180
|
+
static metadata = {
|
|
181
|
+
name: 'npm',
|
|
182
|
+
displayName: 'Run NPM command',
|
|
183
|
+
canRunIndividually: true, // Can be executed alone
|
|
184
|
+
requiresProject: true, // Needs project context
|
|
185
|
+
dependencies: ['npm']
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static help = {
|
|
189
|
+
shortDescription: 'Run npm scripts',
|
|
190
|
+
individualUsage: 'workon <project> npm',
|
|
191
|
+
examples: [
|
|
192
|
+
'workon my-app npm # Run configured npm script',
|
|
193
|
+
'workon my-app cwd npm # Terminal + npm in split'
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Enhanced Command Registry
|
|
200
|
+
```javascript
|
|
201
|
+
class CommandRegistry {
|
|
202
|
+
static getAvailableCommandsForProject(projectConfig) {
|
|
203
|
+
const configuredEvents = Object.keys(projectConfig.events || {});
|
|
204
|
+
return configuredEvents.map(eventName => {
|
|
205
|
+
const command = this.getCommandByName(eventName);
|
|
206
|
+
return {
|
|
207
|
+
name: eventName,
|
|
208
|
+
description: command.metadata.displayName,
|
|
209
|
+
canRunIndividually: command.metadata.canRunIndividually
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Implementation Challenges
|
|
217
|
+
|
|
218
|
+
### 1. Argument Parsing Complexity
|
|
219
|
+
**Challenge**: Distinguishing between project names and commands
|
|
220
|
+
```bash
|
|
221
|
+
workon claude my-project # Project named 'claude' or command 'claude' on 'my-project'?
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Solution**: Commands are always positional after project name:
|
|
225
|
+
```bash
|
|
226
|
+
workon my-project claude # โ
Clear: project 'my-project', command 'claude'
|
|
227
|
+
workon claude # โ
Clear: project 'claude', no specific commands
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 2. Command Validation
|
|
231
|
+
**Challenge**: What if user requests a command that's not configured?
|
|
232
|
+
|
|
233
|
+
**Options:**
|
|
234
|
+
- **Strict**: Error if command not configured
|
|
235
|
+
- **Permissive**: Execute if command exists, ignore configuration
|
|
236
|
+
- **Hybrid**: Warn but execute if possible
|
|
237
|
+
|
|
238
|
+
**Recommendation**: Start strict, add permissive mode with flag later.
|
|
239
|
+
|
|
240
|
+
### 3. Backward Compatibility
|
|
241
|
+
**Challenge**: Ensure existing usage patterns continue to work
|
|
242
|
+
|
|
243
|
+
**Solution**: No commands specified = execute all (current behavior)
|
|
244
|
+
```bash
|
|
245
|
+
workon my-project # Still works as before
|
|
246
|
+
workon my-project --shell # Still works as before
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 4. Complex Command Interactions
|
|
250
|
+
**Challenge**: Some commands have dependencies or conflicts
|
|
251
|
+
|
|
252
|
+
**Examples:**
|
|
253
|
+
- `claude` without `cwd` - should we auto-add `cwd`?
|
|
254
|
+
- `npm` without `cwd` - doesn't make sense
|
|
255
|
+
|
|
256
|
+
**Solution**: Command dependency resolution:
|
|
257
|
+
```javascript
|
|
258
|
+
class CommandDependencyResolver {
|
|
259
|
+
static resolve(requestedCommands, projectConfig) {
|
|
260
|
+
const resolved = [...requestedCommands];
|
|
261
|
+
|
|
262
|
+
// Auto-add dependencies
|
|
263
|
+
if (requestedCommands.includes('npm') && !requestedCommands.includes('cwd')) {
|
|
264
|
+
resolved.unshift('cwd'); // npm needs cwd
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (requestedCommands.includes('claude') && !requestedCommands.includes('cwd')) {
|
|
268
|
+
resolved.unshift('cwd'); // claude needs cwd
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return resolved;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## User Experience Design
|
|
277
|
+
|
|
278
|
+
### 1. Intuitive Command Discovery
|
|
279
|
+
```bash
|
|
280
|
+
workon my-project help # Show available commands for this project
|
|
281
|
+
workon help commands # Show all available command types
|
|
282
|
+
workon --list-projects # Show all projects with their commands
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 2. Smart Defaults
|
|
286
|
+
```bash
|
|
287
|
+
workon my-project claude # Automatically includes 'cwd' dependency
|
|
288
|
+
workon my-project npm # Automatically includes 'cwd' dependency
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 3. Error Messages
|
|
292
|
+
```bash
|
|
293
|
+
workon my-project invalid-cmd
|
|
294
|
+
# Error: Command 'invalid-cmd' not configured for project 'my-project'
|
|
295
|
+
# Available commands: cwd, claude, npm, ide
|
|
296
|
+
#
|
|
297
|
+
# Tip: Run 'workon my-project help' for more details
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Implementation Plan
|
|
301
|
+
|
|
302
|
+
### Phase 1: Argument Parsing Foundation
|
|
303
|
+
1. Implement `ProjectArgumentParser` class
|
|
304
|
+
2. Update CLI entry point to handle positional arguments
|
|
305
|
+
3. Add backward compatibility tests
|
|
306
|
+
4. Basic command validation
|
|
307
|
+
|
|
308
|
+
### Phase 2: Command Execution Logic
|
|
309
|
+
1. Update `open.js` to handle selective command execution
|
|
310
|
+
2. Implement dependency resolution system
|
|
311
|
+
3. Add error handling and user-friendly messages
|
|
312
|
+
4. Update help system
|
|
313
|
+
|
|
314
|
+
### Phase 3: Layout Integration
|
|
315
|
+
1. Update smart layout detection for positional commands
|
|
316
|
+
2. Ensure tmux layouts work with partial command sets
|
|
317
|
+
3. Add fallback behavior for unsupported combinations
|
|
318
|
+
|
|
319
|
+
### Phase 4: Enhanced UX
|
|
320
|
+
1. Add command discovery helpers
|
|
321
|
+
2. Implement auto-completion support
|
|
322
|
+
3. Add command validation with helpful suggestions
|
|
323
|
+
4. Enhanced help and documentation
|
|
324
|
+
|
|
325
|
+
## Success Criteria
|
|
326
|
+
|
|
327
|
+
1. **Backward Compatibility**: `workon my-project` works exactly as before
|
|
328
|
+
2. **Individual Commands**: `workon my-project cwd` only changes directory
|
|
329
|
+
3. **Command Combinations**: `workon my-project cwd claude` creates split terminal
|
|
330
|
+
4. **Error Handling**: Clear messages for invalid command combinations
|
|
331
|
+
5. **Help System**: Users can discover available commands per project
|
|
332
|
+
6. **Performance**: Single commands execute faster than full project setup
|
|
333
|
+
|
|
334
|
+
## Examples
|
|
335
|
+
|
|
336
|
+
### Basic Usage
|
|
337
|
+
```bash
|
|
338
|
+
# Current behavior preserved
|
|
339
|
+
workon my-app # All configured events
|
|
340
|
+
|
|
341
|
+
# New individual command usage
|
|
342
|
+
workon my-app cwd # Just cd to directory
|
|
343
|
+
workon my-app claude # Just open Claude (with auto cwd)
|
|
344
|
+
workon my-app ide # Just open IDE
|
|
345
|
+
workon my-app npm # Just run npm script (with auto cwd)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Advanced Combinations
|
|
349
|
+
```bash
|
|
350
|
+
# Custom combinations
|
|
351
|
+
workon my-app cwd claude # Split terminal: Claude + shell
|
|
352
|
+
workon my-app cwd npm # Split terminal: shell + npm
|
|
353
|
+
workon my-app cwd claude npm # Three-pane: Claude + shell + npm
|
|
354
|
+
workon my-app ide claude # IDE + Claude (no terminal)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Shell Mode
|
|
358
|
+
```bash
|
|
359
|
+
# Shell mode with positional args
|
|
360
|
+
workon my-app cwd --shell # Just outputs: cd "/path/to/project"
|
|
361
|
+
workon my-app cwd claude --shell # Outputs tmux split commands
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Benefits
|
|
365
|
+
|
|
366
|
+
### For Users
|
|
367
|
+
- **Flexibility**: Run only what you need
|
|
368
|
+
- **Performance**: Faster startup for individual commands
|
|
369
|
+
- **Workflow Optimization**: Match commands to specific use cases
|
|
370
|
+
- **Resource Efficiency**: Don't start unnecessary services
|
|
371
|
+
|
|
372
|
+
### For Development
|
|
373
|
+
- **Testing**: Easier to test individual commands
|
|
374
|
+
- **Debugging**: Isolate issues to specific commands
|
|
375
|
+
- **Modularity**: Commands become more independent
|
|
376
|
+
|
|
377
|
+
### For Future Features
|
|
378
|
+
- **Command Aliases**: `workon my-app dev` โ `workon my-app cwd claude npm`
|
|
379
|
+
- **Profiles**: Save common command combinations
|
|
380
|
+
- **Conditional Logic**: Run commands based on project state
|
|
381
|
+
|
|
382
|
+
## Risks and Mitigations
|
|
383
|
+
|
|
384
|
+
### Risk: User Confusion
|
|
385
|
+
**Mitigation**: Clear documentation, intuitive defaults, helpful error messages
|
|
386
|
+
|
|
387
|
+
### Risk: Complex Dependency Resolution
|
|
388
|
+
**Mitigation**: Start simple, add complexity incrementally, clear logging
|
|
389
|
+
|
|
390
|
+
### Risk: Breaking Changes
|
|
391
|
+
**Mitigation**: Maintain backward compatibility, opt-in behavior
|
|
392
|
+
|
|
393
|
+
### Risk: Command Conflicts
|
|
394
|
+
**Mitigation**: Define clear command interaction rules, validation system
|
|
395
|
+
|
|
396
|
+
This ADR provides a comprehensive plan for adding positional command arguments while maintaining the project's usability and setting the foundation for even more flexible workflow management.
|
package/lib/validation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const File = require('phylo');
|
|
2
2
|
const { spawn } = require('child_process');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const registry = require('../commands/registry');
|
|
4
5
|
|
|
5
6
|
class ProjectValidator {
|
|
6
7
|
constructor(config) {
|
|
@@ -74,90 +75,35 @@ class ProjectValidator {
|
|
|
74
75
|
});
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
validateEvents(events) {
|
|
78
|
+
async validateEvents(events) {
|
|
78
79
|
if (!events || typeof events !== 'object') {
|
|
79
80
|
return 'Events must be an object';
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
// Initialize registry if not already done
|
|
84
|
+
await registry.initialize();
|
|
85
|
+
|
|
86
|
+
const validEvents = registry.getValidEventNames();
|
|
83
87
|
const invalidEvents = Object.keys(events).filter(event => !validEvents.includes(event));
|
|
84
88
|
|
|
85
89
|
if (invalidEvents.length > 0) {
|
|
86
90
|
return `Invalid events: ${invalidEvents.join(', ')}. Valid events: ${validEvents.join(', ')}`;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Validate npm event configuration if present
|
|
98
|
-
if (events.npm && typeof events.npm === 'object') {
|
|
99
|
-
const npmValidation = this.validateNpmConfig(events.npm);
|
|
100
|
-
if (npmValidation !== true) {
|
|
101
|
-
return npmValidation;
|
|
93
|
+
// Delegate to command-specific validation
|
|
94
|
+
for (const [eventName, config] of Object.entries(events)) {
|
|
95
|
+
const command = registry.getCommandByName(eventName);
|
|
96
|
+
if (command && command.validation) {
|
|
97
|
+
const result = command.validation.validateConfig(config);
|
|
98
|
+
if (result !== true) {
|
|
99
|
+
return `${eventName}: ${result}`;
|
|
100
|
+
}
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
return true;
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
validateClaudeConfig(config) {
|
|
109
|
-
if (typeof config !== 'object') {
|
|
110
|
-
return 'Claude configuration must be an object';
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Validate flags if present
|
|
114
|
-
if (config.flags) {
|
|
115
|
-
if (!Array.isArray(config.flags)) {
|
|
116
|
-
return 'Claude flags must be an array';
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Basic flag validation - ensure they start with - or --
|
|
120
|
-
const invalidFlags = config.flags.filter(flag =>
|
|
121
|
-
typeof flag !== 'string' || (!flag.startsWith('-') && !flag.startsWith('--'))
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
if (invalidFlags.length > 0) {
|
|
125
|
-
return `Invalid Claude flags: ${invalidFlags.join(', ')}. Flags must start with - or --`;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Validate split_terminal if present
|
|
130
|
-
if (config.split_terminal !== undefined && typeof config.split_terminal !== 'boolean') {
|
|
131
|
-
return 'Claude split_terminal must be a boolean';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
validateNpmConfig(config) {
|
|
138
|
-
if (typeof config !== 'object') {
|
|
139
|
-
return 'NPM configuration must be an object';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Validate command if present
|
|
143
|
-
if (config.command) {
|
|
144
|
-
if (typeof config.command !== 'string' || config.command.trim() === '') {
|
|
145
|
-
return 'NPM command must be a non-empty string';
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Validate watch if present
|
|
150
|
-
if (config.watch !== undefined && typeof config.watch !== 'boolean') {
|
|
151
|
-
return 'NPM watch must be a boolean';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Validate auto_restart if present
|
|
155
|
-
if (config.auto_restart !== undefined && typeof config.auto_restart !== 'boolean') {
|
|
156
|
-
return 'NPM auto_restart must be a boolean';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
107
|
|
|
162
108
|
validateUrl(url) {
|
|
163
109
|
if (!url) return true; // Optional field
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workon",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Work on something great!",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"cz-conventional-changelog": "^2.0.0",
|
|
20
|
-
"standard-version": "^
|
|
20
|
+
"standard-version": "^9.5.0"
|
|
21
21
|
},
|
|
22
22
|
"config": {
|
|
23
23
|
"commitizen": {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const registry = require('./commands/registry');
|
|
2
|
+
const ProjectValidator = require('./lib/validation');
|
|
3
|
+
|
|
4
|
+
async function comprehensiveTest() {
|
|
5
|
+
console.log('๐งช Testing Command-Centric Architecture\n');
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
// Test 1: Registry initialization
|
|
9
|
+
console.log('1. Testing registry initialization...');
|
|
10
|
+
await registry.initialize();
|
|
11
|
+
console.log(' โ
Registry initialized successfully');
|
|
12
|
+
|
|
13
|
+
// Test 2: Command discovery
|
|
14
|
+
console.log('\n2. Testing command discovery...');
|
|
15
|
+
const commands = registry.getValidEventNames();
|
|
16
|
+
const expectedCommands = ['cwd', 'ide', 'web', 'claude', 'npm'];
|
|
17
|
+
const hasAllCommands = expectedCommands.every(cmd => commands.includes(cmd));
|
|
18
|
+
console.log(` ๐ Discovered commands: ${commands.join(', ')}`);
|
|
19
|
+
console.log(` ${hasAllCommands ? 'โ
' : 'โ'} All expected commands found`);
|
|
20
|
+
|
|
21
|
+
// Test 3: Command metadata
|
|
22
|
+
console.log('\n3. Testing command metadata...');
|
|
23
|
+
for (const cmdName of expectedCommands) {
|
|
24
|
+
const command = registry.getCommandByName(cmdName);
|
|
25
|
+
if (command && command.metadata) {
|
|
26
|
+
console.log(` โ
${cmdName}: ${command.metadata.displayName}`);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(` โ ${cmdName}: Missing metadata`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Test 4: Validation system
|
|
33
|
+
console.log('\n4. Testing validation system...');
|
|
34
|
+
const validator = new ProjectValidator({});
|
|
35
|
+
|
|
36
|
+
// Test valid configurations
|
|
37
|
+
const validConfigs = [
|
|
38
|
+
{ cwd: true, ide: true },
|
|
39
|
+
{ claude: { flags: ['--debug'] } },
|
|
40
|
+
{ npm: 'dev' },
|
|
41
|
+
{ npm: { command: 'test', watch: true } }
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
for (const config of validConfigs) {
|
|
45
|
+
const result = await validator.validateEvents(config);
|
|
46
|
+
console.log(` ${result === true ? 'โ
' : 'โ'} Valid config: ${JSON.stringify(config)}`);
|
|
47
|
+
if (result !== true) console.log(` Error: ${result}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Test invalid configurations
|
|
51
|
+
const invalidConfigs = [
|
|
52
|
+
{ invalid: true },
|
|
53
|
+
{ claude: { flags: 'invalid' } },
|
|
54
|
+
{ npm: { command: 123 } }
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const config of invalidConfigs) {
|
|
58
|
+
const result = await validator.validateEvents(config);
|
|
59
|
+
console.log(` ${result !== true ? 'โ
' : 'โ'} Invalid config rejected: ${JSON.stringify(config)}`);
|
|
60
|
+
if (result !== true) console.log(` Error: ${result}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Test 5: Command configuration
|
|
64
|
+
console.log('\n5. Testing command configuration...');
|
|
65
|
+
const claudeCommand = registry.getCommandByName('claude');
|
|
66
|
+
if (claudeCommand) {
|
|
67
|
+
const defaultConfig = claudeCommand.configuration.getDefaultConfig();
|
|
68
|
+
console.log(` โ
Claude default config: ${defaultConfig}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const npmCommand = registry.getCommandByName('npm');
|
|
72
|
+
if (npmCommand) {
|
|
73
|
+
const defaultConfig = npmCommand.configuration.getDefaultConfig();
|
|
74
|
+
console.log(` โ
NPM default config: ${defaultConfig}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Test 6: Tmux integration
|
|
78
|
+
console.log('\n6. Testing tmux integration...');
|
|
79
|
+
const tmuxCommands = registry.getTmuxEnabledCommands();
|
|
80
|
+
console.log(` ๐ Tmux-enabled commands: ${tmuxCommands.map(c => `${c.name}(${c.priority})`).join(', ')}`);
|
|
81
|
+
console.log(` ${tmuxCommands.length > 0 ? 'โ
' : 'โ'} Tmux commands found`);
|
|
82
|
+
|
|
83
|
+
// Test 7: Management UI data
|
|
84
|
+
console.log('\n7. Testing management UI data...');
|
|
85
|
+
const manageCommands = registry.getCommandsForManageUI();
|
|
86
|
+
console.log(` ๐ Management UI commands: ${manageCommands.length}`);
|
|
87
|
+
for (const cmd of manageCommands) {
|
|
88
|
+
console.log(` - ${cmd.name} (${cmd.value})`);
|
|
89
|
+
}
|
|
90
|
+
console.log(` ${manageCommands.length === expectedCommands.length ? 'โ
' : 'โ'} All commands available for management`);
|
|
91
|
+
|
|
92
|
+
// Test 8: Command processing simulation
|
|
93
|
+
console.log('\n8. Testing command processing...');
|
|
94
|
+
const mockProject = {
|
|
95
|
+
name: 'test-project',
|
|
96
|
+
path: { path: '/test/path' },
|
|
97
|
+
ide: 'code',
|
|
98
|
+
homepage: 'https://example.com',
|
|
99
|
+
events: {
|
|
100
|
+
cwd: true,
|
|
101
|
+
ide: true,
|
|
102
|
+
claude: { flags: ['--debug'] },
|
|
103
|
+
npm: 'dev'
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const shellCommands = [];
|
|
108
|
+
const context = {
|
|
109
|
+
project: mockProject,
|
|
110
|
+
isShellMode: true,
|
|
111
|
+
shellCommands
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
for (const eventName of ['cwd', 'ide', 'claude', 'npm']) {
|
|
115
|
+
const command = registry.getCommandByName(eventName);
|
|
116
|
+
if (command && command.processing) {
|
|
117
|
+
try {
|
|
118
|
+
await command.processing.processEvent(context);
|
|
119
|
+
console.log(` โ
${eventName} command processed`);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.log(` โ ${eventName} command failed: ${error.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(` ๐ Generated shell commands: ${shellCommands.length}`);
|
|
127
|
+
shellCommands.forEach((cmd, i) => console.log(` ${i + 1}. ${cmd}`));
|
|
128
|
+
|
|
129
|
+
console.log('\n๐ All tests completed successfully!');
|
|
130
|
+
console.log('\n๐ Architecture Benefits Achieved:');
|
|
131
|
+
console.log(' โ
Commands are self-contained');
|
|
132
|
+
console.log(' โ
Validation is delegated to commands');
|
|
133
|
+
console.log(' โ
Configuration is handled by commands');
|
|
134
|
+
console.log(' โ
No hardcoded command lists');
|
|
135
|
+
console.log(' โ
Auto-discovery works');
|
|
136
|
+
console.log(' โ
Adding new commands requires touching only command directory');
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('โ Test failed:', error.message);
|
|
140
|
+
console.error(error.stack);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
comprehensiveTest();
|