workon 3.0.0 → 3.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/dist/cli.js +1919 -1273
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,27 +9,313 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
// src/lib/config.ts
|
|
13
|
+
import Conf from "conf";
|
|
14
|
+
var TRANSIENT_PROPS, Config;
|
|
15
|
+
var init_config = __esm({
|
|
16
|
+
"src/lib/config.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
TRANSIENT_PROPS = ["pkg", "work"];
|
|
19
|
+
Config = class {
|
|
20
|
+
_transient = {};
|
|
21
|
+
_store;
|
|
22
|
+
constructor() {
|
|
23
|
+
this._store = new Conf({
|
|
24
|
+
projectName: "workon"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
get(key, defaultValue) {
|
|
28
|
+
const rootKey = key.split(".")[0];
|
|
29
|
+
if (TRANSIENT_PROPS.includes(rootKey)) {
|
|
30
|
+
return this._transient[key] ?? defaultValue;
|
|
31
|
+
}
|
|
32
|
+
return this._store.get(key, defaultValue);
|
|
33
|
+
}
|
|
34
|
+
set(key, value) {
|
|
35
|
+
const rootKey = key.split(".")[0];
|
|
36
|
+
if (TRANSIENT_PROPS.includes(rootKey)) {
|
|
37
|
+
this._transient[key] = value;
|
|
38
|
+
} else {
|
|
39
|
+
if (value === void 0) {
|
|
40
|
+
this._store.set(key, value);
|
|
41
|
+
} else {
|
|
42
|
+
this._store.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
has(key) {
|
|
47
|
+
const rootKey = key.split(".")[0];
|
|
48
|
+
if (TRANSIENT_PROPS.includes(rootKey)) {
|
|
49
|
+
return Object.prototype.hasOwnProperty.call(this._transient, key);
|
|
50
|
+
}
|
|
51
|
+
return this._store.has(key);
|
|
52
|
+
}
|
|
53
|
+
delete(key) {
|
|
54
|
+
const rootKey = key.split(".")[0];
|
|
55
|
+
if (TRANSIENT_PROPS.includes(rootKey)) {
|
|
56
|
+
delete this._transient[key];
|
|
57
|
+
} else {
|
|
58
|
+
this._store.delete(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
getProjects() {
|
|
62
|
+
return this.get("projects") ?? {};
|
|
63
|
+
}
|
|
64
|
+
getProject(name) {
|
|
65
|
+
const projects = this.getProjects();
|
|
66
|
+
return projects[name];
|
|
67
|
+
}
|
|
68
|
+
setProject(name, config) {
|
|
69
|
+
const projects = this.getProjects();
|
|
70
|
+
projects[name] = config;
|
|
71
|
+
this.set("projects", projects);
|
|
72
|
+
}
|
|
73
|
+
deleteProject(name) {
|
|
74
|
+
const projects = this.getProjects();
|
|
75
|
+
delete projects[name];
|
|
76
|
+
this.set("projects", projects);
|
|
77
|
+
}
|
|
78
|
+
getDefaults() {
|
|
79
|
+
return this.get("project_defaults");
|
|
80
|
+
}
|
|
81
|
+
setDefaults(defaults) {
|
|
82
|
+
this.set("project_defaults", defaults);
|
|
83
|
+
}
|
|
84
|
+
get path() {
|
|
85
|
+
return this._store.path;
|
|
86
|
+
}
|
|
87
|
+
get store() {
|
|
88
|
+
return this._store.store;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
17
92
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
93
|
+
|
|
94
|
+
// src/lib/project.ts
|
|
95
|
+
import File from "phylo";
|
|
96
|
+
import deepAssign from "deep-assign";
|
|
97
|
+
var Project;
|
|
98
|
+
var init_project = __esm({
|
|
99
|
+
"src/lib/project.ts"() {
|
|
23
100
|
"use strict";
|
|
24
|
-
|
|
101
|
+
Project = class {
|
|
102
|
+
name;
|
|
103
|
+
_base;
|
|
104
|
+
_path;
|
|
105
|
+
_ide;
|
|
106
|
+
_events = {};
|
|
107
|
+
_branch;
|
|
108
|
+
_homepage;
|
|
109
|
+
_defaults;
|
|
110
|
+
_initialCfg;
|
|
111
|
+
constructor(name, cfg, defaults) {
|
|
112
|
+
this._defaults = defaults ?? { base: "" };
|
|
113
|
+
this._initialCfg = { path: name, events: {}, ...cfg };
|
|
114
|
+
this.name = cfg?.name ?? name;
|
|
115
|
+
const merged = deepAssign({}, this._defaults, this._initialCfg);
|
|
116
|
+
if (merged.base) {
|
|
117
|
+
this.base = merged.base;
|
|
118
|
+
}
|
|
119
|
+
if (merged.path) {
|
|
120
|
+
this.path = merged.path;
|
|
121
|
+
}
|
|
122
|
+
if (merged.ide) {
|
|
123
|
+
this._ide = merged.ide;
|
|
124
|
+
}
|
|
125
|
+
if (merged.events) {
|
|
126
|
+
this._events = merged.events;
|
|
127
|
+
}
|
|
128
|
+
if (merged.branch) {
|
|
129
|
+
this._branch = merged.branch;
|
|
130
|
+
}
|
|
131
|
+
if (merged.homepage) {
|
|
132
|
+
this._homepage = merged.homepage;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
set base(path) {
|
|
136
|
+
this._base = File.from(path).absolutify();
|
|
137
|
+
}
|
|
138
|
+
get base() {
|
|
139
|
+
return this._base;
|
|
140
|
+
}
|
|
141
|
+
set ide(cmd) {
|
|
142
|
+
this._ide = cmd;
|
|
143
|
+
}
|
|
144
|
+
get ide() {
|
|
145
|
+
return this._ide;
|
|
146
|
+
}
|
|
147
|
+
set events(eventCfg) {
|
|
148
|
+
this._events = eventCfg;
|
|
149
|
+
}
|
|
150
|
+
get events() {
|
|
151
|
+
return this._events;
|
|
152
|
+
}
|
|
153
|
+
set path(path) {
|
|
154
|
+
if (this._base) {
|
|
155
|
+
this._path = this._base.join(path);
|
|
156
|
+
} else {
|
|
157
|
+
this._path = File.from(path);
|
|
158
|
+
}
|
|
159
|
+
this._path = this._path.absolutify();
|
|
160
|
+
}
|
|
161
|
+
get path() {
|
|
162
|
+
if (!this._path) {
|
|
163
|
+
throw new Error("Project path not set");
|
|
164
|
+
}
|
|
165
|
+
return this._path;
|
|
166
|
+
}
|
|
167
|
+
set branch(branch) {
|
|
168
|
+
this._branch = branch;
|
|
169
|
+
}
|
|
170
|
+
get branch() {
|
|
171
|
+
return this._branch;
|
|
172
|
+
}
|
|
173
|
+
set homepage(url) {
|
|
174
|
+
this._homepage = url;
|
|
175
|
+
}
|
|
176
|
+
get homepage() {
|
|
177
|
+
return this._homepage;
|
|
178
|
+
}
|
|
179
|
+
static $isProject = true;
|
|
180
|
+
$isProject = true;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// src/lib/environment.ts
|
|
186
|
+
import File2 from "phylo";
|
|
187
|
+
import { simpleGit } from "simple-git";
|
|
188
|
+
var BaseEnvironment, ProjectEnvironment, EnvironmentRecognizer;
|
|
189
|
+
var init_environment = __esm({
|
|
190
|
+
"src/lib/environment.ts"() {
|
|
191
|
+
"use strict";
|
|
192
|
+
init_config();
|
|
193
|
+
init_project();
|
|
194
|
+
BaseEnvironment = class {
|
|
195
|
+
$isProjectEnvironment = false;
|
|
196
|
+
};
|
|
197
|
+
ProjectEnvironment = class _ProjectEnvironment {
|
|
198
|
+
$isProjectEnvironment = true;
|
|
199
|
+
project;
|
|
200
|
+
constructor(projectCfg) {
|
|
201
|
+
this.project = new Project(projectCfg.name, projectCfg);
|
|
202
|
+
}
|
|
203
|
+
static load(cfg, defaults) {
|
|
204
|
+
const project = new Project(cfg.name, cfg, defaults);
|
|
205
|
+
return new _ProjectEnvironment({ ...cfg, name: project.name });
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
EnvironmentRecognizer = class {
|
|
209
|
+
static config;
|
|
210
|
+
static log;
|
|
211
|
+
static projects = [];
|
|
212
|
+
static configured = false;
|
|
213
|
+
static configure(config, log) {
|
|
214
|
+
if (this.configured) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.config = config;
|
|
218
|
+
this.log = log;
|
|
219
|
+
this.configured = true;
|
|
220
|
+
}
|
|
221
|
+
static async recognize(dir) {
|
|
222
|
+
this.ensureConfigured();
|
|
223
|
+
const theDir = File2.from(dir).canonicalize();
|
|
224
|
+
this.log.debug("Directory to recognize is: " + theDir.canonicalPath());
|
|
225
|
+
const allProjects = this.getAllProjects();
|
|
226
|
+
const matching = allProjects.filter((p) => p.path.canonicalPath() === theDir.path);
|
|
227
|
+
if (matching.length === 0) {
|
|
228
|
+
return new BaseEnvironment();
|
|
229
|
+
}
|
|
230
|
+
this.log.debug(`Found ${matching.length} matching projects`);
|
|
231
|
+
const base = matching.find((p) => !p.name.includes("#")) ?? matching[0];
|
|
232
|
+
this.log.debug("Base project is: " + base.name);
|
|
233
|
+
const gitDir = base.path.up(".git");
|
|
234
|
+
if (gitDir) {
|
|
235
|
+
try {
|
|
236
|
+
const git = simpleGit(gitDir.path);
|
|
237
|
+
const branchSummary = await git.branchLocal();
|
|
238
|
+
base.branch = branchSummary.current;
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return this.getProjectEnvironment(base, matching);
|
|
243
|
+
}
|
|
244
|
+
static getAllProjects(refresh = false) {
|
|
245
|
+
if (this.projects.length > 0 && !refresh) {
|
|
246
|
+
return this.projects;
|
|
247
|
+
}
|
|
248
|
+
const defaults = this.config.getDefaults();
|
|
249
|
+
if (!defaults?.base) {
|
|
250
|
+
this.projects = [];
|
|
251
|
+
return this.projects;
|
|
252
|
+
}
|
|
253
|
+
const baseDir = File2.from(defaults.base);
|
|
254
|
+
const projectsMap = this.config.getProjects();
|
|
255
|
+
this.projects = Object.entries(projectsMap).map(([name, project]) => ({
|
|
256
|
+
...project,
|
|
257
|
+
name,
|
|
258
|
+
path: baseDir.join(project.path)
|
|
259
|
+
}));
|
|
260
|
+
return this.projects;
|
|
261
|
+
}
|
|
262
|
+
static getProjectEnvironment(base, _matching) {
|
|
263
|
+
const exactName = `${base.name}#${base.branch}`;
|
|
264
|
+
const exactProj = this.projects.find((p) => p.name === exactName);
|
|
265
|
+
const toProjectConfig = (p) => ({
|
|
266
|
+
name: p.name,
|
|
267
|
+
path: p.path.path,
|
|
268
|
+
// Convert PhyloFile to string path
|
|
269
|
+
ide: p.ide,
|
|
270
|
+
homepage: p.homepage,
|
|
271
|
+
events: p.events,
|
|
272
|
+
branch: p.branch,
|
|
273
|
+
exactName
|
|
274
|
+
});
|
|
275
|
+
if (exactProj) {
|
|
276
|
+
return new ProjectEnvironment({ ...toProjectConfig(exactProj), branch: base.branch });
|
|
277
|
+
}
|
|
278
|
+
return new ProjectEnvironment(toProjectConfig(base));
|
|
279
|
+
}
|
|
280
|
+
static ensureConfigured() {
|
|
281
|
+
if (!this.configured) {
|
|
282
|
+
this.config = new Config();
|
|
283
|
+
this.log = {
|
|
284
|
+
debug: () => {
|
|
285
|
+
},
|
|
286
|
+
info: () => {
|
|
287
|
+
},
|
|
288
|
+
log: () => {
|
|
289
|
+
},
|
|
290
|
+
warn: () => {
|
|
291
|
+
},
|
|
292
|
+
error: () => {
|
|
293
|
+
},
|
|
294
|
+
setLogLevel: () => {
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
this.configured = true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// src/events/core/cwd.ts
|
|
305
|
+
import { spawn as spawn2 } from "child_process";
|
|
306
|
+
var CwdEvent;
|
|
307
|
+
var init_cwd = __esm({
|
|
308
|
+
"src/events/core/cwd.ts"() {
|
|
309
|
+
"use strict";
|
|
310
|
+
CwdEvent = class {
|
|
25
311
|
static get metadata() {
|
|
26
312
|
return {
|
|
27
|
-
name: "
|
|
28
|
-
displayName: "
|
|
29
|
-
description: "
|
|
30
|
-
category: "
|
|
31
|
-
requiresTmux:
|
|
32
|
-
dependencies: [
|
|
313
|
+
name: "cwd",
|
|
314
|
+
displayName: "Change directory (cwd)",
|
|
315
|
+
description: "Change current working directory to project path",
|
|
316
|
+
category: "core",
|
|
317
|
+
requiresTmux: false,
|
|
318
|
+
dependencies: []
|
|
33
319
|
};
|
|
34
320
|
}
|
|
35
321
|
static get validation() {
|
|
@@ -38,1360 +324,1573 @@ var init_npm = __esm({
|
|
|
38
324
|
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
39
325
|
return true;
|
|
40
326
|
}
|
|
41
|
-
|
|
42
|
-
if (config.trim().length === 0) {
|
|
43
|
-
return "npm script name cannot be empty";
|
|
44
|
-
}
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
if (typeof config === "object" && config !== null) {
|
|
48
|
-
const cfg = config;
|
|
49
|
-
if (typeof cfg.command !== "string" || cfg.command.trim().length === 0) {
|
|
50
|
-
return "npm.command must be a non-empty string";
|
|
51
|
-
}
|
|
52
|
-
if (cfg.watch !== void 0 && typeof cfg.watch !== "boolean") {
|
|
53
|
-
return "npm.watch must be a boolean";
|
|
54
|
-
}
|
|
55
|
-
if (cfg.auto_restart !== void 0 && typeof cfg.auto_restart !== "boolean") {
|
|
56
|
-
return "npm.auto_restart must be a boolean";
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return "npm config must be a boolean, string (script name), or object";
|
|
327
|
+
return "cwd config must be a boolean (true/false)";
|
|
61
328
|
}
|
|
62
329
|
};
|
|
63
330
|
}
|
|
64
331
|
static get configuration() {
|
|
65
332
|
return {
|
|
66
333
|
async configureInteractive() {
|
|
67
|
-
|
|
68
|
-
message: "Enter NPM script to run:",
|
|
69
|
-
default: "dev"
|
|
70
|
-
});
|
|
71
|
-
const useAdvanced = await confirm2({
|
|
72
|
-
message: "Configure advanced NPM options?",
|
|
73
|
-
default: false
|
|
74
|
-
});
|
|
75
|
-
if (!useAdvanced) {
|
|
76
|
-
return scriptName;
|
|
77
|
-
}
|
|
78
|
-
const watch = await confirm2({
|
|
79
|
-
message: "Enable watch mode?",
|
|
80
|
-
default: false
|
|
81
|
-
});
|
|
82
|
-
const autoRestart = await confirm2({
|
|
83
|
-
message: "Auto-restart on crash?",
|
|
84
|
-
default: false
|
|
85
|
-
});
|
|
86
|
-
if (!watch && !autoRestart) {
|
|
87
|
-
return scriptName;
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
command: scriptName,
|
|
91
|
-
watch,
|
|
92
|
-
auto_restart: autoRestart
|
|
93
|
-
};
|
|
334
|
+
return true;
|
|
94
335
|
},
|
|
95
336
|
getDefaultConfig() {
|
|
96
|
-
return
|
|
337
|
+
return true;
|
|
97
338
|
}
|
|
98
339
|
};
|
|
99
340
|
}
|
|
100
|
-
static getNpmCommand(config) {
|
|
101
|
-
if (typeof config === "boolean" || config === void 0) {
|
|
102
|
-
return "npm run dev";
|
|
103
|
-
}
|
|
104
|
-
if (typeof config === "string") {
|
|
105
|
-
return `npm run ${config}`;
|
|
106
|
-
}
|
|
107
|
-
return `npm run ${config.command}`;
|
|
108
|
-
}
|
|
109
341
|
static get processing() {
|
|
110
342
|
return {
|
|
111
343
|
async processEvent(context) {
|
|
112
344
|
const { project, isShellMode, shellCommands } = context;
|
|
113
|
-
const
|
|
114
|
-
const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
|
|
345
|
+
const projectPath = project.path.path;
|
|
115
346
|
if (isShellMode) {
|
|
116
|
-
shellCommands.push(
|
|
347
|
+
shellCommands.push(`cd "${projectPath}"`);
|
|
117
348
|
} else {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
cwd:
|
|
349
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
350
|
+
spawn2(shell, [], {
|
|
351
|
+
cwd: projectPath,
|
|
121
352
|
stdio: "inherit"
|
|
122
353
|
});
|
|
123
354
|
}
|
|
124
355
|
},
|
|
125
356
|
generateShellCommand(context) {
|
|
126
|
-
const
|
|
127
|
-
return [
|
|
357
|
+
const projectPath = context.project.path.path;
|
|
358
|
+
return [`cd "${projectPath}"`];
|
|
128
359
|
}
|
|
129
360
|
};
|
|
130
361
|
}
|
|
131
362
|
static get tmux() {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
static get help() {
|
|
132
366
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
367
|
+
usage: "cwd: true | false",
|
|
368
|
+
description: "Change the current working directory to the project path",
|
|
369
|
+
examples: [
|
|
370
|
+
{ config: true, description: "Enable directory change" },
|
|
371
|
+
{ config: false, description: "Disable directory change" }
|
|
372
|
+
]
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// src/events/core/ide.ts
|
|
380
|
+
import { spawn as spawn3 } from "child_process";
|
|
381
|
+
var IdeEvent;
|
|
382
|
+
var init_ide = __esm({
|
|
383
|
+
"src/events/core/ide.ts"() {
|
|
384
|
+
"use strict";
|
|
385
|
+
IdeEvent = class {
|
|
386
|
+
static get metadata() {
|
|
387
|
+
return {
|
|
388
|
+
name: "ide",
|
|
389
|
+
displayName: "Open in IDE",
|
|
390
|
+
description: "Open project in configured IDE/editor",
|
|
391
|
+
category: "core",
|
|
392
|
+
requiresTmux: false,
|
|
393
|
+
dependencies: []
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
static get validation() {
|
|
397
|
+
return {
|
|
398
|
+
validateConfig(config) {
|
|
399
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
return "ide config must be a boolean (true/false)";
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
static get configuration() {
|
|
407
|
+
return {
|
|
408
|
+
async configureInteractive() {
|
|
409
|
+
return true;
|
|
135
410
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
411
|
+
getDefaultConfig() {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
static get processing() {
|
|
417
|
+
return {
|
|
418
|
+
async processEvent(context) {
|
|
419
|
+
const { project, isShellMode, shellCommands } = context;
|
|
420
|
+
const projectPath = project.path.path;
|
|
421
|
+
const ide = project.ide || "code";
|
|
422
|
+
if (isShellMode) {
|
|
423
|
+
shellCommands.push(`${ide} "${projectPath}" &`);
|
|
424
|
+
} else {
|
|
425
|
+
spawn3(ide, [projectPath], {
|
|
426
|
+
detached: true,
|
|
427
|
+
stdio: "ignore"
|
|
428
|
+
}).unref();
|
|
139
429
|
}
|
|
140
|
-
|
|
430
|
+
},
|
|
431
|
+
generateShellCommand(context) {
|
|
432
|
+
const projectPath = context.project.path.path;
|
|
433
|
+
const ide = context.project.ide || "code";
|
|
434
|
+
return [`${ide} "${projectPath}" &`];
|
|
141
435
|
}
|
|
142
436
|
};
|
|
143
437
|
}
|
|
438
|
+
static get tmux() {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
144
441
|
static get help() {
|
|
145
442
|
return {
|
|
146
|
-
usage:
|
|
147
|
-
description: "
|
|
443
|
+
usage: "ide: true | false",
|
|
444
|
+
description: "Open the project in the configured IDE",
|
|
148
445
|
examples: [
|
|
149
|
-
{ config: true, description: "
|
|
150
|
-
{ config:
|
|
151
|
-
{ config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
|
|
446
|
+
{ config: true, description: "Enable IDE opening" },
|
|
447
|
+
{ config: false, description: "Disable IDE opening" }
|
|
152
448
|
]
|
|
153
449
|
};
|
|
154
450
|
}
|
|
155
451
|
};
|
|
156
|
-
npm_default = NpmEvent;
|
|
157
452
|
}
|
|
158
453
|
});
|
|
159
454
|
|
|
160
|
-
// src/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
|
|
186
|
-
const { log, environment } = ctx;
|
|
187
|
-
log.debug(`Name '${defaultName}' was${fromUser ? "" : " not"} provided by the user`);
|
|
188
|
-
const question = getFirstQuestion(defaultName, fromUser, environment, showMain);
|
|
189
|
-
const action = await select(question);
|
|
190
|
-
switch (action) {
|
|
191
|
-
case "exit":
|
|
192
|
-
return;
|
|
193
|
-
case "more":
|
|
194
|
-
await startInteractive(defaultName, fromUser, ctx, true);
|
|
195
|
-
return;
|
|
196
|
-
case "init-project":
|
|
197
|
-
await initProject(defaultName, fromUser, ctx);
|
|
198
|
-
return;
|
|
199
|
-
case "init-branch":
|
|
200
|
-
await initBranch(defaultName, ctx);
|
|
201
|
-
return;
|
|
202
|
-
case "switch-project":
|
|
203
|
-
log.info("Switch to an existing project");
|
|
204
|
-
break;
|
|
205
|
-
case "switch-branch":
|
|
206
|
-
log.info("Switch to an existing branch");
|
|
207
|
-
break;
|
|
208
|
-
case "manage-projects":
|
|
209
|
-
log.info("Manage existing projects");
|
|
210
|
-
break;
|
|
211
|
-
case "manage-branches":
|
|
212
|
-
log.info("Manage existing branches");
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
function getFirstQuestion(defaultName, fromUser, environment, showMain) {
|
|
217
|
-
if (!showMain && environment.$isProjectEnvironment && !fromUser) {
|
|
218
|
-
return {
|
|
219
|
-
message: environment.project.name,
|
|
220
|
-
choices: [
|
|
221
|
-
{ name: "Start a branch", value: "init-branch" },
|
|
222
|
-
{ name: "Switch branch", value: "switch-branch" },
|
|
223
|
-
{ name: "Manage branches", value: "manage-branches" },
|
|
224
|
-
{ name: "---", value: "" },
|
|
225
|
-
{ name: "More...", value: "more" },
|
|
226
|
-
{ name: "Exit", value: "exit" }
|
|
227
|
-
].filter((c) => c.value !== "")
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
message: "What do you want to do?",
|
|
232
|
-
choices: [
|
|
233
|
-
{ name: "Start a new project", value: "init-project" },
|
|
234
|
-
{ name: "Open an existing project", value: "switch-project" },
|
|
235
|
-
{ name: "Manage projects", value: "manage-projects" },
|
|
236
|
-
{ name: "---", value: "" },
|
|
237
|
-
{ name: "Exit", value: "exit" }
|
|
238
|
-
].filter((c) => c.value !== "")
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
async function initProject(defaultName, fromUser, ctx) {
|
|
242
|
-
const { config, log } = ctx;
|
|
243
|
-
const defaults = config.getDefaults();
|
|
244
|
-
const projects = config.getProjects();
|
|
245
|
-
let name;
|
|
246
|
-
if (fromUser) {
|
|
247
|
-
name = defaultName;
|
|
248
|
-
log.log(`Project name: ${name}`);
|
|
249
|
-
} else {
|
|
250
|
-
name = await input4({
|
|
251
|
-
message: "What is the name of the project?",
|
|
252
|
-
default: defaultName,
|
|
253
|
-
validate: (value) => {
|
|
254
|
-
if (value in projects) return "Project already exists.";
|
|
255
|
-
if (/\w+#\w+/.test(value)) {
|
|
256
|
-
const projectName = value.substring(0, value.indexOf("#"));
|
|
257
|
-
if (!(projectName in projects)) {
|
|
258
|
-
return `Project '${projectName}' does not exist. Please create it before starting a branch.`;
|
|
455
|
+
// src/events/core/web.ts
|
|
456
|
+
import { spawn as spawn4 } from "child_process";
|
|
457
|
+
import { platform } from "os";
|
|
458
|
+
var WebEvent;
|
|
459
|
+
var init_web = __esm({
|
|
460
|
+
"src/events/core/web.ts"() {
|
|
461
|
+
"use strict";
|
|
462
|
+
WebEvent = class _WebEvent {
|
|
463
|
+
static get metadata() {
|
|
464
|
+
return {
|
|
465
|
+
name: "web",
|
|
466
|
+
displayName: "Open homepage in browser",
|
|
467
|
+
description: "Open project homepage in web browser",
|
|
468
|
+
category: "core",
|
|
469
|
+
requiresTmux: false,
|
|
470
|
+
dependencies: []
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
static get validation() {
|
|
474
|
+
return {
|
|
475
|
+
validateConfig(config) {
|
|
476
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
return "web config must be a boolean (true/false)";
|
|
259
480
|
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
static get configuration() {
|
|
484
|
+
return {
|
|
485
|
+
async configureInteractive() {
|
|
486
|
+
return true;
|
|
487
|
+
},
|
|
488
|
+
getDefaultConfig() {
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
static getOpenCommand() {
|
|
494
|
+
const os = platform();
|
|
495
|
+
switch (os) {
|
|
496
|
+
case "darwin":
|
|
497
|
+
return "open";
|
|
498
|
+
case "win32":
|
|
499
|
+
return "start";
|
|
500
|
+
default:
|
|
501
|
+
return "xdg-open";
|
|
260
502
|
}
|
|
261
|
-
return true;
|
|
262
503
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
504
|
+
static get processing() {
|
|
505
|
+
return {
|
|
506
|
+
async processEvent(context) {
|
|
507
|
+
const { project, isShellMode, shellCommands } = context;
|
|
508
|
+
const homepage = project.homepage;
|
|
509
|
+
if (!homepage) {
|
|
510
|
+
console.warn("No homepage configured for project");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const openCmd = _WebEvent.getOpenCommand();
|
|
514
|
+
if (isShellMode) {
|
|
515
|
+
shellCommands.push(`${openCmd} "${homepage}" &`);
|
|
516
|
+
} else {
|
|
517
|
+
spawn4(openCmd, [homepage], {
|
|
518
|
+
detached: true,
|
|
519
|
+
stdio: "ignore"
|
|
520
|
+
}).unref();
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
generateShellCommand(context) {
|
|
524
|
+
const homepage = context.project.homepage;
|
|
525
|
+
if (!homepage) return [];
|
|
526
|
+
const openCmd = _WebEvent.getOpenCommand();
|
|
527
|
+
return [`${openCmd} "${homepage}" &`];
|
|
528
|
+
}
|
|
529
|
+
};
|
|
287
530
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
]
|
|
303
|
-
});
|
|
304
|
-
const events = {
|
|
305
|
-
cwd: selectedEvents.includes("cwd"),
|
|
306
|
-
ide: selectedEvents.includes("ide")
|
|
307
|
-
};
|
|
308
|
-
const projectConfig = {
|
|
309
|
-
path: basePath,
|
|
310
|
-
ide,
|
|
311
|
-
events
|
|
312
|
-
};
|
|
313
|
-
projects[name] = projectConfig;
|
|
314
|
-
config.set("projects", projects);
|
|
315
|
-
log.info("Your project has been initialized.");
|
|
316
|
-
log.info(`Use 'workon ${name}' to start working!`);
|
|
317
|
-
}
|
|
318
|
-
async function initBranch(defaultName, ctx) {
|
|
319
|
-
const { config, log } = ctx;
|
|
320
|
-
const projects = config.getProjects();
|
|
321
|
-
const branch = await input4({
|
|
322
|
-
message: "What is the name of the branch?",
|
|
323
|
-
validate: (value) => {
|
|
324
|
-
if (/\w+#\w+/.test(value)) return `Branch name can't contain the "#" sign`;
|
|
325
|
-
if (`${defaultName}#${value}` in projects) return "Branch already exists.";
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
const branchName = `${defaultName}#${branch}`;
|
|
330
|
-
const baseProject = projects[defaultName];
|
|
331
|
-
const branchConfig = deepAssign2({}, baseProject, { branch });
|
|
332
|
-
delete branchConfig.name;
|
|
333
|
-
projects[branchName] = branchConfig;
|
|
334
|
-
config.set("projects", projects);
|
|
335
|
-
log.info("Your branch configuration has been initialized.");
|
|
336
|
-
log.info(`Use 'workon ${branchName}' to start working!`);
|
|
337
|
-
}
|
|
338
|
-
var IDE_CHOICES;
|
|
339
|
-
var init_interactive = __esm({
|
|
340
|
-
"src/commands/interactive.ts"() {
|
|
341
|
-
"use strict";
|
|
342
|
-
IDE_CHOICES = [
|
|
343
|
-
{ name: "Visual Studio Code", value: "vscode" },
|
|
344
|
-
{ name: "IntelliJ IDEA", value: "idea" },
|
|
345
|
-
{ name: "Atom", value: "atom" }
|
|
346
|
-
];
|
|
531
|
+
static get tmux() {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
static get help() {
|
|
535
|
+
return {
|
|
536
|
+
usage: "web: true | false",
|
|
537
|
+
description: "Open the project homepage in the default browser",
|
|
538
|
+
examples: [
|
|
539
|
+
{ config: true, description: "Enable browser opening" },
|
|
540
|
+
{ config: false, description: "Disable browser opening" }
|
|
541
|
+
]
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
};
|
|
347
545
|
}
|
|
348
546
|
});
|
|
349
547
|
|
|
350
|
-
// src/
|
|
351
|
-
import {
|
|
352
|
-
import {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
projectName: "workon"
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
get(key, defaultValue) {
|
|
371
|
-
const rootKey = key.split(".")[0];
|
|
372
|
-
if (TRANSIENT_PROPS.includes(rootKey)) {
|
|
373
|
-
return this._transient[key] ?? defaultValue;
|
|
374
|
-
}
|
|
375
|
-
return this._store.get(key, defaultValue);
|
|
376
|
-
}
|
|
377
|
-
set(key, value) {
|
|
378
|
-
const rootKey = key.split(".")[0];
|
|
379
|
-
if (TRANSIENT_PROPS.includes(rootKey)) {
|
|
380
|
-
this._transient[key] = value;
|
|
381
|
-
} else {
|
|
382
|
-
if (value === void 0) {
|
|
383
|
-
this._store.set(key, value);
|
|
384
|
-
} else {
|
|
385
|
-
this._store.set(key, value);
|
|
548
|
+
// src/events/extensions/claude.ts
|
|
549
|
+
import { spawn as spawn5 } from "child_process";
|
|
550
|
+
import { input, confirm } from "@inquirer/prompts";
|
|
551
|
+
var ClaudeEvent;
|
|
552
|
+
var init_claude = __esm({
|
|
553
|
+
"src/events/extensions/claude.ts"() {
|
|
554
|
+
"use strict";
|
|
555
|
+
ClaudeEvent = class _ClaudeEvent {
|
|
556
|
+
static get metadata() {
|
|
557
|
+
return {
|
|
558
|
+
name: "claude",
|
|
559
|
+
displayName: "Launch Claude Code",
|
|
560
|
+
description: "Launch Claude Code with optional flags and configuration",
|
|
561
|
+
category: "development",
|
|
562
|
+
requiresTmux: true,
|
|
563
|
+
dependencies: ["claude"]
|
|
564
|
+
};
|
|
386
565
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
deleteProject(name) {
|
|
417
|
-
const projects = this.getProjects();
|
|
418
|
-
delete projects[name];
|
|
419
|
-
this.set("projects", projects);
|
|
420
|
-
}
|
|
421
|
-
getDefaults() {
|
|
422
|
-
return this.get("project_defaults");
|
|
423
|
-
}
|
|
424
|
-
setDefaults(defaults) {
|
|
425
|
-
this.set("project_defaults", defaults);
|
|
426
|
-
}
|
|
427
|
-
get path() {
|
|
428
|
-
return this._store.path;
|
|
429
|
-
}
|
|
430
|
-
get store() {
|
|
431
|
-
return this._store.store;
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// src/lib/environment.ts
|
|
436
|
-
import File2 from "phylo";
|
|
437
|
-
import { simpleGit } from "simple-git";
|
|
438
|
-
|
|
439
|
-
// src/lib/project.ts
|
|
440
|
-
import File from "phylo";
|
|
441
|
-
import deepAssign from "deep-assign";
|
|
442
|
-
var Project = class {
|
|
443
|
-
name;
|
|
444
|
-
_base;
|
|
445
|
-
_path;
|
|
446
|
-
_ide;
|
|
447
|
-
_events = {};
|
|
448
|
-
_branch;
|
|
449
|
-
_homepage;
|
|
450
|
-
_defaults;
|
|
451
|
-
_initialCfg;
|
|
452
|
-
constructor(name, cfg, defaults) {
|
|
453
|
-
this._defaults = defaults ?? { base: "" };
|
|
454
|
-
this._initialCfg = { path: name, events: {}, ...cfg };
|
|
455
|
-
this.name = cfg?.name ?? name;
|
|
456
|
-
const merged = deepAssign({}, this._defaults, this._initialCfg);
|
|
457
|
-
if (merged.base) {
|
|
458
|
-
this.base = merged.base;
|
|
459
|
-
}
|
|
460
|
-
if (merged.path) {
|
|
461
|
-
this.path = merged.path;
|
|
462
|
-
}
|
|
463
|
-
if (merged.ide) {
|
|
464
|
-
this._ide = merged.ide;
|
|
465
|
-
}
|
|
466
|
-
if (merged.events) {
|
|
467
|
-
this._events = merged.events;
|
|
468
|
-
}
|
|
469
|
-
if (merged.branch) {
|
|
470
|
-
this._branch = merged.branch;
|
|
471
|
-
}
|
|
472
|
-
if (merged.homepage) {
|
|
473
|
-
this._homepage = merged.homepage;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
set base(path) {
|
|
477
|
-
this._base = File.from(path).absolutify();
|
|
478
|
-
}
|
|
479
|
-
get base() {
|
|
480
|
-
return this._base;
|
|
481
|
-
}
|
|
482
|
-
set ide(cmd) {
|
|
483
|
-
this._ide = cmd;
|
|
484
|
-
}
|
|
485
|
-
get ide() {
|
|
486
|
-
return this._ide;
|
|
487
|
-
}
|
|
488
|
-
set events(eventCfg) {
|
|
489
|
-
this._events = eventCfg;
|
|
490
|
-
}
|
|
491
|
-
get events() {
|
|
492
|
-
return this._events;
|
|
493
|
-
}
|
|
494
|
-
set path(path) {
|
|
495
|
-
if (this._base) {
|
|
496
|
-
this._path = this._base.join(path);
|
|
497
|
-
} else {
|
|
498
|
-
this._path = File.from(path);
|
|
499
|
-
}
|
|
500
|
-
this._path = this._path.absolutify();
|
|
501
|
-
}
|
|
502
|
-
get path() {
|
|
503
|
-
if (!this._path) {
|
|
504
|
-
throw new Error("Project path not set");
|
|
505
|
-
}
|
|
506
|
-
return this._path;
|
|
507
|
-
}
|
|
508
|
-
set branch(branch) {
|
|
509
|
-
this._branch = branch;
|
|
510
|
-
}
|
|
511
|
-
get branch() {
|
|
512
|
-
return this._branch;
|
|
513
|
-
}
|
|
514
|
-
set homepage(url) {
|
|
515
|
-
this._homepage = url;
|
|
516
|
-
}
|
|
517
|
-
get homepage() {
|
|
518
|
-
return this._homepage;
|
|
519
|
-
}
|
|
520
|
-
static $isProject = true;
|
|
521
|
-
$isProject = true;
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
// src/lib/environment.ts
|
|
525
|
-
var BaseEnvironment = class {
|
|
526
|
-
$isProjectEnvironment = false;
|
|
527
|
-
};
|
|
528
|
-
var ProjectEnvironment = class _ProjectEnvironment {
|
|
529
|
-
$isProjectEnvironment = true;
|
|
530
|
-
project;
|
|
531
|
-
constructor(projectCfg) {
|
|
532
|
-
this.project = new Project(projectCfg.name, projectCfg);
|
|
533
|
-
}
|
|
534
|
-
static load(cfg, defaults) {
|
|
535
|
-
const project = new Project(cfg.name, cfg, defaults);
|
|
536
|
-
return new _ProjectEnvironment({ ...cfg, name: project.name });
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
var EnvironmentRecognizer = class {
|
|
540
|
-
static config;
|
|
541
|
-
static log;
|
|
542
|
-
static projects = [];
|
|
543
|
-
static configured = false;
|
|
544
|
-
static configure(config, log) {
|
|
545
|
-
if (this.configured) {
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
this.config = config;
|
|
549
|
-
this.log = log;
|
|
550
|
-
this.configured = true;
|
|
551
|
-
}
|
|
552
|
-
static async recognize(dir) {
|
|
553
|
-
this.ensureConfigured();
|
|
554
|
-
const theDir = File2.from(dir).canonicalize();
|
|
555
|
-
this.log.debug("Directory to recognize is: " + theDir.canonicalPath());
|
|
556
|
-
const allProjects = this.getAllProjects();
|
|
557
|
-
const matching = allProjects.filter((p) => p.path.canonicalPath() === theDir.path);
|
|
558
|
-
if (matching.length === 0) {
|
|
559
|
-
return new BaseEnvironment();
|
|
560
|
-
}
|
|
561
|
-
this.log.debug(`Found ${matching.length} matching projects`);
|
|
562
|
-
const base = matching.find((p) => !p.name.includes("#")) ?? matching[0];
|
|
563
|
-
this.log.debug("Base project is: " + base.name);
|
|
564
|
-
const gitDir = base.path.up(".git");
|
|
565
|
-
if (gitDir) {
|
|
566
|
-
try {
|
|
567
|
-
const git = simpleGit(gitDir.path);
|
|
568
|
-
const branchSummary = await git.branchLocal();
|
|
569
|
-
base.branch = branchSummary.current;
|
|
570
|
-
} catch {
|
|
566
|
+
static get validation() {
|
|
567
|
+
return {
|
|
568
|
+
validateConfig(config) {
|
|
569
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
if (typeof config === "object" && config !== null) {
|
|
573
|
+
const cfg = config;
|
|
574
|
+
if (cfg.flags !== void 0) {
|
|
575
|
+
if (!Array.isArray(cfg.flags)) {
|
|
576
|
+
return "claude.flags must be an array of strings";
|
|
577
|
+
}
|
|
578
|
+
for (const flag of cfg.flags) {
|
|
579
|
+
if (typeof flag !== "string") {
|
|
580
|
+
return "claude.flags must contain only strings";
|
|
581
|
+
}
|
|
582
|
+
if (!flag.startsWith("-")) {
|
|
583
|
+
return `Invalid flag "${flag}": flags must start with - or --`;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (cfg.split_terminal !== void 0 && typeof cfg.split_terminal !== "boolean") {
|
|
588
|
+
return "claude.split_terminal must be a boolean";
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
return "claude config must be a boolean or object with flags/split_terminal";
|
|
593
|
+
}
|
|
594
|
+
};
|
|
571
595
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
return new ProjectEnvironment({ ...toProjectConfig(exactProj), branch: base.branch });
|
|
608
|
-
}
|
|
609
|
-
return new ProjectEnvironment(toProjectConfig(base));
|
|
610
|
-
}
|
|
611
|
-
static ensureConfigured() {
|
|
612
|
-
if (!this.configured) {
|
|
613
|
-
this.config = new Config();
|
|
614
|
-
this.log = {
|
|
615
|
-
debug: () => {
|
|
616
|
-
},
|
|
617
|
-
info: () => {
|
|
618
|
-
},
|
|
619
|
-
log: () => {
|
|
620
|
-
},
|
|
621
|
-
warn: () => {
|
|
622
|
-
},
|
|
623
|
-
error: () => {
|
|
624
|
-
},
|
|
625
|
-
setLogLevel: () => {
|
|
596
|
+
static get configuration() {
|
|
597
|
+
return {
|
|
598
|
+
async configureInteractive() {
|
|
599
|
+
const useAdvanced = await confirm({
|
|
600
|
+
message: "Configure advanced Claude options?",
|
|
601
|
+
default: false
|
|
602
|
+
});
|
|
603
|
+
if (!useAdvanced) {
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
const flagsInput = await input({
|
|
607
|
+
message: "Enter Claude flags (comma-separated, e.g., --resume, --debug):",
|
|
608
|
+
default: ""
|
|
609
|
+
});
|
|
610
|
+
const flags = flagsInput.split(",").map((f) => f.trim()).filter((f) => f.length > 0 && f.startsWith("-"));
|
|
611
|
+
const splitTerminal = await confirm({
|
|
612
|
+
message: "Use split terminal layout (Claude + shell)?",
|
|
613
|
+
default: true
|
|
614
|
+
});
|
|
615
|
+
if (flags.length === 0 && !splitTerminal) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
const config = {};
|
|
619
|
+
if (flags.length > 0) config.flags = flags;
|
|
620
|
+
if (splitTerminal) config.split_terminal = splitTerminal;
|
|
621
|
+
return config;
|
|
622
|
+
},
|
|
623
|
+
getDefaultConfig() {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
static getClaudeCommand(config) {
|
|
629
|
+
if (typeof config === "boolean" || config === void 0) {
|
|
630
|
+
return "claude";
|
|
626
631
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
// src/commands/open.ts
|
|
634
|
-
import { Command } from "commander";
|
|
635
|
-
import File4 from "phylo";
|
|
636
|
-
|
|
637
|
-
// src/lib/tmux.ts
|
|
638
|
-
import { exec as execCallback, spawn } from "child_process";
|
|
639
|
-
import { promisify } from "util";
|
|
640
|
-
var exec = promisify(execCallback);
|
|
641
|
-
var TmuxManager = class {
|
|
642
|
-
sessionPrefix = "workon-";
|
|
643
|
-
async isTmuxAvailable() {
|
|
644
|
-
try {
|
|
645
|
-
await exec("which tmux");
|
|
646
|
-
return true;
|
|
647
|
-
} catch {
|
|
648
|
-
return false;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
async sessionExists(sessionName) {
|
|
652
|
-
try {
|
|
653
|
-
await exec(`tmux has-session -t "${sessionName}"`);
|
|
654
|
-
return true;
|
|
655
|
-
} catch {
|
|
656
|
-
return false;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
getSessionName(projectName) {
|
|
660
|
-
return `${this.sessionPrefix}${projectName}`;
|
|
661
|
-
}
|
|
662
|
-
async killSession(sessionName) {
|
|
663
|
-
try {
|
|
664
|
-
await exec(`tmux kill-session -t "${sessionName}"`);
|
|
665
|
-
return true;
|
|
666
|
-
} catch {
|
|
667
|
-
return false;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
671
|
-
const sessionName = this.getSessionName(projectName);
|
|
672
|
-
if (await this.sessionExists(sessionName)) {
|
|
673
|
-
await this.killSession(sessionName);
|
|
674
|
-
}
|
|
675
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
676
|
-
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
677
|
-
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
678
|
-
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
679
|
-
return sessionName;
|
|
680
|
-
}
|
|
681
|
-
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
682
|
-
const sessionName = this.getSessionName(projectName);
|
|
683
|
-
if (await this.sessionExists(sessionName)) {
|
|
684
|
-
await this.killSession(sessionName);
|
|
685
|
-
}
|
|
686
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
687
|
-
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
688
|
-
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
689
|
-
await exec(`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`);
|
|
690
|
-
await exec(`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`);
|
|
691
|
-
await exec(`tmux resize-pane -t "${sessionName}:0.2" -y 10`);
|
|
692
|
-
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
693
|
-
return sessionName;
|
|
694
|
-
}
|
|
695
|
-
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
696
|
-
const sessionName = this.getSessionName(projectName);
|
|
697
|
-
if (await this.sessionExists(sessionName)) {
|
|
698
|
-
await this.killSession(sessionName);
|
|
699
|
-
}
|
|
700
|
-
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`);
|
|
701
|
-
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`);
|
|
702
|
-
await exec(`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`);
|
|
703
|
-
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
704
|
-
return sessionName;
|
|
705
|
-
}
|
|
706
|
-
async attachToSession(sessionName) {
|
|
707
|
-
if (process.env.TMUX) {
|
|
708
|
-
await exec(`tmux switch-client -t "${sessionName}"`);
|
|
709
|
-
} else {
|
|
710
|
-
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
711
|
-
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
712
|
-
if (useiTermIntegration) {
|
|
713
|
-
spawn("tmux", ["-CC", "attach-session", "-t", sessionName], {
|
|
714
|
-
stdio: "inherit",
|
|
715
|
-
detached: true
|
|
716
|
-
});
|
|
717
|
-
} else {
|
|
718
|
-
spawn("tmux", ["attach-session", "-t", sessionName], {
|
|
719
|
-
stdio: "inherit",
|
|
720
|
-
detached: true
|
|
721
|
-
});
|
|
632
|
+
const flags = config.flags || [];
|
|
633
|
+
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
722
634
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
return `tmux attach-session -t "${sessionName}"`;
|
|
774
|
-
}
|
|
775
|
-
async listWorkonSessions() {
|
|
776
|
-
try {
|
|
777
|
-
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
778
|
-
return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
|
|
779
|
-
} catch {
|
|
780
|
-
return [];
|
|
781
|
-
}
|
|
635
|
+
static get processing() {
|
|
636
|
+
return {
|
|
637
|
+
async processEvent(context) {
|
|
638
|
+
const { project, isShellMode, shellCommands } = context;
|
|
639
|
+
const claudeConfig = project.events.claude;
|
|
640
|
+
const claudeCommand = _ClaudeEvent.getClaudeCommand(claudeConfig);
|
|
641
|
+
if (isShellMode) {
|
|
642
|
+
shellCommands.push(claudeCommand);
|
|
643
|
+
} else {
|
|
644
|
+
const args = claudeCommand.split(" ").slice(1);
|
|
645
|
+
spawn5("claude", args, {
|
|
646
|
+
cwd: project.path.path,
|
|
647
|
+
stdio: "inherit"
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
generateShellCommand(context) {
|
|
652
|
+
const claudeConfig = context.project.events.claude;
|
|
653
|
+
return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
static get tmux() {
|
|
658
|
+
return {
|
|
659
|
+
getLayoutPriority() {
|
|
660
|
+
return 100;
|
|
661
|
+
},
|
|
662
|
+
contributeToLayout(enabledCommands) {
|
|
663
|
+
if (enabledCommands.includes("npm")) {
|
|
664
|
+
return "three-pane";
|
|
665
|
+
}
|
|
666
|
+
return "split";
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
static get help() {
|
|
671
|
+
return {
|
|
672
|
+
usage: "claude: true | { flags: string[], split_terminal: boolean }",
|
|
673
|
+
description: "Launch Claude Code in the project directory",
|
|
674
|
+
examples: [
|
|
675
|
+
{ config: true, description: "Launch Claude with defaults" },
|
|
676
|
+
{ config: { flags: ["--resume"] }, description: "Resume previous session" },
|
|
677
|
+
{
|
|
678
|
+
config: { flags: ["--model", "opus"], split_terminal: true },
|
|
679
|
+
description: "Use Opus model with split terminal"
|
|
680
|
+
}
|
|
681
|
+
]
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
};
|
|
782
685
|
}
|
|
783
|
-
};
|
|
686
|
+
});
|
|
784
687
|
|
|
785
|
-
// src/events/
|
|
786
|
-
import { spawn as
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
688
|
+
// src/events/extensions/docker.ts
|
|
689
|
+
import { spawn as spawn6 } from "child_process";
|
|
690
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
691
|
+
var DockerEvent;
|
|
692
|
+
var init_docker = __esm({
|
|
693
|
+
"src/events/extensions/docker.ts"() {
|
|
694
|
+
"use strict";
|
|
695
|
+
DockerEvent = class _DockerEvent {
|
|
696
|
+
static get metadata() {
|
|
697
|
+
return {
|
|
698
|
+
name: "docker",
|
|
699
|
+
displayName: "Docker container management",
|
|
700
|
+
description: "Start/stop Docker containers for the project",
|
|
701
|
+
category: "development",
|
|
702
|
+
requiresTmux: false,
|
|
703
|
+
dependencies: ["docker"]
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
static get validation() {
|
|
707
|
+
return {
|
|
708
|
+
validateConfig(config) {
|
|
709
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
if (typeof config === "string") {
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
if (typeof config === "object" && config !== null) {
|
|
716
|
+
const cfg = config;
|
|
717
|
+
if (cfg.compose_file !== void 0 && typeof cfg.compose_file !== "string") {
|
|
718
|
+
return "docker.compose_file must be a string";
|
|
719
|
+
}
|
|
720
|
+
if (cfg.services !== void 0) {
|
|
721
|
+
if (!Array.isArray(cfg.services)) {
|
|
722
|
+
return "docker.services must be an array";
|
|
723
|
+
}
|
|
724
|
+
for (const service of cfg.services) {
|
|
725
|
+
if (typeof service !== "string") {
|
|
726
|
+
return "docker.services must contain only strings";
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
return "docker config must be a boolean, string (compose file), or object";
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
static get configuration() {
|
|
737
|
+
return {
|
|
738
|
+
async configureInteractive() {
|
|
739
|
+
const composeFile = await input2({
|
|
740
|
+
message: "Enter docker-compose file path:",
|
|
741
|
+
default: "docker-compose.yml"
|
|
742
|
+
});
|
|
743
|
+
const servicesInput = await input2({
|
|
744
|
+
message: "Enter services to start (comma-separated, leave empty for all):",
|
|
745
|
+
default: ""
|
|
746
|
+
});
|
|
747
|
+
const services = servicesInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
748
|
+
if (composeFile === "docker-compose.yml" && services.length === 0) {
|
|
749
|
+
return { compose_file: "docker-compose.yml" };
|
|
750
|
+
}
|
|
751
|
+
if (services.length === 0) {
|
|
752
|
+
return composeFile;
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
compose_file: composeFile,
|
|
756
|
+
services
|
|
757
|
+
};
|
|
758
|
+
},
|
|
759
|
+
getDefaultConfig() {
|
|
760
|
+
return { compose_file: "docker-compose.yml" };
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
static getDockerCommand(config) {
|
|
765
|
+
if (typeof config === "boolean" || config === void 0) {
|
|
766
|
+
return "docker-compose up -d";
|
|
767
|
+
}
|
|
768
|
+
if (typeof config === "string") {
|
|
769
|
+
return `docker-compose -f ${config} up -d`;
|
|
770
|
+
}
|
|
771
|
+
const composeFile = config.compose_file || "docker-compose.yml";
|
|
772
|
+
const services = config.services?.join(" ") || "";
|
|
773
|
+
return `docker-compose -f ${composeFile} up -d ${services}`.trim();
|
|
774
|
+
}
|
|
775
|
+
static get processing() {
|
|
776
|
+
return {
|
|
777
|
+
async processEvent(context) {
|
|
778
|
+
const { project, isShellMode, shellCommands } = context;
|
|
779
|
+
const dockerConfig = project.events.docker;
|
|
780
|
+
const dockerCommand = _DockerEvent.getDockerCommand(
|
|
781
|
+
dockerConfig
|
|
782
|
+
);
|
|
783
|
+
if (isShellMode) {
|
|
784
|
+
shellCommands.push(dockerCommand);
|
|
785
|
+
} else {
|
|
786
|
+
const [cmd, ...args] = dockerCommand.split(" ");
|
|
787
|
+
spawn6(cmd, args, {
|
|
788
|
+
cwd: project.path.path,
|
|
789
|
+
stdio: "inherit"
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
generateShellCommand(context) {
|
|
794
|
+
const dockerConfig = context.project.events.docker;
|
|
795
|
+
return [_DockerEvent.getDockerCommand(dockerConfig)];
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
static get tmux() {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
static get help() {
|
|
803
|
+
return {
|
|
804
|
+
usage: 'docker: true | "compose-file.yml" | { compose_file: string, services?: string[] }',
|
|
805
|
+
description: "Start Docker containers for the project",
|
|
806
|
+
examples: [
|
|
807
|
+
{ config: true, description: "Use default docker-compose.yml" },
|
|
808
|
+
{ config: "docker-compose.dev.yml", description: "Use custom compose file" },
|
|
809
|
+
{
|
|
810
|
+
config: { compose_file: "docker-compose.yml", services: ["web", "db"] },
|
|
811
|
+
description: "Start specific services"
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
};
|
|
815
|
+
}
|
|
796
816
|
};
|
|
797
817
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// src/events/extensions/npm.ts
|
|
821
|
+
var npm_exports = {};
|
|
822
|
+
__export(npm_exports, {
|
|
823
|
+
NpmEvent: () => NpmEvent,
|
|
824
|
+
default: () => npm_default
|
|
825
|
+
});
|
|
826
|
+
import { spawn as spawn7 } from "child_process";
|
|
827
|
+
import { input as input3, confirm as confirm2 } from "@inquirer/prompts";
|
|
828
|
+
var NpmEvent, npm_default;
|
|
829
|
+
var init_npm = __esm({
|
|
830
|
+
"src/events/extensions/npm.ts"() {
|
|
831
|
+
"use strict";
|
|
832
|
+
NpmEvent = class _NpmEvent {
|
|
833
|
+
static get metadata() {
|
|
834
|
+
return {
|
|
835
|
+
name: "npm",
|
|
836
|
+
displayName: "Run NPM command",
|
|
837
|
+
description: "Execute NPM scripts in project directory",
|
|
838
|
+
category: "development",
|
|
839
|
+
requiresTmux: true,
|
|
840
|
+
dependencies: ["npm"]
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
static get validation() {
|
|
844
|
+
return {
|
|
845
|
+
validateConfig(config) {
|
|
846
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
if (typeof config === "string") {
|
|
850
|
+
if (config.trim().length === 0) {
|
|
851
|
+
return "npm script name cannot be empty";
|
|
852
|
+
}
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
if (typeof config === "object" && config !== null) {
|
|
856
|
+
const cfg = config;
|
|
857
|
+
if (typeof cfg.command !== "string" || cfg.command.trim().length === 0) {
|
|
858
|
+
return "npm.command must be a non-empty string";
|
|
859
|
+
}
|
|
860
|
+
if (cfg.watch !== void 0 && typeof cfg.watch !== "boolean") {
|
|
861
|
+
return "npm.watch must be a boolean";
|
|
862
|
+
}
|
|
863
|
+
if (cfg.auto_restart !== void 0 && typeof cfg.auto_restart !== "boolean") {
|
|
864
|
+
return "npm.auto_restart must be a boolean";
|
|
865
|
+
}
|
|
866
|
+
return true;
|
|
867
|
+
}
|
|
868
|
+
return "npm config must be a boolean, string (script name), or object";
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
static get configuration() {
|
|
873
|
+
return {
|
|
874
|
+
async configureInteractive() {
|
|
875
|
+
const scriptName = await input3({
|
|
876
|
+
message: "Enter NPM script to run:",
|
|
877
|
+
default: "dev"
|
|
878
|
+
});
|
|
879
|
+
const useAdvanced = await confirm2({
|
|
880
|
+
message: "Configure advanced NPM options?",
|
|
881
|
+
default: false
|
|
882
|
+
});
|
|
883
|
+
if (!useAdvanced) {
|
|
884
|
+
return scriptName;
|
|
885
|
+
}
|
|
886
|
+
const watch = await confirm2({
|
|
887
|
+
message: "Enable watch mode?",
|
|
888
|
+
default: false
|
|
889
|
+
});
|
|
890
|
+
const autoRestart = await confirm2({
|
|
891
|
+
message: "Auto-restart on crash?",
|
|
892
|
+
default: false
|
|
893
|
+
});
|
|
894
|
+
if (!watch && !autoRestart) {
|
|
895
|
+
return scriptName;
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
command: scriptName,
|
|
899
|
+
watch,
|
|
900
|
+
auto_restart: autoRestart
|
|
901
|
+
};
|
|
902
|
+
},
|
|
903
|
+
getDefaultConfig() {
|
|
904
|
+
return "dev";
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
static getNpmCommand(config) {
|
|
909
|
+
if (typeof config === "boolean" || config === void 0) {
|
|
910
|
+
return "npm run dev";
|
|
803
911
|
}
|
|
804
|
-
|
|
912
|
+
if (typeof config === "string") {
|
|
913
|
+
return `npm run ${config}`;
|
|
914
|
+
}
|
|
915
|
+
return `npm run ${config.command}`;
|
|
916
|
+
}
|
|
917
|
+
static get processing() {
|
|
918
|
+
return {
|
|
919
|
+
async processEvent(context) {
|
|
920
|
+
const { project, isShellMode, shellCommands } = context;
|
|
921
|
+
const npmConfig = project.events.npm;
|
|
922
|
+
const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
|
|
923
|
+
if (isShellMode) {
|
|
924
|
+
shellCommands.push(npmCommand);
|
|
925
|
+
} else {
|
|
926
|
+
const [cmd, ...args] = npmCommand.split(" ");
|
|
927
|
+
spawn7(cmd, args, {
|
|
928
|
+
cwd: project.path.path,
|
|
929
|
+
stdio: "inherit"
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
generateShellCommand(context) {
|
|
934
|
+
const npmConfig = context.project.events.npm;
|
|
935
|
+
return [_NpmEvent.getNpmCommand(npmConfig)];
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
static get tmux() {
|
|
940
|
+
return {
|
|
941
|
+
getLayoutPriority() {
|
|
942
|
+
return 50;
|
|
943
|
+
},
|
|
944
|
+
contributeToLayout(enabledCommands) {
|
|
945
|
+
if (enabledCommands.includes("claude")) {
|
|
946
|
+
return "three-pane";
|
|
947
|
+
}
|
|
948
|
+
return "two-pane-npm";
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
static get help() {
|
|
953
|
+
return {
|
|
954
|
+
usage: 'npm: true | "script" | { command: string, watch?: boolean, auto_restart?: boolean }',
|
|
955
|
+
description: "Run an NPM script in the project directory",
|
|
956
|
+
examples: [
|
|
957
|
+
{ config: true, description: "Run npm run dev" },
|
|
958
|
+
{ config: "test", description: "Run npm run test" },
|
|
959
|
+
{ config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
|
|
960
|
+
]
|
|
961
|
+
};
|
|
805
962
|
}
|
|
806
963
|
};
|
|
964
|
+
npm_default = NpmEvent;
|
|
807
965
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
// src/events/registry.ts
|
|
969
|
+
var ALL_EVENTS, EventRegistryClass, EventRegistry;
|
|
970
|
+
var init_registry = __esm({
|
|
971
|
+
"src/events/registry.ts"() {
|
|
972
|
+
"use strict";
|
|
973
|
+
init_cwd();
|
|
974
|
+
init_ide();
|
|
975
|
+
init_web();
|
|
976
|
+
init_claude();
|
|
977
|
+
init_docker();
|
|
978
|
+
init_npm();
|
|
979
|
+
ALL_EVENTS = [CwdEvent, IdeEvent, WebEvent, ClaudeEvent, DockerEvent, NpmEvent];
|
|
980
|
+
EventRegistryClass = class {
|
|
981
|
+
_events = /* @__PURE__ */ new Map();
|
|
982
|
+
_initialized = false;
|
|
983
|
+
/**
|
|
984
|
+
* Initialize the registry by registering all events
|
|
985
|
+
*/
|
|
986
|
+
async initialize() {
|
|
987
|
+
if (this._initialized) return;
|
|
988
|
+
this.registerEvents();
|
|
989
|
+
this._initialized = true;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Register all event classes
|
|
993
|
+
*/
|
|
994
|
+
registerEvents() {
|
|
995
|
+
for (const EventClass of ALL_EVENTS) {
|
|
996
|
+
if (this.isValidEvent(EventClass)) {
|
|
997
|
+
const metadata = EventClass.metadata;
|
|
998
|
+
this._events.set(metadata.name, EventClass);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Validate if a class is a proper event
|
|
1004
|
+
*/
|
|
1005
|
+
isValidEvent(EventClass) {
|
|
1006
|
+
try {
|
|
1007
|
+
if (typeof EventClass !== "function") return false;
|
|
1008
|
+
const metadata = EventClass.metadata;
|
|
1009
|
+
return metadata !== void 0 && typeof metadata.name === "string" && typeof metadata.displayName === "string" && typeof EventClass.validation === "object" && typeof EventClass.configuration === "object" && typeof EventClass.processing === "object";
|
|
1010
|
+
} catch {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Get all valid event names from registered events
|
|
1016
|
+
*/
|
|
1017
|
+
getValidEventNames() {
|
|
1018
|
+
this.ensureInitialized();
|
|
1019
|
+
return Array.from(this._events.keys());
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Get event by name
|
|
1023
|
+
*/
|
|
1024
|
+
getEventByName(name) {
|
|
1025
|
+
this.ensureInitialized();
|
|
1026
|
+
return this._events.get(name) ?? null;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Get all events for management UI
|
|
1030
|
+
*/
|
|
1031
|
+
getEventsForManageUI() {
|
|
1032
|
+
this.ensureInitialized();
|
|
1033
|
+
const events = [];
|
|
1034
|
+
for (const [name, EventClass] of this._events) {
|
|
1035
|
+
const metadata = EventClass.metadata;
|
|
1036
|
+
events.push({
|
|
1037
|
+
name: metadata.displayName,
|
|
1038
|
+
value: name,
|
|
1039
|
+
description: metadata.description
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
return events.sort((a, b) => a.name.localeCompare(b.name));
|
|
815
1043
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1044
|
+
/**
|
|
1045
|
+
* Get events that support tmux integration
|
|
1046
|
+
*/
|
|
1047
|
+
getTmuxEnabledEvents() {
|
|
1048
|
+
this.ensureInitialized();
|
|
1049
|
+
const tmuxEvents = [];
|
|
1050
|
+
for (const [name, EventClass] of this._events) {
|
|
1051
|
+
const tmux = EventClass.tmux;
|
|
1052
|
+
if (tmux) {
|
|
1053
|
+
tmuxEvents.push({
|
|
1054
|
+
name,
|
|
1055
|
+
event: EventClass,
|
|
1056
|
+
priority: tmux.getLayoutPriority ? tmux.getLayoutPriority() : 0
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return tmuxEvents.sort((a, b) => b.priority - a.priority);
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Get all available events with their metadata
|
|
1064
|
+
*/
|
|
1065
|
+
getAllEvents() {
|
|
1066
|
+
this.ensureInitialized();
|
|
1067
|
+
const events = [];
|
|
1068
|
+
for (const [name, EventClass] of this._events) {
|
|
1069
|
+
const typedClass = EventClass;
|
|
1070
|
+
events.push({
|
|
1071
|
+
name,
|
|
1072
|
+
metadata: typedClass.metadata,
|
|
1073
|
+
hasValidation: !!typedClass.validation,
|
|
1074
|
+
hasConfiguration: !!typedClass.configuration,
|
|
1075
|
+
hasProcessing: !!typedClass.processing,
|
|
1076
|
+
hasTmux: !!typedClass.tmux,
|
|
1077
|
+
hasHelp: !!typedClass.help
|
|
830
1078
|
});
|
|
831
1079
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1080
|
+
return events;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Ensure registry is initialized
|
|
1084
|
+
*/
|
|
1085
|
+
ensureInitialized() {
|
|
1086
|
+
if (!this._initialized) {
|
|
1087
|
+
throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Clear the registry (useful for testing)
|
|
1092
|
+
*/
|
|
1093
|
+
clear() {
|
|
1094
|
+
this._events.clear();
|
|
1095
|
+
this._initialized = false;
|
|
836
1096
|
}
|
|
837
1097
|
};
|
|
1098
|
+
EventRegistry = new EventRegistryClass();
|
|
838
1099
|
}
|
|
839
|
-
|
|
840
|
-
return null;
|
|
841
|
-
}
|
|
842
|
-
static get help() {
|
|
843
|
-
return {
|
|
844
|
-
usage: "cwd: true | false",
|
|
845
|
-
description: "Change the current working directory to the project path",
|
|
846
|
-
examples: [
|
|
847
|
-
{ config: true, description: "Enable directory change" },
|
|
848
|
-
{ config: false, description: "Disable directory change" }
|
|
849
|
-
]
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
};
|
|
1100
|
+
});
|
|
853
1101
|
|
|
854
|
-
// src/
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1102
|
+
// src/commands/interactive.ts
|
|
1103
|
+
var interactive_exports = {};
|
|
1104
|
+
__export(interactive_exports, {
|
|
1105
|
+
runInteractive: () => runInteractive
|
|
1106
|
+
});
|
|
1107
|
+
import { select, input as input4, checkbox, confirm as confirm3 } from "@inquirer/prompts";
|
|
1108
|
+
import File3 from "phylo";
|
|
1109
|
+
import deepAssign2 from "deep-assign";
|
|
1110
|
+
async function runInteractive(ctx) {
|
|
1111
|
+
const { config, log, environment, suggestedName } = ctx;
|
|
1112
|
+
showLogo(config);
|
|
1113
|
+
log.log("");
|
|
1114
|
+
const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name : File3.cwd().name);
|
|
1115
|
+
const fromUser = !!suggestedName;
|
|
1116
|
+
await startInteractive(defaultName, fromUser, ctx);
|
|
1117
|
+
}
|
|
1118
|
+
function showLogo(config) {
|
|
1119
|
+
const version = config.get("pkg")?.version ?? "unknown";
|
|
1120
|
+
console.log(
|
|
1121
|
+
` 8\x1B[2m${" ".repeat(Math.max(15 - version.length - 1, 1)) + "v" + version}\x1B[22m
|
|
1122
|
+
Yb db dP .d8b. 8d8b 8.dP \x1B[92m.d8b. 8d8b.\x1B[0m
|
|
1123
|
+
YbdPYbdP 8' .8 8P 88b \x1B[92m8' .8 8P Y8\x1B[0m
|
|
1124
|
+
YP YP \`Y8P' 8 8 Yb \x1B[92m\`Y8P' 8 8\x1B[0m`
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
|
|
1128
|
+
const { log, environment } = ctx;
|
|
1129
|
+
log.debug(`Name '${defaultName}' was${fromUser ? "" : " not"} provided by the user`);
|
|
1130
|
+
const question = getFirstQuestion(defaultName, fromUser, environment, showMain);
|
|
1131
|
+
const action = await select(question);
|
|
1132
|
+
switch (action) {
|
|
1133
|
+
case "exit":
|
|
1134
|
+
return;
|
|
1135
|
+
case "more":
|
|
1136
|
+
await startInteractive(defaultName, fromUser, ctx, true);
|
|
1137
|
+
return;
|
|
1138
|
+
case "init-project":
|
|
1139
|
+
await initProject(defaultName, fromUser, ctx);
|
|
1140
|
+
return;
|
|
1141
|
+
case "init-branch":
|
|
1142
|
+
await initBranch(defaultName, ctx);
|
|
1143
|
+
return;
|
|
1144
|
+
case "switch-project":
|
|
1145
|
+
await switchProject(ctx);
|
|
1146
|
+
return;
|
|
1147
|
+
case "switch-branch":
|
|
1148
|
+
await switchBranch(defaultName, ctx);
|
|
1149
|
+
return;
|
|
1150
|
+
case "manage-projects":
|
|
1151
|
+
await manageProjects(ctx);
|
|
1152
|
+
return;
|
|
1153
|
+
case "manage-branches":
|
|
1154
|
+
await manageBranches(defaultName, ctx);
|
|
1155
|
+
return;
|
|
866
1156
|
}
|
|
867
|
-
|
|
1157
|
+
}
|
|
1158
|
+
function getFirstQuestion(defaultName, fromUser, environment, showMain) {
|
|
1159
|
+
if (!showMain && environment.$isProjectEnvironment && !fromUser) {
|
|
868
1160
|
return {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
|
|
1161
|
+
message: environment.project.name,
|
|
1162
|
+
choices: [
|
|
1163
|
+
{ name: "Start a branch", value: "init-branch" },
|
|
1164
|
+
{ name: "Switch branch", value: "switch-branch" },
|
|
1165
|
+
{ name: "Manage branches", value: "manage-branches" },
|
|
1166
|
+
{ name: "---", value: "" },
|
|
1167
|
+
{ name: "More...", value: "more" },
|
|
1168
|
+
{ name: "Exit", value: "exit" }
|
|
1169
|
+
].filter((c) => c.value !== "")
|
|
875
1170
|
};
|
|
876
1171
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
},
|
|
882
|
-
|
|
1172
|
+
return {
|
|
1173
|
+
message: "What do you want to do?",
|
|
1174
|
+
choices: [
|
|
1175
|
+
{ name: "Start a new project", value: "init-project" },
|
|
1176
|
+
{ name: "Open an existing project", value: "switch-project" },
|
|
1177
|
+
{ name: "Manage projects", value: "manage-projects" },
|
|
1178
|
+
{ name: "---", value: "" },
|
|
1179
|
+
{ name: "Exit", value: "exit" }
|
|
1180
|
+
].filter((c) => c.value !== "")
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
async function initProject(defaultName, fromUser, ctx) {
|
|
1184
|
+
const { config, log } = ctx;
|
|
1185
|
+
const defaults = config.getDefaults();
|
|
1186
|
+
const projects = config.getProjects();
|
|
1187
|
+
let name;
|
|
1188
|
+
if (fromUser) {
|
|
1189
|
+
name = defaultName;
|
|
1190
|
+
log.log(`Project name: ${name}`);
|
|
1191
|
+
} else {
|
|
1192
|
+
name = await input4({
|
|
1193
|
+
message: "What is the name of the project?",
|
|
1194
|
+
default: defaultName,
|
|
1195
|
+
validate: (value) => {
|
|
1196
|
+
if (value in projects) return "Project already exists.";
|
|
1197
|
+
if (/\w+#\w+/.test(value)) {
|
|
1198
|
+
const projectName = value.substring(0, value.indexOf("#"));
|
|
1199
|
+
if (!(projectName in projects)) {
|
|
1200
|
+
return `Project '${projectName}' does not exist. Please create it before starting a branch.`;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
883
1203
|
return true;
|
|
884
1204
|
}
|
|
885
|
-
};
|
|
1205
|
+
});
|
|
886
1206
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1207
|
+
const isBranch = /\w+#\w+/.test(name);
|
|
1208
|
+
let basePath;
|
|
1209
|
+
if (isBranch) {
|
|
1210
|
+
const projectName = name.substring(0, name.indexOf("#"));
|
|
1211
|
+
basePath = defaults?.base ? File3.from(defaults.base).join(projects[projectName].path).absolutePath() : projects[projectName].path;
|
|
1212
|
+
log.log(`Project path: ${basePath}`);
|
|
1213
|
+
} else {
|
|
1214
|
+
const pathAnswer = await input4({
|
|
1215
|
+
message: "What is the path to the project?",
|
|
1216
|
+
default: defaults?.base ? File3.from(defaults.base).join(name).path : name
|
|
1217
|
+
});
|
|
1218
|
+
let answerFile = File3.from(pathAnswer);
|
|
1219
|
+
const defaultBase = defaults?.base ? File3.from(defaults.base) : File3.cwd();
|
|
1220
|
+
if (!answerFile.isAbsolute()) {
|
|
1221
|
+
answerFile = defaultBase.join(answerFile.path);
|
|
1222
|
+
}
|
|
1223
|
+
try {
|
|
1224
|
+
const canonical = answerFile.canonicalize();
|
|
1225
|
+
if (canonical) {
|
|
1226
|
+
answerFile = canonical;
|
|
1227
|
+
} else {
|
|
1228
|
+
answerFile = answerFile.absolutify();
|
|
906
1229
|
}
|
|
907
|
-
}
|
|
1230
|
+
} catch {
|
|
1231
|
+
answerFile = answerFile.absolutify();
|
|
1232
|
+
}
|
|
1233
|
+
basePath = answerFile.relativize(defaultBase.path).path;
|
|
908
1234
|
}
|
|
909
|
-
|
|
910
|
-
|
|
1235
|
+
const ide = await select({
|
|
1236
|
+
message: "What is the IDE?",
|
|
1237
|
+
choices: IDE_CHOICES
|
|
1238
|
+
});
|
|
1239
|
+
const selectedEvents = await checkbox({
|
|
1240
|
+
message: "Which events should take place when opening?",
|
|
1241
|
+
choices: [
|
|
1242
|
+
{ name: "Change terminal cwd to project path", value: "cwd", checked: true },
|
|
1243
|
+
{ name: "Open project in IDE", value: "ide", checked: true }
|
|
1244
|
+
]
|
|
1245
|
+
});
|
|
1246
|
+
const events = {
|
|
1247
|
+
cwd: selectedEvents.includes("cwd"),
|
|
1248
|
+
ide: selectedEvents.includes("ide")
|
|
1249
|
+
};
|
|
1250
|
+
const projectConfig = {
|
|
1251
|
+
path: basePath,
|
|
1252
|
+
ide,
|
|
1253
|
+
events
|
|
1254
|
+
};
|
|
1255
|
+
projects[name] = projectConfig;
|
|
1256
|
+
config.set("projects", projects);
|
|
1257
|
+
log.info("Your project has been initialized.");
|
|
1258
|
+
log.info(`Use 'workon ${name}' to start working!`);
|
|
1259
|
+
}
|
|
1260
|
+
async function initBranch(defaultName, ctx) {
|
|
1261
|
+
const { config, log } = ctx;
|
|
1262
|
+
const projects = config.getProjects();
|
|
1263
|
+
const branch = await input4({
|
|
1264
|
+
message: "What is the name of the branch?",
|
|
1265
|
+
validate: (value) => {
|
|
1266
|
+
if (/\w+#\w+/.test(value)) return `Branch name can't contain the "#" sign`;
|
|
1267
|
+
if (`${defaultName}#${value}` in projects) return "Branch already exists.";
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
const branchName = `${defaultName}#${branch}`;
|
|
1272
|
+
const baseProject = projects[defaultName];
|
|
1273
|
+
const branchConfig = deepAssign2({}, baseProject, { branch });
|
|
1274
|
+
delete branchConfig.name;
|
|
1275
|
+
projects[branchName] = branchConfig;
|
|
1276
|
+
config.set("projects", projects);
|
|
1277
|
+
log.info("Your branch configuration has been initialized.");
|
|
1278
|
+
log.info(`Use 'workon ${branchName}' to start working!`);
|
|
1279
|
+
}
|
|
1280
|
+
async function switchProject(ctx) {
|
|
1281
|
+
const { config, log } = ctx;
|
|
1282
|
+
const projects = config.getProjects();
|
|
1283
|
+
const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
|
|
1284
|
+
if (baseProjects.length === 0) {
|
|
1285
|
+
log.info('No projects configured yet. Use "Start a new project" to create one.');
|
|
1286
|
+
return;
|
|
911
1287
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1288
|
+
const projectName = await select({
|
|
1289
|
+
message: "Select a project to open:",
|
|
1290
|
+
choices: baseProjects.map((name) => ({
|
|
1291
|
+
name: `${name} (${projects[name].path})`,
|
|
1292
|
+
value: name
|
|
1293
|
+
}))
|
|
1294
|
+
});
|
|
1295
|
+
await openProject(projectName, ctx);
|
|
1296
|
+
}
|
|
1297
|
+
async function switchBranch(projectName, ctx) {
|
|
1298
|
+
const { config, log } = ctx;
|
|
1299
|
+
const projects = config.getProjects();
|
|
1300
|
+
const branchPrefix = `${projectName}#`;
|
|
1301
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1302
|
+
if (branches.length === 0) {
|
|
1303
|
+
log.info(`No branch configurations found for '${projectName}'.`);
|
|
1304
|
+
log.info('Use "Start a branch" to create one.');
|
|
1305
|
+
return;
|
|
921
1306
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1307
|
+
const branchConfig = await select({
|
|
1308
|
+
message: "Select a branch configuration:",
|
|
1309
|
+
choices: branches.map((name) => ({
|
|
1310
|
+
name: name.substring(branchPrefix.length),
|
|
1311
|
+
value: name
|
|
1312
|
+
}))
|
|
1313
|
+
});
|
|
1314
|
+
await openProject(branchConfig, ctx);
|
|
1315
|
+
}
|
|
1316
|
+
async function manageProjects(ctx) {
|
|
1317
|
+
const { config } = ctx;
|
|
1318
|
+
await EventRegistry.initialize();
|
|
1319
|
+
const projects = config.getProjects();
|
|
1320
|
+
const hasProjects = Object.keys(projects).length > 0;
|
|
1321
|
+
const choices = [
|
|
1322
|
+
{ name: "Create new project", value: "create" },
|
|
1323
|
+
...hasProjects ? [
|
|
1324
|
+
{ name: "Edit project", value: "edit" },
|
|
1325
|
+
{ name: "Delete project", value: "delete" },
|
|
1326
|
+
{ name: "List projects", value: "list" }
|
|
1327
|
+
] : [],
|
|
1328
|
+
{ name: "Back", value: "back" }
|
|
1329
|
+
];
|
|
1330
|
+
const action = await select({
|
|
1331
|
+
message: "Manage projects:",
|
|
1332
|
+
choices
|
|
1333
|
+
});
|
|
1334
|
+
switch (action) {
|
|
1335
|
+
case "create":
|
|
1336
|
+
await createProjectManage(ctx);
|
|
1337
|
+
break;
|
|
1338
|
+
case "edit":
|
|
1339
|
+
await editProjectManage(ctx);
|
|
1340
|
+
break;
|
|
1341
|
+
case "delete":
|
|
1342
|
+
await deleteProjectManage(ctx);
|
|
1343
|
+
break;
|
|
1344
|
+
case "list":
|
|
1345
|
+
listProjectsManage(ctx);
|
|
1346
|
+
break;
|
|
1347
|
+
case "back":
|
|
1348
|
+
return;
|
|
937
1349
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
}
|
|
1350
|
+
await manageProjects(ctx);
|
|
1351
|
+
}
|
|
1352
|
+
async function manageBranches(projectName, ctx) {
|
|
1353
|
+
const { config } = ctx;
|
|
1354
|
+
const projects = config.getProjects();
|
|
1355
|
+
const branchPrefix = `${projectName}#`;
|
|
1356
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1357
|
+
const choices = [
|
|
1358
|
+
{ name: "Create new branch config", value: "create" },
|
|
1359
|
+
...branches.length > 0 ? [
|
|
1360
|
+
{ name: "Edit branch config", value: "edit" },
|
|
1361
|
+
{ name: "Delete branch config", value: "delete" },
|
|
1362
|
+
{ name: "List branch configs", value: "list" }
|
|
1363
|
+
] : [],
|
|
1364
|
+
{ name: "Back", value: "back" }
|
|
1365
|
+
];
|
|
1366
|
+
const action = await select({
|
|
1367
|
+
message: `Manage branches for '${projectName}':`,
|
|
1368
|
+
choices
|
|
1369
|
+
});
|
|
1370
|
+
switch (action) {
|
|
1371
|
+
case "create":
|
|
1372
|
+
await initBranch(projectName, ctx);
|
|
1373
|
+
break;
|
|
1374
|
+
case "edit":
|
|
1375
|
+
await editBranchManage(projectName, ctx);
|
|
1376
|
+
break;
|
|
1377
|
+
case "delete":
|
|
1378
|
+
await deleteBranchManage(projectName, ctx);
|
|
1379
|
+
break;
|
|
1380
|
+
case "list":
|
|
1381
|
+
listBranchesManage(projectName, ctx);
|
|
1382
|
+
break;
|
|
1383
|
+
case "back":
|
|
1384
|
+
return;
|
|
947
1385
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
};
|
|
1386
|
+
await manageBranches(projectName, ctx);
|
|
1387
|
+
}
|
|
1388
|
+
async function openProject(projectName, ctx) {
|
|
1389
|
+
const { config, log } = ctx;
|
|
1390
|
+
const projects = config.getProjects();
|
|
1391
|
+
if (!(projectName in projects)) {
|
|
1392
|
+
log.error(`Project '${projectName}' not found.`);
|
|
1393
|
+
return;
|
|
957
1394
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1395
|
+
await EventRegistry.initialize();
|
|
1396
|
+
const projectConfig = projects[projectName];
|
|
1397
|
+
const projectCfg = { ...projectConfig, name: projectName };
|
|
1398
|
+
const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
|
|
1399
|
+
log.info(`Opening project '${projectName}'...`);
|
|
1400
|
+
const events = Object.keys(projectConfig.events).filter(
|
|
1401
|
+
(e) => projectConfig.events[e]
|
|
1402
|
+
);
|
|
1403
|
+
for (const event of events) {
|
|
1404
|
+
const eventHandler = EventRegistry.getEventByName(event);
|
|
1405
|
+
if (eventHandler && eventHandler.processing) {
|
|
1406
|
+
await eventHandler.processing.processEvent({
|
|
1407
|
+
project: projectEnv.project,
|
|
1408
|
+
isShellMode: false,
|
|
1409
|
+
shellCommands: []
|
|
1410
|
+
});
|
|
967
1411
|
}
|
|
968
1412
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1413
|
+
}
|
|
1414
|
+
async function createProjectManage(ctx) {
|
|
1415
|
+
const { config, log } = ctx;
|
|
1416
|
+
const defaults = config.getDefaults();
|
|
1417
|
+
const projects = config.getProjects();
|
|
1418
|
+
const name = await input4({
|
|
1419
|
+
message: "Project name:",
|
|
1420
|
+
validate: (value) => {
|
|
1421
|
+
if (!value.trim()) return "Name is required";
|
|
1422
|
+
if (!/^[\w-]+$/.test(value))
|
|
1423
|
+
return "Name can only contain letters, numbers, underscores, and hyphens";
|
|
1424
|
+
if (value in projects) return "Project already exists";
|
|
1425
|
+
return true;
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
const defaultPath = defaults?.base ? File3.from(defaults.base).join(name).path : name;
|
|
1429
|
+
const pathInput = await input4({
|
|
1430
|
+
message: "Project path:",
|
|
1431
|
+
default: defaultPath
|
|
1432
|
+
});
|
|
1433
|
+
let relativePath = pathInput;
|
|
1434
|
+
if (defaults?.base) {
|
|
1435
|
+
const baseDir = File3.from(defaults.base);
|
|
1436
|
+
const pathFile = File3.from(pathInput);
|
|
1437
|
+
try {
|
|
1438
|
+
if (pathFile.isAbsolute()) {
|
|
1439
|
+
relativePath = pathFile.relativize(baseDir.path).path;
|
|
993
1440
|
}
|
|
994
|
-
}
|
|
1441
|
+
} catch {
|
|
1442
|
+
relativePath = pathInput;
|
|
1443
|
+
}
|
|
995
1444
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1445
|
+
const ide = await select({
|
|
1446
|
+
message: "Select IDE:",
|
|
1447
|
+
choices: IDE_CHOICES
|
|
1448
|
+
});
|
|
1449
|
+
const availableEvents = EventRegistry.getEventsForManageUI();
|
|
1450
|
+
const selectedEvents = await checkbox({
|
|
1451
|
+
message: "Select events to enable:",
|
|
1452
|
+
choices: availableEvents.map((e) => ({
|
|
1453
|
+
name: `${e.name} - ${e.description}`,
|
|
1454
|
+
value: e.value,
|
|
1455
|
+
checked: e.value === "cwd" || e.value === "ide"
|
|
1456
|
+
}))
|
|
1457
|
+
});
|
|
1458
|
+
const events = {};
|
|
1459
|
+
for (const eventName of selectedEvents) {
|
|
1460
|
+
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1461
|
+
if (eventHandler) {
|
|
1462
|
+
const eventConfig = await eventHandler.configuration.configureInteractive();
|
|
1463
|
+
events[eventName] = eventConfig;
|
|
1464
|
+
}
|
|
998
1465
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1466
|
+
const projectConfig = {
|
|
1467
|
+
path: relativePath,
|
|
1468
|
+
ide,
|
|
1469
|
+
events
|
|
1470
|
+
};
|
|
1471
|
+
const confirmed = await confirm3({
|
|
1472
|
+
message: "Save this project?",
|
|
1473
|
+
default: true
|
|
1474
|
+
});
|
|
1475
|
+
if (confirmed) {
|
|
1476
|
+
config.setProject(name, projectConfig);
|
|
1477
|
+
log.info(`Project '${name}' created successfully!`);
|
|
1008
1478
|
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
displayName: "Launch Claude Code",
|
|
1019
|
-
description: "Launch Claude Code with optional flags and configuration",
|
|
1020
|
-
category: "development",
|
|
1021
|
-
requiresTmux: true,
|
|
1022
|
-
dependencies: ["claude"]
|
|
1023
|
-
};
|
|
1479
|
+
}
|
|
1480
|
+
async function editProjectManage(ctx) {
|
|
1481
|
+
const { config, log } = ctx;
|
|
1482
|
+
const projects = config.getProjects();
|
|
1483
|
+
const defaults = config.getDefaults();
|
|
1484
|
+
const baseProjects = Object.keys(projects).filter((name2) => !name2.includes("#"));
|
|
1485
|
+
if (baseProjects.length === 0) {
|
|
1486
|
+
log.info("No projects to edit.");
|
|
1487
|
+
return;
|
|
1024
1488
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (!flag.startsWith("-")) {
|
|
1042
|
-
return `Invalid flag "${flag}": flags must start with - or --`;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
if (cfg.split_terminal !== void 0 && typeof cfg.split_terminal !== "boolean") {
|
|
1047
|
-
return "claude.split_terminal must be a boolean";
|
|
1048
|
-
}
|
|
1049
|
-
return true;
|
|
1050
|
-
}
|
|
1051
|
-
return "claude config must be a boolean or object with flags/split_terminal";
|
|
1489
|
+
const name = await select({
|
|
1490
|
+
message: "Select project to edit:",
|
|
1491
|
+
choices: baseProjects.map((n) => ({ name: n, value: n }))
|
|
1492
|
+
});
|
|
1493
|
+
const project = projects[name];
|
|
1494
|
+
const pathInput = await input4({
|
|
1495
|
+
message: "Project path:",
|
|
1496
|
+
default: project.path
|
|
1497
|
+
});
|
|
1498
|
+
let relativePath = pathInput;
|
|
1499
|
+
if (defaults?.base) {
|
|
1500
|
+
const baseDir = File3.from(defaults.base);
|
|
1501
|
+
const pathFile = File3.from(pathInput);
|
|
1502
|
+
try {
|
|
1503
|
+
if (pathFile.isAbsolute()) {
|
|
1504
|
+
relativePath = pathFile.relativize(baseDir.path).path;
|
|
1052
1505
|
}
|
|
1053
|
-
}
|
|
1506
|
+
} catch {
|
|
1507
|
+
relativePath = pathInput;
|
|
1508
|
+
}
|
|
1054
1509
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1510
|
+
const ide = await select({
|
|
1511
|
+
message: "Select IDE:",
|
|
1512
|
+
choices: IDE_CHOICES,
|
|
1513
|
+
default: project.ide || "vscode"
|
|
1514
|
+
});
|
|
1515
|
+
const keepEvents = await confirm3({
|
|
1516
|
+
message: "Keep existing event configuration?",
|
|
1517
|
+
default: true
|
|
1518
|
+
});
|
|
1519
|
+
let events = project.events;
|
|
1520
|
+
if (!keepEvents) {
|
|
1521
|
+
const availableEvents = EventRegistry.getEventsForManageUI();
|
|
1522
|
+
const currentEvents = Object.keys(project.events);
|
|
1523
|
+
const selectedEvents = await checkbox({
|
|
1524
|
+
message: "Select events to enable:",
|
|
1525
|
+
choices: availableEvents.map((e) => ({
|
|
1526
|
+
name: `${e.name} - ${e.description}`,
|
|
1527
|
+
value: e.value,
|
|
1528
|
+
checked: currentEvents.includes(e.value)
|
|
1529
|
+
}))
|
|
1530
|
+
});
|
|
1531
|
+
events = {};
|
|
1532
|
+
for (const eventName of selectedEvents) {
|
|
1533
|
+
if (project.events[eventName]) {
|
|
1534
|
+
events[eventName] = project.events[eventName];
|
|
1535
|
+
} else {
|
|
1536
|
+
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1537
|
+
if (eventHandler) {
|
|
1538
|
+
const eventConfig = await eventHandler.configuration.configureInteractive();
|
|
1539
|
+
events[eventName] = eventConfig;
|
|
1076
1540
|
}
|
|
1077
|
-
const config = {};
|
|
1078
|
-
if (flags.length > 0) config.flags = flags;
|
|
1079
|
-
if (splitTerminal) config.split_terminal = splitTerminal;
|
|
1080
|
-
return config;
|
|
1081
|
-
},
|
|
1082
|
-
getDefaultConfig() {
|
|
1083
|
-
return true;
|
|
1084
1541
|
}
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
static getClaudeCommand(config) {
|
|
1088
|
-
if (typeof config === "boolean" || config === void 0) {
|
|
1089
|
-
return "claude";
|
|
1090
1542
|
}
|
|
1091
|
-
const flags = config.flags || [];
|
|
1092
|
-
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
1093
1543
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
stdio: "inherit"
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
},
|
|
1110
|
-
generateShellCommand(context) {
|
|
1111
|
-
const claudeConfig = context.project.events.claude;
|
|
1112
|
-
return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1544
|
+
const updatedConfig = {
|
|
1545
|
+
path: relativePath,
|
|
1546
|
+
ide,
|
|
1547
|
+
events
|
|
1548
|
+
};
|
|
1549
|
+
const confirmed = await confirm3({
|
|
1550
|
+
message: "Save changes?",
|
|
1551
|
+
default: true
|
|
1552
|
+
});
|
|
1553
|
+
if (confirmed) {
|
|
1554
|
+
config.setProject(name, updatedConfig);
|
|
1555
|
+
log.info(`Project '${name}' updated successfully!`);
|
|
1115
1556
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1557
|
+
}
|
|
1558
|
+
async function deleteProjectManage(ctx) {
|
|
1559
|
+
const { config, log } = ctx;
|
|
1560
|
+
const projects = config.getProjects();
|
|
1561
|
+
const baseProjects = Object.keys(projects).filter((name2) => !name2.includes("#"));
|
|
1562
|
+
if (baseProjects.length === 0) {
|
|
1563
|
+
log.info("No projects to delete.");
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const name = await select({
|
|
1567
|
+
message: "Select project to delete:",
|
|
1568
|
+
choices: baseProjects.map((n) => ({ name: n, value: n }))
|
|
1569
|
+
});
|
|
1570
|
+
const branchPrefix = `${name}#`;
|
|
1571
|
+
const branches = Object.keys(projects).filter((n) => n.startsWith(branchPrefix));
|
|
1572
|
+
if (branches.length > 0) {
|
|
1573
|
+
log.warn(`This project has ${branches.length} branch configuration(s).`);
|
|
1574
|
+
const deleteAll = await confirm3({
|
|
1575
|
+
message: "Delete all branch configurations as well?",
|
|
1576
|
+
default: false
|
|
1577
|
+
});
|
|
1578
|
+
if (deleteAll) {
|
|
1579
|
+
for (const branch of branches) {
|
|
1580
|
+
config.deleteProject(branch);
|
|
1126
1581
|
}
|
|
1127
|
-
}
|
|
1582
|
+
}
|
|
1128
1583
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
{
|
|
1137
|
-
config: { flags: ["--model", "opus"], split_terminal: true },
|
|
1138
|
-
description: "Use Opus model with split terminal"
|
|
1139
|
-
}
|
|
1140
|
-
]
|
|
1141
|
-
};
|
|
1584
|
+
const confirmed = await confirm3({
|
|
1585
|
+
message: `Are you sure you want to delete '${name}'?`,
|
|
1586
|
+
default: false
|
|
1587
|
+
});
|
|
1588
|
+
if (confirmed) {
|
|
1589
|
+
config.deleteProject(name);
|
|
1590
|
+
log.info(`Project '${name}' deleted.`);
|
|
1142
1591
|
}
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1592
|
+
}
|
|
1593
|
+
function listProjectsManage(ctx) {
|
|
1594
|
+
const { config } = ctx;
|
|
1595
|
+
const projects = config.getProjects();
|
|
1596
|
+
const defaults = config.getDefaults();
|
|
1597
|
+
console.log("\nConfigured projects:\n");
|
|
1598
|
+
const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
|
|
1599
|
+
for (const name of baseProjects) {
|
|
1600
|
+
const project = projects[name];
|
|
1601
|
+
const fullPath = defaults?.base ? File3.from(defaults.base).join(project.path).path : project.path;
|
|
1602
|
+
console.log(` ${name}`);
|
|
1603
|
+
console.log(` Path: ${fullPath}`);
|
|
1604
|
+
console.log(` IDE: ${project.ide || "not set"}`);
|
|
1605
|
+
console.log(` Events: ${Object.keys(project.events).join(", ") || "none"}`);
|
|
1606
|
+
const branchPrefix = `${name}#`;
|
|
1607
|
+
const branches = Object.keys(projects).filter((n) => n.startsWith(branchPrefix));
|
|
1608
|
+
if (branches.length > 0) {
|
|
1609
|
+
console.log(` Branches: ${branches.length}`);
|
|
1610
|
+
}
|
|
1611
|
+
console.log();
|
|
1158
1612
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if (typeof config === "object" && config !== null) {
|
|
1169
|
-
const cfg = config;
|
|
1170
|
-
if (cfg.compose_file !== void 0 && typeof cfg.compose_file !== "string") {
|
|
1171
|
-
return "docker.compose_file must be a string";
|
|
1172
|
-
}
|
|
1173
|
-
if (cfg.services !== void 0) {
|
|
1174
|
-
if (!Array.isArray(cfg.services)) {
|
|
1175
|
-
return "docker.services must be an array";
|
|
1176
|
-
}
|
|
1177
|
-
for (const service of cfg.services) {
|
|
1178
|
-
if (typeof service !== "string") {
|
|
1179
|
-
return "docker.services must contain only strings";
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
return true;
|
|
1184
|
-
}
|
|
1185
|
-
return "docker config must be a boolean, string (compose file), or object";
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1613
|
+
}
|
|
1614
|
+
async function editBranchManage(projectName, ctx) {
|
|
1615
|
+
const { config, log } = ctx;
|
|
1616
|
+
const projects = config.getProjects();
|
|
1617
|
+
const branchPrefix = `${projectName}#`;
|
|
1618
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1619
|
+
if (branches.length === 0) {
|
|
1620
|
+
log.info("No branch configurations to edit.");
|
|
1621
|
+
return;
|
|
1188
1622
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1623
|
+
const branchName = await select({
|
|
1624
|
+
message: "Select branch configuration to edit:",
|
|
1625
|
+
choices: branches.map((n) => ({
|
|
1626
|
+
name: n.substring(branchPrefix.length),
|
|
1627
|
+
value: n
|
|
1628
|
+
}))
|
|
1629
|
+
});
|
|
1630
|
+
const branch = projects[branchName];
|
|
1631
|
+
const keepEvents = await confirm3({
|
|
1632
|
+
message: "Keep existing event configuration?",
|
|
1633
|
+
default: true
|
|
1634
|
+
});
|
|
1635
|
+
let events = branch.events;
|
|
1636
|
+
if (!keepEvents) {
|
|
1637
|
+
const availableEvents = EventRegistry.getEventsForManageUI();
|
|
1638
|
+
const currentEvents = Object.keys(branch.events);
|
|
1639
|
+
const selectedEvents = await checkbox({
|
|
1640
|
+
message: "Select events to enable:",
|
|
1641
|
+
choices: availableEvents.map((e) => ({
|
|
1642
|
+
name: `${e.name} - ${e.description}`,
|
|
1643
|
+
value: e.value,
|
|
1644
|
+
checked: currentEvents.includes(e.value)
|
|
1645
|
+
}))
|
|
1646
|
+
});
|
|
1647
|
+
events = {};
|
|
1648
|
+
for (const eventName of selectedEvents) {
|
|
1649
|
+
if (branch.events[eventName]) {
|
|
1650
|
+
events[eventName] = branch.events[eventName];
|
|
1651
|
+
} else {
|
|
1652
|
+
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1653
|
+
if (eventHandler) {
|
|
1654
|
+
const eventConfig = await eventHandler.configuration.configureInteractive();
|
|
1655
|
+
events[eventName] = eventConfig;
|
|
1206
1656
|
}
|
|
1207
|
-
return {
|
|
1208
|
-
compose_file: composeFile,
|
|
1209
|
-
services
|
|
1210
|
-
};
|
|
1211
|
-
},
|
|
1212
|
-
getDefaultConfig() {
|
|
1213
|
-
return { compose_file: "docker-compose.yml" };
|
|
1214
1657
|
}
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
static getDockerCommand(config) {
|
|
1218
|
-
if (typeof config === "boolean" || config === void 0) {
|
|
1219
|
-
return "docker-compose up -d";
|
|
1220
1658
|
}
|
|
1221
|
-
if (typeof config === "string") {
|
|
1222
|
-
return `docker-compose -f ${config} up -d`;
|
|
1223
|
-
}
|
|
1224
|
-
const composeFile = config.compose_file || "docker-compose.yml";
|
|
1225
|
-
const services = config.services?.join(" ") || "";
|
|
1226
|
-
return `docker-compose -f ${composeFile} up -d ${services}`.trim();
|
|
1227
1659
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
const [cmd, ...args] = dockerCommand.split(" ");
|
|
1240
|
-
spawn6(cmd, args, {
|
|
1241
|
-
cwd: project.path.path,
|
|
1242
|
-
stdio: "inherit"
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
},
|
|
1246
|
-
generateShellCommand(context) {
|
|
1247
|
-
const dockerConfig = context.project.events.docker;
|
|
1248
|
-
return [_DockerEvent.getDockerCommand(dockerConfig)];
|
|
1249
|
-
}
|
|
1250
|
-
};
|
|
1660
|
+
const updatedConfig = {
|
|
1661
|
+
...branch,
|
|
1662
|
+
events
|
|
1663
|
+
};
|
|
1664
|
+
const confirmed = await confirm3({
|
|
1665
|
+
message: "Save changes?",
|
|
1666
|
+
default: true
|
|
1667
|
+
});
|
|
1668
|
+
if (confirmed) {
|
|
1669
|
+
config.setProject(branchName, updatedConfig);
|
|
1670
|
+
log.info(`Branch configuration '${branchName}' updated successfully!`);
|
|
1251
1671
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1672
|
+
}
|
|
1673
|
+
async function deleteBranchManage(projectName, ctx) {
|
|
1674
|
+
const { config, log } = ctx;
|
|
1675
|
+
const projects = config.getProjects();
|
|
1676
|
+
const branchPrefix = `${projectName}#`;
|
|
1677
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1678
|
+
if (branches.length === 0) {
|
|
1679
|
+
log.info("No branch configurations to delete.");
|
|
1680
|
+
return;
|
|
1254
1681
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1682
|
+
const branchName = await select({
|
|
1683
|
+
message: "Select branch configuration to delete:",
|
|
1684
|
+
choices: branches.map((n) => ({
|
|
1685
|
+
name: n.substring(branchPrefix.length),
|
|
1686
|
+
value: n
|
|
1687
|
+
}))
|
|
1688
|
+
});
|
|
1689
|
+
const confirmed = await confirm3({
|
|
1690
|
+
message: `Are you sure you want to delete '${branchName}'?`,
|
|
1691
|
+
default: false
|
|
1692
|
+
});
|
|
1693
|
+
if (confirmed) {
|
|
1694
|
+
config.deleteProject(branchName);
|
|
1695
|
+
log.info(`Branch configuration '${branchName}' deleted.`);
|
|
1268
1696
|
}
|
|
1269
|
-
}
|
|
1697
|
+
}
|
|
1698
|
+
function listBranchesManage(projectName, ctx) {
|
|
1699
|
+
const { config } = ctx;
|
|
1700
|
+
const projects = config.getProjects();
|
|
1701
|
+
const branchPrefix = `${projectName}#`;
|
|
1702
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1703
|
+
console.log(`
|
|
1704
|
+
Branch configurations for '${projectName}':
|
|
1705
|
+
`);
|
|
1706
|
+
for (const branchName of branches) {
|
|
1707
|
+
const branch = projects[branchName];
|
|
1708
|
+
const shortName = branchName.substring(branchPrefix.length);
|
|
1709
|
+
console.log(` ${shortName}`);
|
|
1710
|
+
console.log(` Events: ${Object.keys(branch.events).join(", ") || "none"}`);
|
|
1711
|
+
console.log();
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
var IDE_CHOICES;
|
|
1715
|
+
var init_interactive = __esm({
|
|
1716
|
+
"src/commands/interactive.ts"() {
|
|
1717
|
+
"use strict";
|
|
1718
|
+
init_environment();
|
|
1719
|
+
init_registry();
|
|
1720
|
+
IDE_CHOICES = [
|
|
1721
|
+
{ name: "Visual Studio Code", value: "vscode" },
|
|
1722
|
+
{ name: "IntelliJ IDEA", value: "idea" },
|
|
1723
|
+
{ name: "Atom", value: "atom" }
|
|
1724
|
+
];
|
|
1725
|
+
}
|
|
1726
|
+
});
|
|
1270
1727
|
|
|
1271
|
-
// src/
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1728
|
+
// src/commands/index.ts
|
|
1729
|
+
init_config();
|
|
1730
|
+
init_environment();
|
|
1731
|
+
import { Command as Command8 } from "commander";
|
|
1732
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1733
|
+
import { join, dirname } from "path";
|
|
1734
|
+
import { fileURLToPath } from "url";
|
|
1735
|
+
import loog from "loog";
|
|
1736
|
+
import omelette from "omelette";
|
|
1737
|
+
import File7 from "phylo";
|
|
1738
|
+
|
|
1739
|
+
// src/commands/open.ts
|
|
1740
|
+
init_environment();
|
|
1741
|
+
import { Command } from "commander";
|
|
1742
|
+
import File4 from "phylo";
|
|
1743
|
+
|
|
1744
|
+
// src/lib/tmux.ts
|
|
1745
|
+
import { exec as execCallback, spawn } from "child_process";
|
|
1746
|
+
import { promisify } from "util";
|
|
1747
|
+
var exec = promisify(execCallback);
|
|
1748
|
+
var TmuxManager = class {
|
|
1749
|
+
sessionPrefix = "workon-";
|
|
1750
|
+
async isTmuxAvailable() {
|
|
1751
|
+
try {
|
|
1752
|
+
await exec("which tmux");
|
|
1753
|
+
return true;
|
|
1754
|
+
} catch {
|
|
1755
|
+
return false;
|
|
1294
1756
|
}
|
|
1295
1757
|
}
|
|
1296
|
-
|
|
1297
|
-
* Validate if a class is a proper event
|
|
1298
|
-
*/
|
|
1299
|
-
isValidEvent(EventClass) {
|
|
1758
|
+
async sessionExists(sessionName) {
|
|
1300
1759
|
try {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
return metadata !== void 0 && typeof metadata.name === "string" && typeof metadata.displayName === "string" && typeof EventClass.validation === "object" && typeof EventClass.configuration === "object" && typeof EventClass.processing === "object";
|
|
1760
|
+
await exec(`tmux has-session -t "${sessionName}"`);
|
|
1761
|
+
return true;
|
|
1304
1762
|
} catch {
|
|
1305
1763
|
return false;
|
|
1306
1764
|
}
|
|
1307
1765
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
*/
|
|
1318
|
-
getEventByName(name) {
|
|
1319
|
-
this.ensureInitialized();
|
|
1320
|
-
return this._events.get(name) ?? null;
|
|
1321
|
-
}
|
|
1322
|
-
/**
|
|
1323
|
-
* Get all events for management UI
|
|
1324
|
-
*/
|
|
1325
|
-
getEventsForManageUI() {
|
|
1326
|
-
this.ensureInitialized();
|
|
1327
|
-
const events = [];
|
|
1328
|
-
for (const [name, EventClass] of this._events) {
|
|
1329
|
-
const metadata = EventClass.metadata;
|
|
1330
|
-
events.push({
|
|
1331
|
-
name: metadata.displayName,
|
|
1332
|
-
value: name,
|
|
1333
|
-
description: metadata.description
|
|
1334
|
-
});
|
|
1766
|
+
getSessionName(projectName) {
|
|
1767
|
+
return `${this.sessionPrefix}${projectName}`;
|
|
1768
|
+
}
|
|
1769
|
+
async killSession(sessionName) {
|
|
1770
|
+
try {
|
|
1771
|
+
await exec(`tmux kill-session -t "${sessionName}"`);
|
|
1772
|
+
return true;
|
|
1773
|
+
} catch {
|
|
1774
|
+
return false;
|
|
1335
1775
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1776
|
+
}
|
|
1777
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
1778
|
+
const sessionName = this.getSessionName(projectName);
|
|
1779
|
+
if (await this.sessionExists(sessionName)) {
|
|
1780
|
+
await this.killSession(sessionName);
|
|
1781
|
+
}
|
|
1782
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1783
|
+
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
1784
|
+
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
1785
|
+
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
1786
|
+
return sessionName;
|
|
1787
|
+
}
|
|
1788
|
+
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1789
|
+
const sessionName = this.getSessionName(projectName);
|
|
1790
|
+
if (await this.sessionExists(sessionName)) {
|
|
1791
|
+
await this.killSession(sessionName);
|
|
1792
|
+
}
|
|
1793
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1794
|
+
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
1795
|
+
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
1796
|
+
await exec(`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`);
|
|
1797
|
+
await exec(`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`);
|
|
1798
|
+
await exec(`tmux resize-pane -t "${sessionName}:0.2" -y 10`);
|
|
1799
|
+
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
1800
|
+
return sessionName;
|
|
1801
|
+
}
|
|
1802
|
+
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1803
|
+
const sessionName = this.getSessionName(projectName);
|
|
1804
|
+
if (await this.sessionExists(sessionName)) {
|
|
1805
|
+
await this.killSession(sessionName);
|
|
1806
|
+
}
|
|
1807
|
+
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`);
|
|
1808
|
+
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`);
|
|
1809
|
+
await exec(`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`);
|
|
1810
|
+
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
1811
|
+
return sessionName;
|
|
1812
|
+
}
|
|
1813
|
+
async attachToSession(sessionName) {
|
|
1814
|
+
if (process.env.TMUX) {
|
|
1815
|
+
await exec(`tmux switch-client -t "${sessionName}"`);
|
|
1816
|
+
} else {
|
|
1817
|
+
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
1818
|
+
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1819
|
+
if (useiTermIntegration) {
|
|
1820
|
+
spawn("tmux", ["-CC", "attach-session", "-t", sessionName], {
|
|
1821
|
+
stdio: "inherit",
|
|
1822
|
+
detached: true
|
|
1823
|
+
});
|
|
1824
|
+
} else {
|
|
1825
|
+
spawn("tmux", ["attach-session", "-t", sessionName], {
|
|
1826
|
+
stdio: "inherit",
|
|
1827
|
+
detached: true
|
|
1351
1828
|
});
|
|
1352
1829
|
}
|
|
1353
1830
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1831
|
+
}
|
|
1832
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
1833
|
+
const sessionName = this.getSessionName(projectName);
|
|
1834
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1835
|
+
return [
|
|
1836
|
+
`# Create tmux split session for ${projectName}`,
|
|
1837
|
+
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
1838
|
+
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
1839
|
+
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
1840
|
+
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
1841
|
+
this.getAttachCommand(sessionName)
|
|
1842
|
+
];
|
|
1843
|
+
}
|
|
1844
|
+
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1845
|
+
const sessionName = this.getSessionName(projectName);
|
|
1846
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1847
|
+
return [
|
|
1848
|
+
`# Create tmux three-pane session for ${projectName}`,
|
|
1849
|
+
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
1850
|
+
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
1851
|
+
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
1852
|
+
`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`,
|
|
1853
|
+
`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`,
|
|
1854
|
+
`tmux resize-pane -t "${sessionName}:0.2" -y 10`,
|
|
1855
|
+
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
1856
|
+
this.getAttachCommand(sessionName)
|
|
1857
|
+
];
|
|
1858
|
+
}
|
|
1859
|
+
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1860
|
+
const sessionName = this.getSessionName(projectName);
|
|
1861
|
+
return [
|
|
1862
|
+
`# Create tmux two-pane session with npm for ${projectName}`,
|
|
1863
|
+
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
1864
|
+
`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`,
|
|
1865
|
+
`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`,
|
|
1866
|
+
`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`,
|
|
1867
|
+
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
1868
|
+
this.getAttachCommand(sessionName)
|
|
1869
|
+
];
|
|
1870
|
+
}
|
|
1871
|
+
getAttachCommand(sessionName) {
|
|
1872
|
+
if (process.env.TMUX) {
|
|
1873
|
+
return `tmux switch-client -t "${sessionName}"`;
|
|
1373
1874
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
*/
|
|
1379
|
-
ensureInitialized() {
|
|
1380
|
-
if (!this._initialized) {
|
|
1381
|
-
throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
|
|
1875
|
+
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
1876
|
+
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1877
|
+
if (useiTermIntegration) {
|
|
1878
|
+
return `tmux -CC attach-session -t "${sessionName}"`;
|
|
1382
1879
|
}
|
|
1880
|
+
return `tmux attach-session -t "${sessionName}"`;
|
|
1383
1881
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1882
|
+
async listWorkonSessions() {
|
|
1883
|
+
try {
|
|
1884
|
+
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
1885
|
+
return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
|
|
1886
|
+
} catch {
|
|
1887
|
+
return [];
|
|
1888
|
+
}
|
|
1390
1889
|
}
|
|
1391
1890
|
};
|
|
1392
|
-
var EventRegistry = new EventRegistryClass();
|
|
1393
1891
|
|
|
1394
1892
|
// src/commands/open.ts
|
|
1893
|
+
init_registry();
|
|
1395
1894
|
function createOpenCommand(ctx) {
|
|
1396
1895
|
const { config, log } = ctx;
|
|
1397
1896
|
const command = new Command("open").description("Open a project by passing its project id").argument("[project]", "The id of the project to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("-n, --dry-run", "Show what would happen without executing").option("--shell", "Output shell commands instead of spawning processes").action(async (projectArg, options) => {
|
|
@@ -1806,8 +2305,9 @@ function createConfigCommand(ctx) {
|
|
|
1806
2305
|
}
|
|
1807
2306
|
|
|
1808
2307
|
// src/commands/manage.ts
|
|
2308
|
+
init_registry();
|
|
1809
2309
|
import { Command as Command6 } from "commander";
|
|
1810
|
-
import { select as select2, input as input5, confirm as
|
|
2310
|
+
import { select as select2, input as input5, confirm as confirm4, checkbox as checkbox2 } from "@inquirer/prompts";
|
|
1811
2311
|
import File5 from "phylo";
|
|
1812
2312
|
var IDE_CHOICES2 = [
|
|
1813
2313
|
{ name: "Visual Studio Code", value: "vscode" },
|
|
@@ -1939,7 +2439,7 @@ async function createProject(ctx) {
|
|
|
1939
2439
|
}
|
|
1940
2440
|
console.log("\nProject configuration:");
|
|
1941
2441
|
console.log(JSON.stringify(projectConfig, null, 2));
|
|
1942
|
-
const confirmed = await
|
|
2442
|
+
const confirmed = await confirm4({
|
|
1943
2443
|
message: "Save this project?",
|
|
1944
2444
|
default: true
|
|
1945
2445
|
});
|
|
@@ -1990,7 +2490,7 @@ async function editProject(ctx) {
|
|
|
1990
2490
|
message: "Project homepage URL:",
|
|
1991
2491
|
default: project.homepage || ""
|
|
1992
2492
|
});
|
|
1993
|
-
const keepEvents = await
|
|
2493
|
+
const keepEvents = await confirm4({
|
|
1994
2494
|
message: "Keep existing event configuration?",
|
|
1995
2495
|
default: true
|
|
1996
2496
|
});
|
|
@@ -2029,7 +2529,7 @@ async function editProject(ctx) {
|
|
|
2029
2529
|
}
|
|
2030
2530
|
console.log("\nUpdated configuration:");
|
|
2031
2531
|
console.log(JSON.stringify(updatedConfig, null, 2));
|
|
2032
|
-
const confirmed = await
|
|
2532
|
+
const confirmed = await confirm4({
|
|
2033
2533
|
message: "Save changes?",
|
|
2034
2534
|
default: true
|
|
2035
2535
|
});
|
|
@@ -2052,7 +2552,7 @@ async function deleteProject(ctx) {
|
|
|
2052
2552
|
message: "Select project to delete:",
|
|
2053
2553
|
choices: projectNames.map((n) => ({ name: n, value: n }))
|
|
2054
2554
|
});
|
|
2055
|
-
const confirmed = await
|
|
2555
|
+
const confirmed = await confirm4({
|
|
2056
2556
|
message: `Are you sure you want to delete '${name}'?`,
|
|
2057
2557
|
default: false
|
|
2058
2558
|
});
|
|
@@ -2081,6 +2581,142 @@ async function listProjects(ctx) {
|
|
|
2081
2581
|
}
|
|
2082
2582
|
}
|
|
2083
2583
|
|
|
2584
|
+
// src/commands/add.ts
|
|
2585
|
+
import { Command as Command7 } from "commander";
|
|
2586
|
+
import { existsSync, readFileSync } from "fs";
|
|
2587
|
+
import { basename, resolve } from "path";
|
|
2588
|
+
import File6 from "phylo";
|
|
2589
|
+
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
2590
|
+
function createAddCommand(ctx) {
|
|
2591
|
+
const { log } = ctx;
|
|
2592
|
+
return new Command7("add").description("Add a project from a directory path").argument("[path]", "Path to the project directory (defaults to current directory)", ".").option("-d, --debug", "Enable debug logging").option("-n, --name <name>", "Override the detected project name").option(
|
|
2593
|
+
"-i, --ide <ide>",
|
|
2594
|
+
"Specify the IDE to use (vscode, idea, atom, code, subl, vim, emacs)"
|
|
2595
|
+
).option("-f, --force", "Overwrite existing project with same name").action(async (pathArg, options) => {
|
|
2596
|
+
if (options.debug) {
|
|
2597
|
+
log.setLogLevel("debug");
|
|
2598
|
+
}
|
|
2599
|
+
await addProject(pathArg, options, ctx);
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
async function addProject(pathArg, options, ctx) {
|
|
2603
|
+
const { config, log } = ctx;
|
|
2604
|
+
const defaults = config.getDefaults();
|
|
2605
|
+
const projects = config.getProjects();
|
|
2606
|
+
const targetPath = resolve(pathArg);
|
|
2607
|
+
log.debug(`Resolved path: ${targetPath}`);
|
|
2608
|
+
if (!existsSync(targetPath)) {
|
|
2609
|
+
log.error(`Path does not exist: ${targetPath}`);
|
|
2610
|
+
process.exit(1);
|
|
2611
|
+
}
|
|
2612
|
+
const pathFile = File6.from(targetPath);
|
|
2613
|
+
try {
|
|
2614
|
+
const stat = pathFile.stat();
|
|
2615
|
+
if (!stat.isDirectory()) {
|
|
2616
|
+
log.error(`Path is not a directory: ${targetPath}`);
|
|
2617
|
+
process.exit(1);
|
|
2618
|
+
}
|
|
2619
|
+
} catch {
|
|
2620
|
+
log.error(`Cannot access path: ${targetPath}`);
|
|
2621
|
+
process.exit(1);
|
|
2622
|
+
}
|
|
2623
|
+
const discovery = discoverProject(targetPath, log);
|
|
2624
|
+
log.debug(`Discovery result: ${JSON.stringify(discovery)}`);
|
|
2625
|
+
const projectName = options.name || discovery.name;
|
|
2626
|
+
log.debug(`Project name: ${projectName}`);
|
|
2627
|
+
if (!/^[\w-]+$/.test(projectName)) {
|
|
2628
|
+
log.error(`Invalid project name: ${projectName}`);
|
|
2629
|
+
log.error("Name can only contain letters, numbers, underscores, and hyphens");
|
|
2630
|
+
process.exit(1);
|
|
2631
|
+
}
|
|
2632
|
+
if (projectName in projects && !options.force) {
|
|
2633
|
+
const overwrite = await confirm5({
|
|
2634
|
+
message: `Project '${projectName}' already exists. Overwrite?`,
|
|
2635
|
+
default: false
|
|
2636
|
+
});
|
|
2637
|
+
if (!overwrite) {
|
|
2638
|
+
log.info("Cancelled.");
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
const ide = options.ide || discovery.detectedIde || "vscode";
|
|
2643
|
+
log.debug(`IDE: ${ide}`);
|
|
2644
|
+
let relativePath = targetPath;
|
|
2645
|
+
if (defaults?.base) {
|
|
2646
|
+
const baseDir = File6.from(defaults.base);
|
|
2647
|
+
try {
|
|
2648
|
+
const relPath = pathFile.relativize(baseDir.path);
|
|
2649
|
+
if (relPath && !relPath.path.startsWith("..")) {
|
|
2650
|
+
relativePath = relPath.path;
|
|
2651
|
+
}
|
|
2652
|
+
} catch {
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
log.debug(`Relative path: ${relativePath}`);
|
|
2656
|
+
const projectConfig = {
|
|
2657
|
+
path: relativePath,
|
|
2658
|
+
ide,
|
|
2659
|
+
events: {
|
|
2660
|
+
cwd: true,
|
|
2661
|
+
ide: true
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
if ((discovery.isNode || discovery.isBun) && discovery.packageJson) {
|
|
2665
|
+
const scripts = discovery.packageJson.scripts;
|
|
2666
|
+
if (scripts && (scripts.dev || scripts.start)) {
|
|
2667
|
+
projectConfig.events.npm = scripts.dev ? "dev" : "start";
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
config.setProject(projectName, projectConfig);
|
|
2671
|
+
log.info(`Added project '${projectName}'`);
|
|
2672
|
+
log.info(` Path: ${relativePath}`);
|
|
2673
|
+
log.info(` IDE: ${ide}`);
|
|
2674
|
+
log.info(` Events: ${Object.keys(projectConfig.events).join(", ")}`);
|
|
2675
|
+
log.info("");
|
|
2676
|
+
log.info(`Use 'workon ${projectName}' to start working!`);
|
|
2677
|
+
}
|
|
2678
|
+
function discoverProject(targetPath, log) {
|
|
2679
|
+
const dirName = basename(targetPath);
|
|
2680
|
+
const discovery = {
|
|
2681
|
+
name: dirName,
|
|
2682
|
+
isNode: false,
|
|
2683
|
+
isBun: false,
|
|
2684
|
+
detectedIde: null,
|
|
2685
|
+
packageJson: null
|
|
2686
|
+
};
|
|
2687
|
+
const packageJsonPath = resolve(targetPath, "package.json");
|
|
2688
|
+
if (existsSync(packageJsonPath)) {
|
|
2689
|
+
discovery.isNode = true;
|
|
2690
|
+
log.debug("Detected Node project (package.json found)");
|
|
2691
|
+
try {
|
|
2692
|
+
const content = readFileSync(packageJsonPath, "utf-8");
|
|
2693
|
+
discovery.packageJson = JSON.parse(content);
|
|
2694
|
+
const pkgName = discovery.packageJson?.name;
|
|
2695
|
+
if (pkgName && /^[\w-]+$/.test(pkgName)) {
|
|
2696
|
+
discovery.name = pkgName;
|
|
2697
|
+
log.debug(`Using name from package.json: ${pkgName}`);
|
|
2698
|
+
}
|
|
2699
|
+
} catch (error) {
|
|
2700
|
+
log.debug(`Failed to parse package.json: ${error.message}`);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
const bunLockPath = resolve(targetPath, "bun.lockb");
|
|
2704
|
+
if (existsSync(bunLockPath)) {
|
|
2705
|
+
discovery.isBun = true;
|
|
2706
|
+
log.debug("Detected Bun project (bun.lockb found)");
|
|
2707
|
+
}
|
|
2708
|
+
const vscodeDir = resolve(targetPath, ".vscode");
|
|
2709
|
+
const ideaDir = resolve(targetPath, ".idea");
|
|
2710
|
+
if (existsSync(vscodeDir)) {
|
|
2711
|
+
discovery.detectedIde = "vscode";
|
|
2712
|
+
log.debug("Detected VS Code (.vscode directory found)");
|
|
2713
|
+
} else if (existsSync(ideaDir)) {
|
|
2714
|
+
discovery.detectedIde = "idea";
|
|
2715
|
+
log.debug("Detected IntelliJ IDEA (.idea directory found)");
|
|
2716
|
+
}
|
|
2717
|
+
return discovery;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2084
2720
|
// src/commands/index.ts
|
|
2085
2721
|
var __filename = fileURLToPath(import.meta.url);
|
|
2086
2722
|
var __dirname = dirname(__filename);
|
|
@@ -2091,16 +2727,16 @@ function findPackageJson() {
|
|
|
2091
2727
|
join(process.cwd(), "package.json")
|
|
2092
2728
|
];
|
|
2093
2729
|
for (const p of paths) {
|
|
2094
|
-
if (
|
|
2730
|
+
if (existsSync2(p)) {
|
|
2095
2731
|
return p;
|
|
2096
2732
|
}
|
|
2097
2733
|
}
|
|
2098
2734
|
throw new Error("Could not find package.json");
|
|
2099
2735
|
}
|
|
2100
2736
|
function createCli() {
|
|
2101
|
-
const program2 = new
|
|
2737
|
+
const program2 = new Command8();
|
|
2102
2738
|
const packageJsonPath = findPackageJson();
|
|
2103
|
-
const packageJson = JSON.parse(
|
|
2739
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
2104
2740
|
const config = new Config();
|
|
2105
2741
|
const log = loog({
|
|
2106
2742
|
prefixStyle: "ascii",
|
|
@@ -2109,35 +2745,45 @@ function createCli() {
|
|
|
2109
2745
|
config.set("pkg", packageJson);
|
|
2110
2746
|
EnvironmentRecognizer.configure(config, log);
|
|
2111
2747
|
const completion = setupCompletion(config);
|
|
2112
|
-
program2.name("workon").description("Work on something great!").version(packageJson.version).option("-d, --debug", "Enable debug logging").option("--completion", "Setup shell tab completion").option("--shell", "Output shell commands for evaluation").option("--init", "Generate shell integration function").hook("preAction", (thisCommand) => {
|
|
2748
|
+
program2.name("workon").description("Work on something great!").version(packageJson.version).argument("[project]", "Project name to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("--completion", "Setup shell tab completion").option("--shell", "Output shell commands for evaluation").option("--init", "Generate shell integration function").hook("preAction", (thisCommand) => {
|
|
2113
2749
|
const opts = thisCommand.opts();
|
|
2114
2750
|
if (opts.debug) {
|
|
2115
2751
|
log.setLogLevel("debug");
|
|
2116
2752
|
}
|
|
2117
|
-
}).action(
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2753
|
+
}).action(
|
|
2754
|
+
async (project, options) => {
|
|
2755
|
+
if (options.debug) {
|
|
2756
|
+
log.setLogLevel("debug");
|
|
2757
|
+
}
|
|
2758
|
+
if (options.completion) {
|
|
2759
|
+
log.debug("Setting up command-line completion");
|
|
2760
|
+
completion.setupShellInitFile();
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
if (options.init) {
|
|
2764
|
+
log.debug("Generating shell integration function");
|
|
2765
|
+
outputShellInit(program2);
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
if (project) {
|
|
2769
|
+
const args = ["open", project];
|
|
2770
|
+
if (options.shell) args.push("--shell");
|
|
2771
|
+
if (options.debug) args.push("--debug");
|
|
2772
|
+
await program2.parseAsync(["node", "workon", ...args]);
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
const environment = await EnvironmentRecognizer.recognize(File7.cwd());
|
|
2776
|
+
program2.setOptionValue("_environment", environment);
|
|
2777
|
+
program2.setOptionValue("_config", config);
|
|
2778
|
+
program2.setOptionValue("_log", log);
|
|
2779
|
+
const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
2780
|
+
await runInteractive2({ config, log, environment });
|
|
2130
2781
|
}
|
|
2131
|
-
|
|
2132
|
-
program2.setOptionValue("_environment", environment);
|
|
2133
|
-
program2.setOptionValue("_config", config);
|
|
2134
|
-
program2.setOptionValue("_log", log);
|
|
2135
|
-
const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
2136
|
-
await runInteractive2({ config, log, environment });
|
|
2137
|
-
});
|
|
2782
|
+
);
|
|
2138
2783
|
program2.setOptionValue("_config", config);
|
|
2139
2784
|
program2.setOptionValue("_log", log);
|
|
2140
2785
|
program2.addCommand(createOpenCommand({ config, log }));
|
|
2786
|
+
program2.addCommand(createAddCommand({ config, log }));
|
|
2141
2787
|
program2.addCommand(createConfigCommand({ config, log }));
|
|
2142
2788
|
program2.addCommand(createManageCommand({ config, log }));
|
|
2143
2789
|
program2.on("command:*", async (operands) => {
|