workon 1.3.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 +25 -1
- package/cli/manage.js +29 -77
- package/cli/open.js +152 -74
- 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/docs/ideas.md +71 -65
- package/lib/tmux.js +81 -0
- package/lib/validation.js +14 -35
- package/package.json +2 -2
- package/test-architecture.js +145 -0
- package/test-registry.js +57 -0
package/cli/open.js
CHANGED
|
@@ -4,10 +4,15 @@ const { ProjectEnvironment } = require('../lib/environment');
|
|
|
4
4
|
const spawn = require('child_process').spawn;
|
|
5
5
|
const File = require('phylo');
|
|
6
6
|
const TmuxManager = require('../lib/tmux');
|
|
7
|
+
const registry = require('../commands/registry');
|
|
7
8
|
|
|
8
9
|
class open extends command {
|
|
9
|
-
execute (params) {
|
|
10
|
+
async execute (params) {
|
|
10
11
|
let me = this;
|
|
12
|
+
|
|
13
|
+
// Initialize command registry
|
|
14
|
+
await registry.initialize();
|
|
15
|
+
|
|
11
16
|
if (params.project) {
|
|
12
17
|
return me.processProject(params.project);
|
|
13
18
|
} else {
|
|
@@ -64,20 +69,37 @@ class open extends command {
|
|
|
64
69
|
me.shellCommands = [];
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
//
|
|
72
|
+
// Intelligent layout detection
|
|
68
73
|
const hasCwd = events.includes('cwd');
|
|
69
74
|
const hasClaudeEvent = events.includes('claude');
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
const hasNpmEvent = events.includes('npm');
|
|
76
|
+
|
|
77
|
+
if (hasCwd && hasClaudeEvent && hasNpmEvent) {
|
|
78
|
+
// Three-pane layout: Claude + Terminal + NPM
|
|
79
|
+
await me.handleThreePaneLayout(project, isShellMode);
|
|
80
|
+
// Process other events except cwd, claude, and npm
|
|
81
|
+
for (const event of events.filter(e => !['cwd', 'claude', 'npm'].includes(e))) {
|
|
82
|
+
await me.processEvent(event);
|
|
83
|
+
}
|
|
84
|
+
} else if (hasCwd && hasNpmEvent) {
|
|
85
|
+
// Two-pane layout: Terminal + NPM (no Claude)
|
|
86
|
+
await me.handleTwoPaneNpmLayout(project, isShellMode);
|
|
87
|
+
// Process other events except cwd and npm
|
|
88
|
+
for (const event of events.filter(e => !['cwd', 'npm'].includes(e))) {
|
|
89
|
+
await me.processEvent(event);
|
|
90
|
+
}
|
|
91
|
+
} else if (hasCwd && hasClaudeEvent) {
|
|
92
|
+
// Two-pane layout: Claude + Terminal (existing split terminal)
|
|
75
93
|
await me.handleSplitTerminal(project, isShellMode);
|
|
76
94
|
// Process other events except cwd and claude
|
|
77
|
-
events.filter(e =>
|
|
95
|
+
for (const event of events.filter(e => !['cwd', 'claude'].includes(e))) {
|
|
96
|
+
await me.processEvent(event);
|
|
97
|
+
}
|
|
78
98
|
} else {
|
|
79
99
|
// Normal event processing
|
|
80
|
-
events
|
|
100
|
+
for (const event of events) {
|
|
101
|
+
await me.processEvent(event);
|
|
102
|
+
}
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
// Output collected shell commands if in shell mode
|
|
@@ -123,24 +145,128 @@ class open extends command {
|
|
|
123
145
|
} catch (error) {
|
|
124
146
|
me.log.debug(`Failed to create tmux session: ${error.message}`);
|
|
125
147
|
// Fall back to normal behavior
|
|
126
|
-
me.processEvent('cwd');
|
|
127
|
-
me.processEvent('claude');
|
|
148
|
+
await me.processEvent('cwd');
|
|
149
|
+
await me.processEvent('claude');
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
me.log.debug('Tmux not available, falling back to normal mode');
|
|
153
|
+
// Fall back to normal behavior
|
|
154
|
+
await me.processEvent('cwd');
|
|
155
|
+
await me.processEvent('claude');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async handleThreePaneLayout(project, isShellMode) {
|
|
161
|
+
let me = this;
|
|
162
|
+
const tmux = new TmuxManager();
|
|
163
|
+
const claudeConfig = project.events.claude;
|
|
164
|
+
const claudeArgs = (claudeConfig && claudeConfig.flags) ? claudeConfig.flags : [];
|
|
165
|
+
const npmConfig = project.events.npm;
|
|
166
|
+
const NpmCommand = registry.getCommandByName('npm');
|
|
167
|
+
const npmCommand = NpmCommand ? NpmCommand._getNpmCommand(npmConfig) : 'npm run dev';
|
|
168
|
+
|
|
169
|
+
if (isShellMode) {
|
|
170
|
+
// Check if tmux is available
|
|
171
|
+
if (await tmux.isTmuxAvailable()) {
|
|
172
|
+
const commands = tmux.buildThreePaneShellCommands(
|
|
173
|
+
project.name,
|
|
174
|
+
project.path.path,
|
|
175
|
+
claudeArgs,
|
|
176
|
+
npmCommand
|
|
177
|
+
);
|
|
178
|
+
me.shellCommands.push(...commands);
|
|
179
|
+
} else {
|
|
180
|
+
// Fall back to normal behavior if tmux is not available
|
|
181
|
+
me.log.debug('Tmux not available, falling back to normal mode');
|
|
182
|
+
me.shellCommands.push(`cd "${project.path.path}"`);
|
|
183
|
+
const claudeCommand = claudeArgs.length > 0
|
|
184
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
185
|
+
: 'claude';
|
|
186
|
+
me.shellCommands.push(claudeCommand);
|
|
187
|
+
me.shellCommands.push(npmCommand);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
// Direct execution mode
|
|
191
|
+
if (await tmux.isTmuxAvailable()) {
|
|
192
|
+
try {
|
|
193
|
+
const sessionName = await tmux.createThreePaneSession(
|
|
194
|
+
project.name,
|
|
195
|
+
project.path.path,
|
|
196
|
+
claudeArgs,
|
|
197
|
+
npmCommand
|
|
198
|
+
);
|
|
199
|
+
await tmux.attachToSession(sessionName);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
me.log.debug(`Failed to create tmux session: ${error.message}`);
|
|
202
|
+
// Fall back to normal behavior
|
|
203
|
+
await me.processEvent('cwd');
|
|
204
|
+
await me.processEvent('claude');
|
|
205
|
+
await me.processEvent('npm');
|
|
128
206
|
}
|
|
129
207
|
} else {
|
|
130
208
|
me.log.debug('Tmux not available, falling back to normal mode');
|
|
131
209
|
// Fall back to normal behavior
|
|
132
|
-
me.processEvent('cwd');
|
|
133
|
-
me.processEvent('claude');
|
|
210
|
+
await me.processEvent('cwd');
|
|
211
|
+
await me.processEvent('claude');
|
|
212
|
+
await me.processEvent('npm');
|
|
134
213
|
}
|
|
135
214
|
}
|
|
136
215
|
}
|
|
137
216
|
|
|
138
|
-
|
|
217
|
+
async handleTwoPaneNpmLayout(project, isShellMode) {
|
|
218
|
+
let me = this;
|
|
219
|
+
const tmux = new TmuxManager();
|
|
220
|
+
const npmConfig = project.events.npm;
|
|
221
|
+
const NpmCommand = registry.getCommandByName('npm');
|
|
222
|
+
const npmCommand = NpmCommand ? NpmCommand._getNpmCommand(npmConfig) : 'npm run dev';
|
|
223
|
+
|
|
224
|
+
if (isShellMode) {
|
|
225
|
+
// Check if tmux is available
|
|
226
|
+
if (await tmux.isTmuxAvailable()) {
|
|
227
|
+
const commands = tmux.buildTwoPaneNpmShellCommands(
|
|
228
|
+
project.name,
|
|
229
|
+
project.path.path,
|
|
230
|
+
npmCommand
|
|
231
|
+
);
|
|
232
|
+
me.shellCommands.push(...commands);
|
|
233
|
+
} else {
|
|
234
|
+
// Fall back to normal behavior if tmux is not available
|
|
235
|
+
me.log.debug('Tmux not available, falling back to normal mode');
|
|
236
|
+
me.shellCommands.push(`cd "${project.path.path}"`);
|
|
237
|
+
me.shellCommands.push(npmCommand);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
// Direct execution mode
|
|
241
|
+
if (await tmux.isTmuxAvailable()) {
|
|
242
|
+
try {
|
|
243
|
+
const sessionName = await tmux.createTwoPaneNpmSession(
|
|
244
|
+
project.name,
|
|
245
|
+
project.path.path,
|
|
246
|
+
npmCommand
|
|
247
|
+
);
|
|
248
|
+
await tmux.attachToSession(sessionName);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
me.log.debug(`Failed to create tmux session: ${error.message}`);
|
|
251
|
+
// Fall back to normal behavior
|
|
252
|
+
await me.processEvent('cwd');
|
|
253
|
+
await me.processEvent('npm');
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
me.log.debug('Tmux not available, falling back to normal mode');
|
|
257
|
+
// Fall back to normal behavior
|
|
258
|
+
await me.processEvent('cwd');
|
|
259
|
+
await me.processEvent('npm');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async processEvent (event) {
|
|
139
266
|
let me = this;
|
|
140
267
|
let environment = me.root().environment;
|
|
141
268
|
let project = environment.project;
|
|
142
269
|
let scripts = project.scripts || {};
|
|
143
|
-
let homepage = project.homepage;
|
|
144
270
|
let capitalEvt = `${event[0].toUpperCase()}${event.substring(1)}`;
|
|
145
271
|
|
|
146
272
|
me.log.debug(`Processing event ${event}`);
|
|
@@ -155,67 +281,19 @@ class open extends command {
|
|
|
155
281
|
let isShellMode = me.params.shell || me.root().params.shell;
|
|
156
282
|
me.log.debug(`Shell mode is: ${isShellMode}`);
|
|
157
283
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
me.shellCommands.push(`cd "${environment.project.path.path}"`);
|
|
169
|
-
} else {
|
|
170
|
-
spawn(process.env.SHELL, ['-i'], {
|
|
171
|
-
cwd: environment.project.path.path,
|
|
172
|
-
stdio: 'inherit'
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
break;
|
|
176
|
-
case 'web':
|
|
177
|
-
if (homepage) {
|
|
178
|
-
if (isShellMode) {
|
|
179
|
-
// Different approaches based on OS
|
|
180
|
-
let openCmd;
|
|
181
|
-
switch (process.platform) {
|
|
182
|
-
case 'darwin': openCmd = 'open'; break;
|
|
183
|
-
case 'win32': openCmd = 'start'; break;
|
|
184
|
-
default: openCmd = 'xdg-open'; break;
|
|
185
|
-
}
|
|
186
|
-
me.shellCommands.push(`${openCmd} "${homepage}" &`);
|
|
187
|
-
} else {
|
|
188
|
-
require("openurl2").open(homepage);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
break;
|
|
192
|
-
case 'claude':
|
|
193
|
-
let claudeArgs = [];
|
|
194
|
-
let claudeConfig = project.events.claude;
|
|
195
|
-
|
|
196
|
-
// Handle advanced Claude configuration
|
|
197
|
-
if (claudeConfig && typeof claudeConfig === 'object') {
|
|
198
|
-
if (claudeConfig.flags && Array.isArray(claudeConfig.flags)) {
|
|
199
|
-
claudeArgs = claudeArgs.concat(claudeConfig.flags);
|
|
200
|
-
}
|
|
201
|
-
// Additional config options can be handled here in the future
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (isShellMode) {
|
|
205
|
-
let claudeCommand = claudeArgs.length > 0
|
|
206
|
-
? `claude ${claudeArgs.join(' ')}`
|
|
207
|
-
: 'claude';
|
|
208
|
-
me.shellCommands.push(claudeCommand);
|
|
209
|
-
} else {
|
|
210
|
-
spawn('claude', claudeArgs, {
|
|
211
|
-
cwd: environment.project.path.path,
|
|
212
|
-
stdio: 'inherit'
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
284
|
+
// Use CommandRegistry to process the event
|
|
285
|
+
const command = registry.getCommandByName(event);
|
|
286
|
+
if (command && command.processing) {
|
|
287
|
+
await command.processing.processEvent({
|
|
288
|
+
project,
|
|
289
|
+
isShellMode,
|
|
290
|
+
shellCommands: me.shellCommands || []
|
|
291
|
+
});
|
|
292
|
+
} else {
|
|
293
|
+
me.log.debug(`No command handler found for event: ${event}`);
|
|
216
294
|
}
|
|
217
295
|
}
|
|
218
|
-
if (`
|
|
296
|
+
if (`after${capitalEvt}` in scripts) {
|
|
219
297
|
me.log.debug(`Found 'after' script, unfortunately scripts are not yet supported.`);
|
|
220
298
|
}
|
|
221
299
|
}
|
package/commands/base.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base command interface that all commands must implement
|
|
3
|
+
* Provides standardized structure for command-centric architecture
|
|
4
|
+
*/
|
|
5
|
+
class BaseCommand {
|
|
6
|
+
/**
|
|
7
|
+
* Command metadata - must be implemented by each command
|
|
8
|
+
* @returns {Object} metadata object with name, displayName, description, etc.
|
|
9
|
+
*/
|
|
10
|
+
static get metadata() {
|
|
11
|
+
throw new Error('Command must implement static metadata getter');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validation rules for command configuration
|
|
16
|
+
* @returns {Object} validation methods
|
|
17
|
+
*/
|
|
18
|
+
static get validation() {
|
|
19
|
+
return {
|
|
20
|
+
/**
|
|
21
|
+
* Validate command-specific configuration
|
|
22
|
+
* @param {*} config - Configuration to validate
|
|
23
|
+
* @returns {true|string} true if valid, error message if invalid
|
|
24
|
+
*/
|
|
25
|
+
validateConfig(config) {
|
|
26
|
+
return true; // Default: accept any config
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Interactive configuration setup
|
|
33
|
+
* @returns {Object} configuration methods
|
|
34
|
+
*/
|
|
35
|
+
static get configuration() {
|
|
36
|
+
return {
|
|
37
|
+
/**
|
|
38
|
+
* Interactive setup prompts for the command
|
|
39
|
+
* @returns {*} Configuration object or primitive value
|
|
40
|
+
*/
|
|
41
|
+
async configureInteractive() {
|
|
42
|
+
return 'true'; // Default: simple boolean enable
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get default configuration for the command
|
|
47
|
+
* @returns {*} Default configuration
|
|
48
|
+
*/
|
|
49
|
+
getDefaultConfig() {
|
|
50
|
+
return 'true';
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Event processing logic
|
|
57
|
+
* @returns {Object} processing methods
|
|
58
|
+
*/
|
|
59
|
+
static get processing() {
|
|
60
|
+
return {
|
|
61
|
+
/**
|
|
62
|
+
* Process the command event
|
|
63
|
+
* @param {Object} context - Processing context
|
|
64
|
+
* @param {Object} context.project - Project configuration
|
|
65
|
+
* @param {boolean} context.isShellMode - Whether in shell mode
|
|
66
|
+
* @param {string[]} context.shellCommands - Array to collect shell commands
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
async processEvent(context) {
|
|
70
|
+
throw new Error('Command must implement processEvent method');
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate shell command for the event
|
|
75
|
+
* @param {Object} context - Processing context
|
|
76
|
+
* @returns {string[]} Array of shell commands
|
|
77
|
+
*/
|
|
78
|
+
generateShellCommand(context) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Tmux integration (optional)
|
|
86
|
+
* @returns {Object|null} tmux methods or null if not supported
|
|
87
|
+
*/
|
|
88
|
+
static get tmux() {
|
|
89
|
+
return null; // Default: no tmux integration
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Help and documentation
|
|
94
|
+
* @returns {Object} help information
|
|
95
|
+
*/
|
|
96
|
+
static get help() {
|
|
97
|
+
return {
|
|
98
|
+
usage: `${this.metadata.name}: <configuration>`,
|
|
99
|
+
description: this.metadata.description,
|
|
100
|
+
examples: []
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = BaseCommand;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const BaseCommand = require('../../base');
|
|
2
|
+
const spawn = require('child_process').spawn;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CWD (Change Working Directory) Command
|
|
6
|
+
* Changes the current working directory to the project path
|
|
7
|
+
*/
|
|
8
|
+
class CwdCommand extends BaseCommand {
|
|
9
|
+
static get metadata() {
|
|
10
|
+
return {
|
|
11
|
+
name: 'cwd',
|
|
12
|
+
displayName: 'Change directory (cwd)',
|
|
13
|
+
description: 'Change current working directory to project path',
|
|
14
|
+
category: 'core',
|
|
15
|
+
requiresTmux: false,
|
|
16
|
+
dependencies: []
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get validation() {
|
|
21
|
+
return {
|
|
22
|
+
validateConfig(config) {
|
|
23
|
+
// CWD command accepts boolean or string 'true'
|
|
24
|
+
if (config === true || config === 'true' || config === false || config === 'false') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return 'CWD configuration must be a boolean or string "true"/"false"';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static get configuration() {
|
|
33
|
+
return {
|
|
34
|
+
async configureInteractive() {
|
|
35
|
+
// CWD is typically just enabled/disabled, no advanced config needed
|
|
36
|
+
return 'true';
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
getDefaultConfig() {
|
|
40
|
+
return 'true';
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static get processing() {
|
|
46
|
+
return {
|
|
47
|
+
async processEvent(context) {
|
|
48
|
+
const { project, isShellMode, shellCommands } = context;
|
|
49
|
+
|
|
50
|
+
if (isShellMode) {
|
|
51
|
+
shellCommands.push(`cd "${project.path.path}"`);
|
|
52
|
+
} else {
|
|
53
|
+
// In non-shell mode, spawn a new shell in the project directory
|
|
54
|
+
spawn(process.env.SHELL, ['-i'], {
|
|
55
|
+
cwd: project.path.path,
|
|
56
|
+
stdio: 'inherit'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
generateShellCommand(context) {
|
|
62
|
+
const { project } = context;
|
|
63
|
+
return [`cd "${project.path.path}"`];
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static get help() {
|
|
69
|
+
return {
|
|
70
|
+
usage: 'cwd: true',
|
|
71
|
+
description: 'Changes the current working directory to the project path',
|
|
72
|
+
examples: [
|
|
73
|
+
{
|
|
74
|
+
config: 'cwd: true',
|
|
75
|
+
description: 'Enable directory change when opening project'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
config: 'cwd: false',
|
|
79
|
+
description: 'Disable directory change (stay in current directory)'
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = CwdCommand;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const BaseCommand = require('../../base');
|
|
2
|
+
const spawn = require('child_process').spawn;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IDE Command
|
|
6
|
+
* Opens the project in the configured IDE/editor
|
|
7
|
+
*/
|
|
8
|
+
class IdeCommand extends BaseCommand {
|
|
9
|
+
static get metadata() {
|
|
10
|
+
return {
|
|
11
|
+
name: 'ide',
|
|
12
|
+
displayName: 'Open in IDE',
|
|
13
|
+
description: 'Open project in configured IDE/editor',
|
|
14
|
+
category: 'core',
|
|
15
|
+
requiresTmux: false,
|
|
16
|
+
dependencies: []
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get validation() {
|
|
21
|
+
return {
|
|
22
|
+
validateConfig(config) {
|
|
23
|
+
// IDE command accepts boolean or string 'true'
|
|
24
|
+
if (config === true || config === 'true' || config === false || config === 'false') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return 'IDE configuration must be a boolean or string "true"/"false"';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static get configuration() {
|
|
33
|
+
return {
|
|
34
|
+
async configureInteractive() {
|
|
35
|
+
// IDE configuration is handled at the project level (project.ide)
|
|
36
|
+
// This event just enables/disables opening the IDE
|
|
37
|
+
return 'true';
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
getDefaultConfig() {
|
|
41
|
+
return 'true';
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static get processing() {
|
|
47
|
+
return {
|
|
48
|
+
async processEvent(context) {
|
|
49
|
+
const { project, isShellMode, shellCommands } = context;
|
|
50
|
+
|
|
51
|
+
if (isShellMode) {
|
|
52
|
+
shellCommands.push(`${project.ide} "${project.path.path}" &`);
|
|
53
|
+
} else {
|
|
54
|
+
// In non-shell mode, spawn the IDE directly
|
|
55
|
+
spawn(project.ide, [project.path.path]);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
generateShellCommand(context) {
|
|
60
|
+
const { project } = context;
|
|
61
|
+
return [`${project.ide} "${project.path.path}" &`];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static get help() {
|
|
67
|
+
return {
|
|
68
|
+
usage: 'ide: true',
|
|
69
|
+
description: 'Opens the project in the configured IDE/editor',
|
|
70
|
+
examples: [
|
|
71
|
+
{
|
|
72
|
+
config: 'ide: true',
|
|
73
|
+
description: 'Enable opening project in IDE when switching to project'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
config: 'ide: false',
|
|
77
|
+
description: 'Disable automatic IDE opening'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = IdeCommand;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const BaseCommand = require('../../base');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Web Command
|
|
5
|
+
* Opens the project homepage in a web browser
|
|
6
|
+
*/
|
|
7
|
+
class WebCommand extends BaseCommand {
|
|
8
|
+
static get metadata() {
|
|
9
|
+
return {
|
|
10
|
+
name: 'web',
|
|
11
|
+
displayName: 'Open homepage in browser',
|
|
12
|
+
description: 'Open project homepage in web browser',
|
|
13
|
+
category: 'core',
|
|
14
|
+
requiresTmux: false,
|
|
15
|
+
dependencies: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static get validation() {
|
|
20
|
+
return {
|
|
21
|
+
validateConfig(config) {
|
|
22
|
+
// Web command accepts boolean or string 'true'
|
|
23
|
+
if (config === true || config === 'true' || config === false || config === 'false') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return 'Web configuration must be a boolean or string "true"/"false"';
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static get configuration() {
|
|
32
|
+
return {
|
|
33
|
+
async configureInteractive() {
|
|
34
|
+
// Web event just enables/disables opening the homepage
|
|
35
|
+
// The actual homepage URL is configured at the project level
|
|
36
|
+
return 'true';
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
getDefaultConfig() {
|
|
40
|
+
return 'true';
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static get processing() {
|
|
46
|
+
return {
|
|
47
|
+
async processEvent(context) {
|
|
48
|
+
const { project, isShellMode, shellCommands } = context;
|
|
49
|
+
const homepage = project.homepage;
|
|
50
|
+
|
|
51
|
+
if (!homepage) {
|
|
52
|
+
// No homepage configured, skip
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isShellMode) {
|
|
57
|
+
// Different approaches based on OS
|
|
58
|
+
let openCmd;
|
|
59
|
+
switch (process.platform) {
|
|
60
|
+
case 'darwin': openCmd = 'open'; break;
|
|
61
|
+
case 'win32': openCmd = 'start'; break;
|
|
62
|
+
default: openCmd = 'xdg-open'; break;
|
|
63
|
+
}
|
|
64
|
+
shellCommands.push(`${openCmd} "${homepage}" &`);
|
|
65
|
+
} else {
|
|
66
|
+
// In non-shell mode, use openurl2
|
|
67
|
+
require("openurl2").open(homepage);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
generateShellCommand(context) {
|
|
72
|
+
const { project } = context;
|
|
73
|
+
const homepage = project.homepage;
|
|
74
|
+
|
|
75
|
+
if (!homepage) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let openCmd;
|
|
80
|
+
switch (process.platform) {
|
|
81
|
+
case 'darwin': openCmd = 'open'; break;
|
|
82
|
+
case 'win32': openCmd = 'start'; break;
|
|
83
|
+
default: openCmd = 'xdg-open'; break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [`${openCmd} "${homepage}" &`];
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static get help() {
|
|
92
|
+
return {
|
|
93
|
+
usage: 'web: true',
|
|
94
|
+
description: 'Opens the project homepage in the default web browser',
|
|
95
|
+
examples: [
|
|
96
|
+
{
|
|
97
|
+
config: 'web: true',
|
|
98
|
+
description: 'Enable opening project homepage when switching to project'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
config: 'web: false',
|
|
102
|
+
description: 'Disable automatic homepage opening'
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = WebCommand;
|