workon 2.1.3 โ 3.1.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/README.md +19 -4
- package/bin/workon +1 -11
- package/dist/cli.js +2364 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1216 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +280 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.js +1173 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -21
- package/.claude/settings.local.json +0 -11
- package/.cursorindexingignore +0 -3
- package/.history/.gitignore_20250806202718 +0 -30
- package/.history/.gitignore_20250806231444 +0 -32
- package/.history/.gitignore_20250806231450 +0 -32
- package/.history/lib/tmux_20250806233103.js +0 -109
- package/.history/lib/tmux_20250806233219.js +0 -109
- package/.history/lib/tmux_20250806233223.js +0 -109
- package/.history/lib/tmux_20250806233230.js +0 -109
- package/.history/lib/tmux_20250806233231.js +0 -109
- package/.history/lib/tmux_20250807120751.js +0 -190
- package/.history/lib/tmux_20250807120757.js +0 -190
- package/.history/lib/tmux_20250807120802.js +0 -190
- package/.history/lib/tmux_20250807120808.js +0 -190
- package/.history/package_20250807114243.json +0 -43
- package/.history/package_20250807114257.json +0 -43
- package/.history/package_20250807114404.json +0 -43
- package/.history/package_20250807114409.json +0 -43
- package/.history/package_20250807114510.json +0 -43
- package/.history/package_20250807114637.json +0 -43
- package/.vscode/launch.json +0 -20
- package/.vscode/terminals.json +0 -11
- package/CHANGELOG.md +0 -110
- package/CLAUDE.md +0 -100
- package/cli/base.js +0 -16
- package/cli/config/index.js +0 -19
- package/cli/config/list.js +0 -26
- package/cli/config/set.js +0 -19
- package/cli/config/unset.js +0 -26
- package/cli/index.js +0 -184
- package/cli/interactive.js +0 -290
- package/cli/manage.js +0 -413
- package/cli/open.js +0 -414
- package/commands/base.js +0 -105
- package/commands/core/cwd/index.js +0 -86
- package/commands/core/ide/index.js +0 -84
- package/commands/core/web/index.js +0 -109
- package/commands/extensions/claude/index.js +0 -211
- package/commands/extensions/docker/index.js +0 -167
- package/commands/extensions/npm/index.js +0 -208
- package/commands/registry.js +0 -196
- package/demo-colon-syntax.js +0 -57
- package/docs/adr/001-command-centric-architecture.md +0 -304
- package/docs/adr/002-positional-command-arguments.md +0 -402
- package/docs/ideas.md +0 -93
- package/lib/config.js +0 -51
- package/lib/environment/base.js +0 -12
- package/lib/environment/index.js +0 -108
- package/lib/environment/project.js +0 -26
- package/lib/project.js +0 -68
- package/lib/tmux.js +0 -223
- package/lib/validation.js +0 -120
- package/test-architecture.js +0 -145
- package/test-colon-syntax.js +0 -85
- package/test-registry.js +0 -57
package/lib/project.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
const File = require('phylo');
|
|
2
|
-
const deepAssign = require('deep-assign');
|
|
3
|
-
|
|
4
|
-
class Project {
|
|
5
|
-
constructor (name, cfg, defaults) {
|
|
6
|
-
this._defaults = defaults = defaults || {};
|
|
7
|
-
this._initialCfg = cfg = cfg || {};
|
|
8
|
-
|
|
9
|
-
if (!('name' in cfg)) {
|
|
10
|
-
cfg.name = name;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (!('path' in cfg)) {
|
|
14
|
-
cfg.path = name;
|
|
15
|
-
}
|
|
16
|
-
deepAssign(this, deepAssign(defaults, cfg));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
set base (path) {
|
|
20
|
-
this._base = File.from(path).absolutify();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get base () {
|
|
24
|
-
return this._base;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
set ide (cmd) {
|
|
28
|
-
this._ide = cmd;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
get ide () {
|
|
32
|
-
return this._ide;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
set events (eventCfg) {
|
|
36
|
-
this._eventCfg = eventCfg;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get events () {
|
|
40
|
-
return this._eventCfg;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
set path (path) {
|
|
44
|
-
if (this._base) {
|
|
45
|
-
this._path = this._base.join(path);
|
|
46
|
-
} else {
|
|
47
|
-
this._path = File.from(path);
|
|
48
|
-
}
|
|
49
|
-
this._path = this._path.absolutify();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
get path () {
|
|
53
|
-
return this._path;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
set branch (branch) {
|
|
57
|
-
this._branch = branch;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
get branch () {
|
|
61
|
-
return this._branch;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
Project.$isProject = true;
|
|
66
|
-
Project.prototype.$isProject = true;
|
|
67
|
-
|
|
68
|
-
module.exports = Project;
|
package/lib/tmux.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
const { spawn } = require('child_process');
|
|
2
|
-
const { promisify } = require('util');
|
|
3
|
-
const exec = promisify(require('child_process').exec);
|
|
4
|
-
|
|
5
|
-
class TmuxManager {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.sessionPrefix = 'workon-';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async isTmuxAvailable() {
|
|
11
|
-
try {
|
|
12
|
-
await exec('which tmux');
|
|
13
|
-
return true;
|
|
14
|
-
} catch {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async sessionExists(sessionName) {
|
|
20
|
-
try {
|
|
21
|
-
await exec(`tmux has-session -t "${sessionName}"`);
|
|
22
|
-
return true;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getSessionName(projectName) {
|
|
29
|
-
return `${this.sessionPrefix}${projectName}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async killSession(sessionName) {
|
|
33
|
-
try {
|
|
34
|
-
await exec(`tmux kill-session -t "${sessionName}"`);
|
|
35
|
-
return true;
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
42
|
-
const sessionName = this.getSessionName(projectName);
|
|
43
|
-
|
|
44
|
-
// Kill existing session if it exists
|
|
45
|
-
if (await this.sessionExists(sessionName)) {
|
|
46
|
-
await this.killSession(sessionName);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const claudeCommand = claudeArgs.length > 0
|
|
50
|
-
? `claude ${claudeArgs.join(' ')}`
|
|
51
|
-
: 'claude';
|
|
52
|
-
|
|
53
|
-
// Create new tmux session with claude in the first pane
|
|
54
|
-
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
55
|
-
|
|
56
|
-
// Split window horizontally and run shell in second pane
|
|
57
|
-
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
58
|
-
|
|
59
|
-
// Set focus on claude pane (left pane)
|
|
60
|
-
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
61
|
-
|
|
62
|
-
return sessionName;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = 'npm run dev') {
|
|
66
|
-
const sessionName = this.getSessionName(projectName);
|
|
67
|
-
|
|
68
|
-
// Kill existing session if it exists
|
|
69
|
-
if (await this.sessionExists(sessionName)) {
|
|
70
|
-
await this.killSession(sessionName);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const claudeCommand = claudeArgs.length > 0
|
|
74
|
-
? `claude ${claudeArgs.join(' ')}`
|
|
75
|
-
: 'claude';
|
|
76
|
-
|
|
77
|
-
// Create new tmux session with claude in the first pane (left side)
|
|
78
|
-
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
79
|
-
|
|
80
|
-
// Split window vertically - creates right side (50/50 split)
|
|
81
|
-
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
82
|
-
|
|
83
|
-
// Split the right pane horizontally - creates top-right and bottom-right (50/50 split)
|
|
84
|
-
await exec(`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`);
|
|
85
|
-
|
|
86
|
-
// Set remain-on-exit to keep pane open if command fails
|
|
87
|
-
await exec(`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`);
|
|
88
|
-
|
|
89
|
-
// Resize panes to ensure npm pane is visible (give it at least 10 lines)
|
|
90
|
-
await exec(`tmux resize-pane -t "${sessionName}:0.2" -y 10`);
|
|
91
|
-
|
|
92
|
-
// Set focus on claude pane (left pane)
|
|
93
|
-
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
94
|
-
|
|
95
|
-
return sessionName;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = 'npm run dev') {
|
|
99
|
-
const sessionName = this.getSessionName(projectName);
|
|
100
|
-
|
|
101
|
-
// Kill existing session if it exists
|
|
102
|
-
if (await this.sessionExists(sessionName)) {
|
|
103
|
-
await this.killSession(sessionName);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Create new tmux session with shell in the first pane (left side)
|
|
107
|
-
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`);
|
|
108
|
-
|
|
109
|
-
// Split window vertically and run npm command in right pane
|
|
110
|
-
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`);
|
|
111
|
-
|
|
112
|
-
// Set remain-on-exit to keep pane open if command fails
|
|
113
|
-
await exec(`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`);
|
|
114
|
-
|
|
115
|
-
// Set focus on terminal pane (left pane)
|
|
116
|
-
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
117
|
-
|
|
118
|
-
return sessionName;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async attachToSession(sessionName) {
|
|
122
|
-
// Check if we're already in a tmux session
|
|
123
|
-
if (process.env.TMUX) {
|
|
124
|
-
// If we're already in tmux, switch to the session
|
|
125
|
-
await exec(`tmux switch-client -t "${sessionName}"`);
|
|
126
|
-
} else {
|
|
127
|
-
// Check if iTerm2 integration is available
|
|
128
|
-
const isITerm = process.env.TERM_PROGRAM === 'iTerm.app' ||
|
|
129
|
-
process.env.LC_TERMINAL === 'iTerm2' ||
|
|
130
|
-
!!process.env.ITERM_SESSION_ID;
|
|
131
|
-
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
132
|
-
|
|
133
|
-
if (useiTermIntegration) {
|
|
134
|
-
// Use iTerm2 tmux integration - spawn detached to avoid blocking
|
|
135
|
-
spawn('tmux', ['-CC', 'attach-session', '-t', sessionName], {
|
|
136
|
-
stdio: 'inherit',
|
|
137
|
-
detached: true
|
|
138
|
-
});
|
|
139
|
-
} else {
|
|
140
|
-
// Use regular tmux - spawn detached to avoid blocking
|
|
141
|
-
spawn('tmux', ['attach-session', '-t', sessionName], {
|
|
142
|
-
stdio: 'inherit',
|
|
143
|
-
detached: true
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
150
|
-
const sessionName = this.getSessionName(projectName);
|
|
151
|
-
const claudeCommand = claudeArgs.length > 0
|
|
152
|
-
? `claude ${claudeArgs.join(' ')}`
|
|
153
|
-
: 'claude';
|
|
154
|
-
|
|
155
|
-
return [
|
|
156
|
-
`# Create tmux split session for ${projectName}`,
|
|
157
|
-
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
158
|
-
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
159
|
-
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
160
|
-
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
161
|
-
process.env.TMUX
|
|
162
|
-
? `tmux switch-client -t "${sessionName}"`
|
|
163
|
-
: ((process.env.TERM_PROGRAM === 'iTerm.app' || process.env.LC_TERMINAL === 'iTerm2' || process.env.ITERM_SESSION_ID) && !process.env.TMUX_CC_NOT_SUPPORTED)
|
|
164
|
-
? `tmux -CC attach-session -t "${sessionName}"`
|
|
165
|
-
: `tmux attach-session -t "${sessionName}"`
|
|
166
|
-
];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = 'npm run dev') {
|
|
170
|
-
const sessionName = this.getSessionName(projectName);
|
|
171
|
-
const claudeCommand = claudeArgs.length > 0
|
|
172
|
-
? `claude ${claudeArgs.join(' ')}`
|
|
173
|
-
: 'claude';
|
|
174
|
-
|
|
175
|
-
return [
|
|
176
|
-
`# Create tmux three-pane session for ${projectName}`,
|
|
177
|
-
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
178
|
-
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
179
|
-
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
180
|
-
`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`,
|
|
181
|
-
`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`,
|
|
182
|
-
`tmux resize-pane -t "${sessionName}:0.2" -y 10`,
|
|
183
|
-
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
184
|
-
process.env.TMUX
|
|
185
|
-
? `tmux switch-client -t "${sessionName}"`
|
|
186
|
-
: ((process.env.TERM_PROGRAM === 'iTerm.app' || process.env.LC_TERMINAL === 'iTerm2' || process.env.ITERM_SESSION_ID) && !process.env.TMUX_CC_NOT_SUPPORTED)
|
|
187
|
-
? `tmux -CC attach-session -t "${sessionName}"`
|
|
188
|
-
: `tmux attach-session -t "${sessionName}"`
|
|
189
|
-
];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = 'npm run dev') {
|
|
193
|
-
const sessionName = this.getSessionName(projectName);
|
|
194
|
-
|
|
195
|
-
return [
|
|
196
|
-
`# Create tmux two-pane session with npm for ${projectName}`,
|
|
197
|
-
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
198
|
-
`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`,
|
|
199
|
-
`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`,
|
|
200
|
-
`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`,
|
|
201
|
-
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
202
|
-
process.env.TMUX
|
|
203
|
-
? `tmux switch-client -t "${sessionName}"`
|
|
204
|
-
: ((process.env.TERM_PROGRAM === 'iTerm.app' || process.env.LC_TERMINAL === 'iTerm2' || process.env.ITERM_SESSION_ID) && !process.env.TMUX_CC_NOT_SUPPORTED)
|
|
205
|
-
? `tmux -CC attach-session -t "${sessionName}"`
|
|
206
|
-
: `tmux attach-session -t "${sessionName}"`
|
|
207
|
-
];
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async listWorkonSessions() {
|
|
211
|
-
try {
|
|
212
|
-
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
213
|
-
return stdout.trim()
|
|
214
|
-
.split('\n')
|
|
215
|
-
.filter(session => session.startsWith(this.sessionPrefix))
|
|
216
|
-
.map(session => session.replace(this.sessionPrefix, ''));
|
|
217
|
-
} catch {
|
|
218
|
-
return [];
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
module.exports = TmuxManager;
|
package/lib/validation.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
const File = require('phylo');
|
|
2
|
-
const { spawn } = require('child_process');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const registry = require('../commands/registry');
|
|
5
|
-
|
|
6
|
-
class ProjectValidator {
|
|
7
|
-
constructor(config) {
|
|
8
|
-
this.config = config;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
validateProjectName(name, existingProjects = {}) {
|
|
12
|
-
if (!name || name.trim() === '') {
|
|
13
|
-
return 'Project name cannot be empty';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (name in existingProjects) {
|
|
17
|
-
return 'Project already exists';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
21
|
-
return 'Project name can only contain letters, numbers, underscores, and hyphens';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
validateProjectPath(projectPath, basePath) {
|
|
28
|
-
if (!projectPath || projectPath.trim() === '') {
|
|
29
|
-
return 'Project path cannot be empty';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const fullPath = basePath ? path.resolve(basePath, projectPath) : path.resolve(projectPath);
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const fileObj = new File(fullPath);
|
|
36
|
-
if (!fileObj.exists) {
|
|
37
|
-
return `Directory does not exist: ${fullPath}`;
|
|
38
|
-
}
|
|
39
|
-
if (!fileObj.isDirectory) {
|
|
40
|
-
return `Path is not a directory: ${fullPath}`;
|
|
41
|
-
}
|
|
42
|
-
} catch (error) {
|
|
43
|
-
return `Invalid path: ${error.message}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
validateIdeCommand(ideCommand) {
|
|
50
|
-
if (!ideCommand || ideCommand.trim() === '') {
|
|
51
|
-
return 'IDE command cannot be empty';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const validIdeCommands = ['code', 'idea', 'atom', 'subl', 'vim', 'emacs'];
|
|
55
|
-
if (!validIdeCommands.includes(ideCommand)) {
|
|
56
|
-
return `Unknown IDE command. Supported: ${validIdeCommands.join(', ')}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async validateIdeAvailability(ideCommand) {
|
|
63
|
-
return new Promise((resolve) => {
|
|
64
|
-
const child = spawn('which', [ideCommand], { stdio: 'pipe' });
|
|
65
|
-
child.on('close', (code) => {
|
|
66
|
-
if (code === 0) {
|
|
67
|
-
resolve(true);
|
|
68
|
-
} else {
|
|
69
|
-
resolve(`IDE command '${ideCommand}' not found in PATH`);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
child.on('error', () => {
|
|
73
|
-
resolve(`Could not check if '${ideCommand}' is available`);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async validateEvents(events) {
|
|
79
|
-
if (!events || typeof events !== 'object') {
|
|
80
|
-
return 'Events must be an object';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Initialize registry if not already done
|
|
84
|
-
await registry.initialize();
|
|
85
|
-
|
|
86
|
-
const validEvents = registry.getValidEventNames();
|
|
87
|
-
const invalidEvents = Object.keys(events).filter(event => !validEvents.includes(event));
|
|
88
|
-
|
|
89
|
-
if (invalidEvents.length > 0) {
|
|
90
|
-
return `Invalid events: ${invalidEvents.join(', ')}. Valid events: ${validEvents.join(', ')}`;
|
|
91
|
-
}
|
|
92
|
-
|
|
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
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
validateUrl(url) {
|
|
109
|
-
if (!url) return true; // Optional field
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
new URL(url);
|
|
113
|
-
return true;
|
|
114
|
-
} catch {
|
|
115
|
-
return 'Invalid URL format';
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
module.exports = ProjectValidator;
|
package/test-architecture.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
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();
|
package/test-colon-syntax.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Comprehensive test of the colon syntax feature
|
|
4
|
-
const { execSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
console.log('๐งช Testing Colon Syntax Feature\n');
|
|
7
|
-
|
|
8
|
-
const tests = [
|
|
9
|
-
{
|
|
10
|
-
name: 'All commands (backward compatibility)',
|
|
11
|
-
command: 'node bin/workon test-project --shell',
|
|
12
|
-
expectShellCommands: 3 // cwd, claude, npm in tmux
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
name: 'Single command: cwd only',
|
|
16
|
-
command: 'node bin/workon test-project:cwd --shell',
|
|
17
|
-
expectOutput: 'cd "/users/israelroldan/code/test-project"'
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: 'Single command: claude (auto-adds cwd)',
|
|
21
|
-
command: 'node bin/workon test-project:claude --shell',
|
|
22
|
-
expectTmux: true
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: 'Multiple commands: cwd,npm',
|
|
26
|
-
command: 'node bin/workon test-project:cwd,npm --shell',
|
|
27
|
-
expectTmux: true
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'Project help',
|
|
31
|
-
command: 'node bin/workon test-project:help',
|
|
32
|
-
expectOutput: 'Available commands for'
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: 'Invalid command validation',
|
|
36
|
-
command: 'node bin/workon test-project:invalid 2>&1 || true',
|
|
37
|
-
expectOutput: 'Commands not configured'
|
|
38
|
-
}
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
let passed = 0;
|
|
42
|
-
let failed = 0;
|
|
43
|
-
|
|
44
|
-
for (const test of tests) {
|
|
45
|
-
try {
|
|
46
|
-
console.log(`Testing: ${test.name}`);
|
|
47
|
-
const output = execSync(test.command, { encoding: 'utf8', timeout: 10000 });
|
|
48
|
-
|
|
49
|
-
let success = false;
|
|
50
|
-
|
|
51
|
-
if (test.expectOutput) {
|
|
52
|
-
success = output.includes(test.expectOutput);
|
|
53
|
-
} else if (test.expectTmux) {
|
|
54
|
-
success = output.includes('tmux');
|
|
55
|
-
} else if (test.expectShellCommands) {
|
|
56
|
-
const lines = output.trim().split('\n').filter(line => line.trim() && !line.startsWith('#') && !line.startsWith('โน'));
|
|
57
|
-
success = lines.length >= test.expectShellCommands;
|
|
58
|
-
} else {
|
|
59
|
-
success = true; // Just check it doesn't crash
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (success) {
|
|
63
|
-
console.log(' โ
PASS');
|
|
64
|
-
passed++;
|
|
65
|
-
} else {
|
|
66
|
-
console.log(' โ FAIL');
|
|
67
|
-
console.log(` Expected: ${test.expectOutput || test.expectTmux || test.expectShellCommands}`);
|
|
68
|
-
console.log(` Got: ${output.substring(0, 200)}...`);
|
|
69
|
-
failed++;
|
|
70
|
-
}
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.log(' โ ERROR:', error.message);
|
|
73
|
-
failed++;
|
|
74
|
-
}
|
|
75
|
-
console.log('');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log(`\n๐ Results: ${passed} passed, ${failed} failed`);
|
|
79
|
-
|
|
80
|
-
if (failed === 0) {
|
|
81
|
-
console.log('๐ All tests passed! Colon syntax is working perfectly.');
|
|
82
|
-
} else {
|
|
83
|
-
console.log('โ ๏ธ Some tests failed. Please check the implementation.');
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|