workon 3.1.0 → 3.2.1
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 +1849 -1334
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +105 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +105 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,27 +9,314 @@ 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 (error) {
|
|
240
|
+
this.log.debug(`Git branch detection failed: ${error.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return this.getProjectEnvironment(base, matching);
|
|
244
|
+
}
|
|
245
|
+
static getAllProjects(refresh = false) {
|
|
246
|
+
if (this.projects.length > 0 && !refresh) {
|
|
247
|
+
return this.projects;
|
|
248
|
+
}
|
|
249
|
+
const defaults = this.config.getDefaults();
|
|
250
|
+
if (!defaults?.base) {
|
|
251
|
+
this.projects = [];
|
|
252
|
+
return this.projects;
|
|
253
|
+
}
|
|
254
|
+
const baseDir = File2.from(defaults.base);
|
|
255
|
+
const projectsMap = this.config.getProjects();
|
|
256
|
+
this.projects = Object.entries(projectsMap).map(([name, project]) => ({
|
|
257
|
+
...project,
|
|
258
|
+
name,
|
|
259
|
+
path: baseDir.join(project.path)
|
|
260
|
+
}));
|
|
261
|
+
return this.projects;
|
|
262
|
+
}
|
|
263
|
+
static getProjectEnvironment(base, _matching) {
|
|
264
|
+
const exactName = `${base.name}#${base.branch}`;
|
|
265
|
+
const exactProj = this.projects.find((p) => p.name === exactName);
|
|
266
|
+
const toProjectConfig = (p) => ({
|
|
267
|
+
name: p.name,
|
|
268
|
+
path: p.path.path,
|
|
269
|
+
// Convert PhyloFile to string path
|
|
270
|
+
ide: p.ide,
|
|
271
|
+
homepage: p.homepage,
|
|
272
|
+
events: p.events,
|
|
273
|
+
branch: p.branch,
|
|
274
|
+
exactName
|
|
275
|
+
});
|
|
276
|
+
if (exactProj) {
|
|
277
|
+
return new ProjectEnvironment({ ...toProjectConfig(exactProj), branch: base.branch });
|
|
278
|
+
}
|
|
279
|
+
return new ProjectEnvironment(toProjectConfig(base));
|
|
280
|
+
}
|
|
281
|
+
static ensureConfigured() {
|
|
282
|
+
if (!this.configured) {
|
|
283
|
+
this.config = new Config();
|
|
284
|
+
this.log = {
|
|
285
|
+
debug: () => {
|
|
286
|
+
},
|
|
287
|
+
info: () => {
|
|
288
|
+
},
|
|
289
|
+
log: () => {
|
|
290
|
+
},
|
|
291
|
+
warn: () => {
|
|
292
|
+
},
|
|
293
|
+
error: () => {
|
|
294
|
+
},
|
|
295
|
+
setLogLevel: () => {
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
this.configured = true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// src/events/core/cwd.ts
|
|
306
|
+
import { spawn } from "child_process";
|
|
307
|
+
var CwdEvent;
|
|
308
|
+
var init_cwd = __esm({
|
|
309
|
+
"src/events/core/cwd.ts"() {
|
|
310
|
+
"use strict";
|
|
311
|
+
CwdEvent = class {
|
|
25
312
|
static get metadata() {
|
|
26
313
|
return {
|
|
27
|
-
name: "
|
|
28
|
-
displayName: "
|
|
29
|
-
description: "
|
|
30
|
-
category: "
|
|
31
|
-
requiresTmux:
|
|
32
|
-
dependencies: [
|
|
314
|
+
name: "cwd",
|
|
315
|
+
displayName: "Change directory (cwd)",
|
|
316
|
+
description: "Change current working directory to project path",
|
|
317
|
+
category: "core",
|
|
318
|
+
requiresTmux: false,
|
|
319
|
+
dependencies: []
|
|
33
320
|
};
|
|
34
321
|
}
|
|
35
322
|
static get validation() {
|
|
@@ -38,160 +325,827 @@ var init_npm = __esm({
|
|
|
38
325
|
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
39
326
|
return true;
|
|
40
327
|
}
|
|
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";
|
|
328
|
+
return "cwd config must be a boolean (true/false)";
|
|
61
329
|
}
|
|
62
330
|
};
|
|
63
331
|
}
|
|
64
332
|
static get configuration() {
|
|
65
333
|
return {
|
|
66
334
|
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
|
-
};
|
|
335
|
+
return true;
|
|
94
336
|
},
|
|
95
337
|
getDefaultConfig() {
|
|
96
|
-
return
|
|
338
|
+
return true;
|
|
97
339
|
}
|
|
98
340
|
};
|
|
99
341
|
}
|
|
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
342
|
static get processing() {
|
|
110
343
|
return {
|
|
111
344
|
async processEvent(context) {
|
|
112
345
|
const { project, isShellMode, shellCommands } = context;
|
|
113
|
-
const
|
|
114
|
-
const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
|
|
346
|
+
const projectPath = project.path.path;
|
|
115
347
|
if (isShellMode) {
|
|
116
|
-
shellCommands.push(
|
|
348
|
+
shellCommands.push(`cd "${projectPath}"`);
|
|
117
349
|
} else {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
cwd:
|
|
350
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
351
|
+
spawn(shell, [], {
|
|
352
|
+
cwd: projectPath,
|
|
121
353
|
stdio: "inherit"
|
|
122
354
|
});
|
|
123
355
|
}
|
|
124
356
|
},
|
|
125
357
|
generateShellCommand(context) {
|
|
126
|
-
const
|
|
127
|
-
return [
|
|
358
|
+
const projectPath = context.project.path.path;
|
|
359
|
+
return [`cd "${projectPath}"`];
|
|
128
360
|
}
|
|
129
361
|
};
|
|
130
362
|
}
|
|
131
363
|
static get tmux() {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
static get help() {
|
|
132
367
|
return {
|
|
133
|
-
|
|
134
|
-
|
|
368
|
+
usage: "cwd: true | false",
|
|
369
|
+
description: "Change the current working directory to the project path",
|
|
370
|
+
examples: [
|
|
371
|
+
{ config: true, description: "Enable directory change" },
|
|
372
|
+
{ config: false, description: "Disable directory change" }
|
|
373
|
+
]
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// src/events/core/ide.ts
|
|
381
|
+
import { spawn as spawn2 } from "child_process";
|
|
382
|
+
var IdeEvent;
|
|
383
|
+
var init_ide = __esm({
|
|
384
|
+
"src/events/core/ide.ts"() {
|
|
385
|
+
"use strict";
|
|
386
|
+
IdeEvent = class {
|
|
387
|
+
static get metadata() {
|
|
388
|
+
return {
|
|
389
|
+
name: "ide",
|
|
390
|
+
displayName: "Open in IDE",
|
|
391
|
+
description: "Open project in configured IDE/editor",
|
|
392
|
+
category: "core",
|
|
393
|
+
requiresTmux: false,
|
|
394
|
+
dependencies: []
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
static get validation() {
|
|
398
|
+
return {
|
|
399
|
+
validateConfig(config) {
|
|
400
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
return "ide config must be a boolean (true/false)";
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
static get configuration() {
|
|
408
|
+
return {
|
|
409
|
+
async configureInteractive() {
|
|
410
|
+
return true;
|
|
135
411
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
412
|
+
getDefaultConfig() {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
static get processing() {
|
|
418
|
+
return {
|
|
419
|
+
async processEvent(context) {
|
|
420
|
+
const { project, isShellMode, shellCommands } = context;
|
|
421
|
+
const projectPath = project.path.path;
|
|
422
|
+
const ide = project.ide || "code";
|
|
423
|
+
if (isShellMode) {
|
|
424
|
+
shellCommands.push(`${ide} "${projectPath}" &`);
|
|
425
|
+
} else {
|
|
426
|
+
spawn2(ide, [projectPath], {
|
|
427
|
+
detached: true,
|
|
428
|
+
stdio: "ignore"
|
|
429
|
+
}).unref();
|
|
139
430
|
}
|
|
140
|
-
|
|
431
|
+
},
|
|
432
|
+
generateShellCommand(context) {
|
|
433
|
+
const projectPath = context.project.path.path;
|
|
434
|
+
const ide = context.project.ide || "code";
|
|
435
|
+
return [`${ide} "${projectPath}" &`];
|
|
141
436
|
}
|
|
142
437
|
};
|
|
143
438
|
}
|
|
439
|
+
static get tmux() {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
144
442
|
static get help() {
|
|
145
443
|
return {
|
|
146
|
-
usage:
|
|
147
|
-
description: "
|
|
444
|
+
usage: "ide: true | false",
|
|
445
|
+
description: "Open the project in the configured IDE",
|
|
148
446
|
examples: [
|
|
149
|
-
{ config: true, description: "
|
|
150
|
-
{ config:
|
|
151
|
-
{ config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
|
|
447
|
+
{ config: true, description: "Enable IDE opening" },
|
|
448
|
+
{ config: false, description: "Disable IDE opening" }
|
|
152
449
|
]
|
|
153
450
|
};
|
|
154
451
|
}
|
|
155
452
|
};
|
|
156
|
-
npm_default = NpmEvent;
|
|
157
453
|
}
|
|
158
454
|
});
|
|
159
455
|
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
456
|
+
// src/events/core/web.ts
|
|
457
|
+
import { spawn as spawn3 } from "child_process";
|
|
458
|
+
import { platform } from "os";
|
|
459
|
+
var WebEvent;
|
|
460
|
+
var init_web = __esm({
|
|
461
|
+
"src/events/core/web.ts"() {
|
|
462
|
+
"use strict";
|
|
463
|
+
WebEvent = class _WebEvent {
|
|
464
|
+
static get metadata() {
|
|
465
|
+
return {
|
|
466
|
+
name: "web",
|
|
467
|
+
displayName: "Open homepage in browser",
|
|
468
|
+
description: "Open project homepage in web browser",
|
|
469
|
+
category: "core",
|
|
470
|
+
requiresTmux: false,
|
|
471
|
+
dependencies: []
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
static get validation() {
|
|
475
|
+
return {
|
|
476
|
+
validateConfig(config) {
|
|
477
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
return "web config must be a boolean (true/false)";
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
static get configuration() {
|
|
485
|
+
return {
|
|
486
|
+
async configureInteractive() {
|
|
487
|
+
return true;
|
|
488
|
+
},
|
|
489
|
+
getDefaultConfig() {
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
static getOpenCommand() {
|
|
495
|
+
const os = platform();
|
|
496
|
+
switch (os) {
|
|
497
|
+
case "darwin":
|
|
498
|
+
return "open";
|
|
499
|
+
case "win32":
|
|
500
|
+
return "start";
|
|
501
|
+
default:
|
|
502
|
+
return "xdg-open";
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
static get processing() {
|
|
506
|
+
return {
|
|
507
|
+
async processEvent(context) {
|
|
508
|
+
const { project, isShellMode, shellCommands } = context;
|
|
509
|
+
const homepage = project.homepage;
|
|
510
|
+
if (!homepage) {
|
|
511
|
+
console.warn("No homepage configured for project");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const openCmd = _WebEvent.getOpenCommand();
|
|
515
|
+
if (isShellMode) {
|
|
516
|
+
shellCommands.push(`${openCmd} "${homepage}" &`);
|
|
517
|
+
} else {
|
|
518
|
+
spawn3(openCmd, [homepage], {
|
|
519
|
+
detached: true,
|
|
520
|
+
stdio: "ignore"
|
|
521
|
+
}).unref();
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
generateShellCommand(context) {
|
|
525
|
+
const homepage = context.project.homepage;
|
|
526
|
+
if (!homepage) return [];
|
|
527
|
+
const openCmd = _WebEvent.getOpenCommand();
|
|
528
|
+
return [`${openCmd} "${homepage}" &`];
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
static get tmux() {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
static get help() {
|
|
536
|
+
return {
|
|
537
|
+
usage: "web: true | false",
|
|
538
|
+
description: "Open the project homepage in the default browser",
|
|
539
|
+
examples: [
|
|
540
|
+
{ config: true, description: "Enable browser opening" },
|
|
541
|
+
{ config: false, description: "Disable browser opening" }
|
|
542
|
+
]
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// src/events/extensions/claude.ts
|
|
550
|
+
import { spawn as spawn4 } from "child_process";
|
|
551
|
+
import { input, confirm } from "@inquirer/prompts";
|
|
552
|
+
var ClaudeEvent;
|
|
553
|
+
var init_claude = __esm({
|
|
554
|
+
"src/events/extensions/claude.ts"() {
|
|
555
|
+
"use strict";
|
|
556
|
+
ClaudeEvent = class _ClaudeEvent {
|
|
557
|
+
static get metadata() {
|
|
558
|
+
return {
|
|
559
|
+
name: "claude",
|
|
560
|
+
displayName: "Launch Claude Code",
|
|
561
|
+
description: "Launch Claude Code with optional flags and configuration",
|
|
562
|
+
category: "development",
|
|
563
|
+
requiresTmux: true,
|
|
564
|
+
dependencies: ["claude"]
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
static get validation() {
|
|
568
|
+
return {
|
|
569
|
+
validateConfig(config) {
|
|
570
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
if (typeof config === "object" && config !== null) {
|
|
574
|
+
const cfg = config;
|
|
575
|
+
if (cfg.flags !== void 0) {
|
|
576
|
+
if (!Array.isArray(cfg.flags)) {
|
|
577
|
+
return "claude.flags must be an array of strings";
|
|
578
|
+
}
|
|
579
|
+
for (const flag of cfg.flags) {
|
|
580
|
+
if (typeof flag !== "string") {
|
|
581
|
+
return "claude.flags must contain only strings";
|
|
582
|
+
}
|
|
583
|
+
if (!flag.startsWith("-")) {
|
|
584
|
+
return `Invalid flag "${flag}": flags must start with - or --`;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (cfg.split_terminal !== void 0 && typeof cfg.split_terminal !== "boolean") {
|
|
589
|
+
return "claude.split_terminal must be a boolean";
|
|
590
|
+
}
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
return "claude config must be a boolean or object with flags/split_terminal";
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
static get configuration() {
|
|
598
|
+
return {
|
|
599
|
+
async configureInteractive() {
|
|
600
|
+
const useAdvanced = await confirm({
|
|
601
|
+
message: "Configure advanced Claude options?",
|
|
602
|
+
default: false
|
|
603
|
+
});
|
|
604
|
+
if (!useAdvanced) {
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
const flagsInput = await input({
|
|
608
|
+
message: "Enter Claude flags (comma-separated, e.g., --resume, --debug):",
|
|
609
|
+
default: ""
|
|
610
|
+
});
|
|
611
|
+
const flags = flagsInput.split(",").map((f) => f.trim()).filter((f) => f.length > 0 && f.startsWith("-"));
|
|
612
|
+
const splitTerminal = await confirm({
|
|
613
|
+
message: "Use split terminal layout (Claude + shell)?",
|
|
614
|
+
default: true
|
|
615
|
+
});
|
|
616
|
+
if (flags.length === 0 && !splitTerminal) {
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
const config = {};
|
|
620
|
+
if (flags.length > 0) config.flags = flags;
|
|
621
|
+
if (splitTerminal) config.split_terminal = splitTerminal;
|
|
622
|
+
return config;
|
|
623
|
+
},
|
|
624
|
+
getDefaultConfig() {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
static getClaudeCommand(config) {
|
|
630
|
+
if (typeof config === "boolean" || config === void 0) {
|
|
631
|
+
return "claude";
|
|
632
|
+
}
|
|
633
|
+
const flags = config.flags || [];
|
|
634
|
+
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
635
|
+
}
|
|
636
|
+
static get processing() {
|
|
637
|
+
return {
|
|
638
|
+
async processEvent(context) {
|
|
639
|
+
const { project, isShellMode, shellCommands } = context;
|
|
640
|
+
const claudeConfig = project.events.claude;
|
|
641
|
+
const claudeCommand = _ClaudeEvent.getClaudeCommand(claudeConfig);
|
|
642
|
+
if (isShellMode) {
|
|
643
|
+
shellCommands.push(claudeCommand);
|
|
644
|
+
} else {
|
|
645
|
+
const args = claudeCommand.split(" ").slice(1);
|
|
646
|
+
spawn4("claude", args, {
|
|
647
|
+
cwd: project.path.path,
|
|
648
|
+
stdio: "inherit"
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
generateShellCommand(context) {
|
|
653
|
+
const claudeConfig = context.project.events.claude;
|
|
654
|
+
return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
static get tmux() {
|
|
659
|
+
return {
|
|
660
|
+
getLayoutPriority() {
|
|
661
|
+
return 100;
|
|
662
|
+
},
|
|
663
|
+
contributeToLayout(enabledCommands) {
|
|
664
|
+
if (enabledCommands.includes("npm")) {
|
|
665
|
+
return "three-pane";
|
|
666
|
+
}
|
|
667
|
+
return "split";
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
static get help() {
|
|
672
|
+
return {
|
|
673
|
+
usage: "claude: true | { flags: string[], split_terminal: boolean }",
|
|
674
|
+
description: "Launch Claude Code in the project directory",
|
|
675
|
+
examples: [
|
|
676
|
+
{ config: true, description: "Launch Claude with defaults" },
|
|
677
|
+
{ config: { flags: ["--resume"] }, description: "Resume previous session" },
|
|
678
|
+
{
|
|
679
|
+
config: { flags: ["--model", "opus"], split_terminal: true },
|
|
680
|
+
description: "Use Opus model with split terminal"
|
|
681
|
+
}
|
|
682
|
+
]
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// src/events/extensions/docker.ts
|
|
690
|
+
import { spawn as spawn5 } from "child_process";
|
|
691
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
692
|
+
var DockerEvent;
|
|
693
|
+
var init_docker = __esm({
|
|
694
|
+
"src/events/extensions/docker.ts"() {
|
|
695
|
+
"use strict";
|
|
696
|
+
DockerEvent = class _DockerEvent {
|
|
697
|
+
static get metadata() {
|
|
698
|
+
return {
|
|
699
|
+
name: "docker",
|
|
700
|
+
displayName: "Docker container management",
|
|
701
|
+
description: "Start/stop Docker containers for the project",
|
|
702
|
+
category: "development",
|
|
703
|
+
requiresTmux: false,
|
|
704
|
+
dependencies: ["docker"]
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
static get validation() {
|
|
708
|
+
return {
|
|
709
|
+
validateConfig(config) {
|
|
710
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
if (typeof config === "string") {
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
if (typeof config === "object" && config !== null) {
|
|
717
|
+
const cfg = config;
|
|
718
|
+
if (cfg.compose_file !== void 0 && typeof cfg.compose_file !== "string") {
|
|
719
|
+
return "docker.compose_file must be a string";
|
|
720
|
+
}
|
|
721
|
+
if (cfg.services !== void 0) {
|
|
722
|
+
if (!Array.isArray(cfg.services)) {
|
|
723
|
+
return "docker.services must be an array";
|
|
724
|
+
}
|
|
725
|
+
for (const service of cfg.services) {
|
|
726
|
+
if (typeof service !== "string") {
|
|
727
|
+
return "docker.services must contain only strings";
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
return "docker config must be a boolean, string (compose file), or object";
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
static get configuration() {
|
|
738
|
+
return {
|
|
739
|
+
async configureInteractive() {
|
|
740
|
+
const composeFile = await input2({
|
|
741
|
+
message: "Enter docker-compose file path:",
|
|
742
|
+
default: "docker-compose.yml"
|
|
743
|
+
});
|
|
744
|
+
const servicesInput = await input2({
|
|
745
|
+
message: "Enter services to start (comma-separated, leave empty for all):",
|
|
746
|
+
default: ""
|
|
747
|
+
});
|
|
748
|
+
const services = servicesInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
749
|
+
if (composeFile === "docker-compose.yml" && services.length === 0) {
|
|
750
|
+
return { compose_file: "docker-compose.yml" };
|
|
751
|
+
}
|
|
752
|
+
if (services.length === 0) {
|
|
753
|
+
return composeFile;
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
compose_file: composeFile,
|
|
757
|
+
services
|
|
758
|
+
};
|
|
759
|
+
},
|
|
760
|
+
getDefaultConfig() {
|
|
761
|
+
return { compose_file: "docker-compose.yml" };
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
static getDockerCommand(config) {
|
|
766
|
+
if (typeof config === "boolean" || config === void 0) {
|
|
767
|
+
return "docker-compose up -d";
|
|
768
|
+
}
|
|
769
|
+
if (typeof config === "string") {
|
|
770
|
+
return `docker-compose -f ${config} up -d`;
|
|
771
|
+
}
|
|
772
|
+
const composeFile = config.compose_file || "docker-compose.yml";
|
|
773
|
+
const services = config.services?.join(" ") || "";
|
|
774
|
+
return `docker-compose -f ${composeFile} up -d ${services}`.trim();
|
|
775
|
+
}
|
|
776
|
+
static get processing() {
|
|
777
|
+
return {
|
|
778
|
+
async processEvent(context) {
|
|
779
|
+
const { project, isShellMode, shellCommands } = context;
|
|
780
|
+
const dockerConfig = project.events.docker;
|
|
781
|
+
const dockerCommand = _DockerEvent.getDockerCommand(
|
|
782
|
+
dockerConfig
|
|
783
|
+
);
|
|
784
|
+
if (isShellMode) {
|
|
785
|
+
shellCommands.push(dockerCommand);
|
|
786
|
+
} else {
|
|
787
|
+
const [cmd, ...args] = dockerCommand.split(" ");
|
|
788
|
+
spawn5(cmd, args, {
|
|
789
|
+
cwd: project.path.path,
|
|
790
|
+
stdio: "inherit"
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
generateShellCommand(context) {
|
|
795
|
+
const dockerConfig = context.project.events.docker;
|
|
796
|
+
return [_DockerEvent.getDockerCommand(dockerConfig)];
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
static get tmux() {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
static get help() {
|
|
804
|
+
return {
|
|
805
|
+
usage: 'docker: true | "compose-file.yml" | { compose_file: string, services?: string[] }',
|
|
806
|
+
description: "Start Docker containers for the project",
|
|
807
|
+
examples: [
|
|
808
|
+
{ config: true, description: "Use default docker-compose.yml" },
|
|
809
|
+
{ config: "docker-compose.dev.yml", description: "Use custom compose file" },
|
|
810
|
+
{
|
|
811
|
+
config: { compose_file: "docker-compose.yml", services: ["web", "db"] },
|
|
812
|
+
description: "Start specific services"
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
// src/events/extensions/npm.ts
|
|
822
|
+
var npm_exports = {};
|
|
823
|
+
__export(npm_exports, {
|
|
824
|
+
NpmEvent: () => NpmEvent,
|
|
825
|
+
default: () => npm_default
|
|
826
|
+
});
|
|
827
|
+
import { spawn as spawn6 } from "child_process";
|
|
828
|
+
import { input as input3, confirm as confirm2 } from "@inquirer/prompts";
|
|
829
|
+
var NpmEvent, npm_default;
|
|
830
|
+
var init_npm = __esm({
|
|
831
|
+
"src/events/extensions/npm.ts"() {
|
|
832
|
+
"use strict";
|
|
833
|
+
NpmEvent = class _NpmEvent {
|
|
834
|
+
static get metadata() {
|
|
835
|
+
return {
|
|
836
|
+
name: "npm",
|
|
837
|
+
displayName: "Run NPM command",
|
|
838
|
+
description: "Execute NPM scripts in project directory",
|
|
839
|
+
category: "development",
|
|
840
|
+
requiresTmux: true,
|
|
841
|
+
dependencies: ["npm"]
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
static get validation() {
|
|
845
|
+
return {
|
|
846
|
+
validateConfig(config) {
|
|
847
|
+
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
if (typeof config === "string") {
|
|
851
|
+
if (config.trim().length === 0) {
|
|
852
|
+
return "npm script name cannot be empty";
|
|
853
|
+
}
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
if (typeof config === "object" && config !== null) {
|
|
857
|
+
const cfg = config;
|
|
858
|
+
if (typeof cfg.command !== "string" || cfg.command.trim().length === 0) {
|
|
859
|
+
return "npm.command must be a non-empty string";
|
|
860
|
+
}
|
|
861
|
+
if (cfg.watch !== void 0 && typeof cfg.watch !== "boolean") {
|
|
862
|
+
return "npm.watch must be a boolean";
|
|
863
|
+
}
|
|
864
|
+
if (cfg.auto_restart !== void 0 && typeof cfg.auto_restart !== "boolean") {
|
|
865
|
+
return "npm.auto_restart must be a boolean";
|
|
866
|
+
}
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
return "npm config must be a boolean, string (script name), or object";
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
static get configuration() {
|
|
874
|
+
return {
|
|
875
|
+
async configureInteractive() {
|
|
876
|
+
const scriptName = await input3({
|
|
877
|
+
message: "Enter NPM script to run:",
|
|
878
|
+
default: "dev"
|
|
879
|
+
});
|
|
880
|
+
const useAdvanced = await confirm2({
|
|
881
|
+
message: "Configure advanced NPM options?",
|
|
882
|
+
default: false
|
|
883
|
+
});
|
|
884
|
+
if (!useAdvanced) {
|
|
885
|
+
return scriptName;
|
|
886
|
+
}
|
|
887
|
+
const watch = await confirm2({
|
|
888
|
+
message: "Enable watch mode?",
|
|
889
|
+
default: false
|
|
890
|
+
});
|
|
891
|
+
const autoRestart = await confirm2({
|
|
892
|
+
message: "Auto-restart on crash?",
|
|
893
|
+
default: false
|
|
894
|
+
});
|
|
895
|
+
if (!watch && !autoRestart) {
|
|
896
|
+
return scriptName;
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
command: scriptName,
|
|
900
|
+
watch,
|
|
901
|
+
auto_restart: autoRestart
|
|
902
|
+
};
|
|
903
|
+
},
|
|
904
|
+
getDefaultConfig() {
|
|
905
|
+
return "dev";
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
static getNpmCommand(config) {
|
|
910
|
+
if (typeof config === "boolean" || config === void 0) {
|
|
911
|
+
return "npm run dev";
|
|
912
|
+
}
|
|
913
|
+
if (typeof config === "string") {
|
|
914
|
+
return `npm run ${config}`;
|
|
915
|
+
}
|
|
916
|
+
return `npm run ${config.command}`;
|
|
917
|
+
}
|
|
918
|
+
static get processing() {
|
|
919
|
+
return {
|
|
920
|
+
async processEvent(context) {
|
|
921
|
+
const { project, isShellMode, shellCommands } = context;
|
|
922
|
+
const npmConfig = project.events.npm;
|
|
923
|
+
const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
|
|
924
|
+
if (isShellMode) {
|
|
925
|
+
shellCommands.push(npmCommand);
|
|
926
|
+
} else {
|
|
927
|
+
const [cmd, ...args] = npmCommand.split(" ");
|
|
928
|
+
spawn6(cmd, args, {
|
|
929
|
+
cwd: project.path.path,
|
|
930
|
+
stdio: "inherit"
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
generateShellCommand(context) {
|
|
935
|
+
const npmConfig = context.project.events.npm;
|
|
936
|
+
return [_NpmEvent.getNpmCommand(npmConfig)];
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
static get tmux() {
|
|
941
|
+
return {
|
|
942
|
+
getLayoutPriority() {
|
|
943
|
+
return 50;
|
|
944
|
+
},
|
|
945
|
+
contributeToLayout(enabledCommands) {
|
|
946
|
+
if (enabledCommands.includes("claude")) {
|
|
947
|
+
return "three-pane";
|
|
948
|
+
}
|
|
949
|
+
return "two-pane-npm";
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
static get help() {
|
|
954
|
+
return {
|
|
955
|
+
usage: 'npm: true | "script" | { command: string, watch?: boolean, auto_restart?: boolean }',
|
|
956
|
+
description: "Run an NPM script in the project directory",
|
|
957
|
+
examples: [
|
|
958
|
+
{ config: true, description: "Run npm run dev" },
|
|
959
|
+
{ config: "test", description: "Run npm run test" },
|
|
960
|
+
{ config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
|
|
961
|
+
]
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
npm_default = NpmEvent;
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
// src/events/registry.ts
|
|
970
|
+
var ALL_EVENTS, EventRegistryClass, EventRegistry;
|
|
971
|
+
var init_registry = __esm({
|
|
972
|
+
"src/events/registry.ts"() {
|
|
973
|
+
"use strict";
|
|
974
|
+
init_cwd();
|
|
975
|
+
init_ide();
|
|
976
|
+
init_web();
|
|
977
|
+
init_claude();
|
|
978
|
+
init_docker();
|
|
979
|
+
init_npm();
|
|
980
|
+
ALL_EVENTS = [CwdEvent, IdeEvent, WebEvent, ClaudeEvent, DockerEvent, NpmEvent];
|
|
981
|
+
EventRegistryClass = class {
|
|
982
|
+
_events = /* @__PURE__ */ new Map();
|
|
983
|
+
_initialized = false;
|
|
984
|
+
/**
|
|
985
|
+
* Initialize the registry by registering all events
|
|
986
|
+
*/
|
|
987
|
+
async initialize() {
|
|
988
|
+
if (this._initialized) return;
|
|
989
|
+
this.registerEvents();
|
|
990
|
+
this._initialized = true;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Register all event classes
|
|
994
|
+
*/
|
|
995
|
+
registerEvents() {
|
|
996
|
+
for (const EventClass of ALL_EVENTS) {
|
|
997
|
+
if (this.isValidEventClass(EventClass)) {
|
|
998
|
+
this._events.set(EventClass.metadata.name, EventClass);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Type guard to check if an object is a valid EventHandlerClass
|
|
1004
|
+
*/
|
|
1005
|
+
isValidEventClass(obj) {
|
|
1006
|
+
if (typeof obj !== "function" && typeof obj !== "object") return false;
|
|
1007
|
+
if (obj === null) return false;
|
|
1008
|
+
const candidate = obj;
|
|
1009
|
+
return candidate.metadata !== void 0 && typeof candidate.metadata.name === "string" && typeof candidate.metadata.displayName === "string" && candidate.validation !== void 0 && typeof candidate.validation.validateConfig === "function" && candidate.configuration !== void 0 && typeof candidate.configuration.configureInteractive === "function" && candidate.processing !== void 0 && typeof candidate.processing.processEvent === "function";
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Get all valid event names from registered events
|
|
1013
|
+
*/
|
|
1014
|
+
getValidEventNames() {
|
|
1015
|
+
this.ensureInitialized();
|
|
1016
|
+
return Array.from(this._events.keys());
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Get event by name
|
|
1020
|
+
*/
|
|
1021
|
+
getEventByName(name) {
|
|
1022
|
+
this.ensureInitialized();
|
|
1023
|
+
return this._events.get(name) ?? null;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Get all events for management UI
|
|
1027
|
+
*/
|
|
1028
|
+
getEventsForManageUI() {
|
|
1029
|
+
this.ensureInitialized();
|
|
1030
|
+
const events = [];
|
|
1031
|
+
for (const [name, eventClass] of this._events) {
|
|
1032
|
+
events.push({
|
|
1033
|
+
name: eventClass.metadata.displayName,
|
|
1034
|
+
value: name,
|
|
1035
|
+
description: eventClass.metadata.description
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
return events.sort((a, b) => a.name.localeCompare(b.name));
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Get events that support tmux integration
|
|
1042
|
+
*/
|
|
1043
|
+
getTmuxEnabledEvents() {
|
|
1044
|
+
this.ensureInitialized();
|
|
1045
|
+
const tmuxEvents = [];
|
|
1046
|
+
for (const [name, eventClass] of this._events) {
|
|
1047
|
+
const tmux = eventClass.tmux;
|
|
1048
|
+
if (tmux) {
|
|
1049
|
+
tmuxEvents.push({
|
|
1050
|
+
name,
|
|
1051
|
+
event: eventClass,
|
|
1052
|
+
priority: tmux.getLayoutPriority()
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return tmuxEvents.sort((a, b) => b.priority - a.priority);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Get all available events with their metadata
|
|
1060
|
+
*/
|
|
1061
|
+
getAllEvents() {
|
|
1062
|
+
this.ensureInitialized();
|
|
1063
|
+
const events = [];
|
|
1064
|
+
for (const [name, eventClass] of this._events) {
|
|
1065
|
+
events.push({
|
|
1066
|
+
name,
|
|
1067
|
+
metadata: eventClass.metadata,
|
|
1068
|
+
hasValidation: !!eventClass.validation,
|
|
1069
|
+
hasConfiguration: !!eventClass.configuration,
|
|
1070
|
+
hasProcessing: !!eventClass.processing,
|
|
1071
|
+
hasTmux: !!eventClass.tmux,
|
|
1072
|
+
hasHelp: !!eventClass.help
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
return events;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Ensure registry is initialized
|
|
1079
|
+
*/
|
|
1080
|
+
ensureInitialized() {
|
|
1081
|
+
if (!this._initialized) {
|
|
1082
|
+
throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Clear the registry (useful for testing)
|
|
1087
|
+
*/
|
|
1088
|
+
clear() {
|
|
1089
|
+
this._events.clear();
|
|
1090
|
+
this._initialized = false;
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
EventRegistry = new EventRegistryClass();
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// src/types/constants.ts
|
|
1098
|
+
var IDE_CHOICES;
|
|
1099
|
+
var init_constants = __esm({
|
|
1100
|
+
"src/types/constants.ts"() {
|
|
1101
|
+
"use strict";
|
|
1102
|
+
IDE_CHOICES = [
|
|
1103
|
+
{ name: "Visual Studio Code", value: "vscode" },
|
|
1104
|
+
{ name: "Visual Studio Code (code)", value: "code" },
|
|
1105
|
+
{ name: "IntelliJ IDEA", value: "idea" },
|
|
1106
|
+
{ name: "Atom", value: "atom" },
|
|
1107
|
+
{ name: "Sublime Text", value: "subl" },
|
|
1108
|
+
{ name: "Vim", value: "vim" },
|
|
1109
|
+
{ name: "Emacs", value: "emacs" }
|
|
1110
|
+
];
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
// src/commands/interactive.ts
|
|
1115
|
+
var interactive_exports = {};
|
|
1116
|
+
__export(interactive_exports, {
|
|
1117
|
+
runInteractive: () => runInteractive
|
|
1118
|
+
});
|
|
1119
|
+
import { select, input as input4, checkbox, confirm as confirm3 } from "@inquirer/prompts";
|
|
1120
|
+
import File3 from "phylo";
|
|
1121
|
+
import deepAssign2 from "deep-assign";
|
|
1122
|
+
async function runInteractive(ctx) {
|
|
1123
|
+
const { config, log, environment, suggestedName } = ctx;
|
|
1124
|
+
showLogo(config);
|
|
1125
|
+
log.log("");
|
|
1126
|
+
const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name : File3.cwd().name);
|
|
1127
|
+
const fromUser = !!suggestedName;
|
|
1128
|
+
await startInteractive(defaultName, fromUser, ctx);
|
|
1129
|
+
}
|
|
1130
|
+
function showLogo(config) {
|
|
1131
|
+
const version = config.get("pkg")?.version ?? "unknown";
|
|
1132
|
+
console.log(
|
|
1133
|
+
` 8\x1B[2m${" ".repeat(Math.max(15 - version.length - 1, 1)) + "v" + version}\x1B[22m
|
|
1134
|
+
Yb db dP .d8b. 8d8b 8.dP \x1B[92m.d8b. 8d8b.\x1B[0m
|
|
1135
|
+
YbdPYbdP 8' .8 8P 88b \x1B[92m8' .8 8P Y8\x1B[0m
|
|
1136
|
+
YP YP \`Y8P' 8 8 Yb \x1B[92m\`Y8P' 8 8\x1B[0m`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
|
|
1140
|
+
const { log, environment } = ctx;
|
|
1141
|
+
log.debug(`Name '${defaultName}' was${fromUser ? "" : " not"} provided by the user`);
|
|
1142
|
+
const question = getFirstQuestion(defaultName, fromUser, environment, showMain);
|
|
1143
|
+
const action = await select(question);
|
|
1144
|
+
switch (action) {
|
|
1145
|
+
case "exit":
|
|
1146
|
+
return;
|
|
1147
|
+
case "more":
|
|
1148
|
+
await startInteractive(defaultName, fromUser, ctx, true);
|
|
195
1149
|
return;
|
|
196
1150
|
case "init-project":
|
|
197
1151
|
await initProject(defaultName, fromUser, ctx);
|
|
@@ -200,17 +1154,17 @@ async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
|
|
|
200
1154
|
await initBranch(defaultName, ctx);
|
|
201
1155
|
return;
|
|
202
1156
|
case "switch-project":
|
|
203
|
-
|
|
204
|
-
|
|
1157
|
+
await switchProject(ctx);
|
|
1158
|
+
return;
|
|
205
1159
|
case "switch-branch":
|
|
206
|
-
|
|
207
|
-
|
|
1160
|
+
await switchBranch(defaultName, ctx);
|
|
1161
|
+
return;
|
|
208
1162
|
case "manage-projects":
|
|
209
|
-
|
|
210
|
-
|
|
1163
|
+
await manageProjects(ctx);
|
|
1164
|
+
return;
|
|
211
1165
|
case "manage-branches":
|
|
212
|
-
|
|
213
|
-
|
|
1166
|
+
await manageBranches(defaultName, ctx);
|
|
1167
|
+
return;
|
|
214
1168
|
}
|
|
215
1169
|
}
|
|
216
1170
|
function getFirstQuestion(defaultName, fromUser, environment, showMain) {
|
|
@@ -279,364 +1233,537 @@ async function initProject(defaultName, fromUser, ctx) {
|
|
|
279
1233
|
answerFile = defaultBase.join(answerFile.path);
|
|
280
1234
|
}
|
|
281
1235
|
try {
|
|
282
|
-
const canonical = answerFile.canonicalize();
|
|
283
|
-
if (canonical) {
|
|
284
|
-
answerFile = canonical;
|
|
285
|
-
} else {
|
|
286
|
-
answerFile = answerFile.absolutify();
|
|
1236
|
+
const canonical = answerFile.canonicalize();
|
|
1237
|
+
if (canonical) {
|
|
1238
|
+
answerFile = canonical;
|
|
1239
|
+
} else {
|
|
1240
|
+
answerFile = answerFile.absolutify();
|
|
1241
|
+
}
|
|
1242
|
+
} catch {
|
|
1243
|
+
answerFile = answerFile.absolutify();
|
|
1244
|
+
}
|
|
1245
|
+
basePath = answerFile.relativize(defaultBase.path).path;
|
|
1246
|
+
}
|
|
1247
|
+
const ide = await select({
|
|
1248
|
+
message: "What is the IDE?",
|
|
1249
|
+
choices: IDE_CHOICES
|
|
1250
|
+
});
|
|
1251
|
+
const selectedEvents = await checkbox({
|
|
1252
|
+
message: "Which events should take place when opening?",
|
|
1253
|
+
choices: [
|
|
1254
|
+
{ name: "Change terminal cwd to project path", value: "cwd", checked: true },
|
|
1255
|
+
{ name: "Open project in IDE", value: "ide", checked: true }
|
|
1256
|
+
]
|
|
1257
|
+
});
|
|
1258
|
+
const events = {
|
|
1259
|
+
cwd: selectedEvents.includes("cwd"),
|
|
1260
|
+
ide: selectedEvents.includes("ide")
|
|
1261
|
+
};
|
|
1262
|
+
const projectConfig = {
|
|
1263
|
+
path: basePath,
|
|
1264
|
+
ide,
|
|
1265
|
+
events
|
|
1266
|
+
};
|
|
1267
|
+
projects[name] = projectConfig;
|
|
1268
|
+
config.set("projects", projects);
|
|
1269
|
+
log.info("Your project has been initialized.");
|
|
1270
|
+
log.info(`Use 'workon ${name}' to start working!`);
|
|
1271
|
+
}
|
|
1272
|
+
async function initBranch(defaultName, ctx) {
|
|
1273
|
+
const { config, log } = ctx;
|
|
1274
|
+
const projects = config.getProjects();
|
|
1275
|
+
const branch = await input4({
|
|
1276
|
+
message: "What is the name of the branch?",
|
|
1277
|
+
validate: (value) => {
|
|
1278
|
+
if (/\w+#\w+/.test(value)) return `Branch name can't contain the "#" sign`;
|
|
1279
|
+
if (`${defaultName}#${value}` in projects) return "Branch already exists.";
|
|
1280
|
+
return true;
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
const branchName = `${defaultName}#${branch}`;
|
|
1284
|
+
const baseProject = projects[defaultName];
|
|
1285
|
+
const { name: _excludedName, ...mergedConfig } = deepAssign2({}, baseProject, {
|
|
1286
|
+
branch
|
|
1287
|
+
});
|
|
1288
|
+
const branchConfig = mergedConfig;
|
|
1289
|
+
projects[branchName] = branchConfig;
|
|
1290
|
+
config.set("projects", projects);
|
|
1291
|
+
log.info("Your branch configuration has been initialized.");
|
|
1292
|
+
log.info(`Use 'workon ${branchName}' to start working!`);
|
|
1293
|
+
}
|
|
1294
|
+
async function switchProject(ctx) {
|
|
1295
|
+
const { config, log } = ctx;
|
|
1296
|
+
const projects = config.getProjects();
|
|
1297
|
+
const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
|
|
1298
|
+
if (baseProjects.length === 0) {
|
|
1299
|
+
log.info('No projects configured yet. Use "Start a new project" to create one.');
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const projectName = await select({
|
|
1303
|
+
message: "Select a project to open:",
|
|
1304
|
+
choices: baseProjects.map((name) => ({
|
|
1305
|
+
name: `${name} (${projects[name].path})`,
|
|
1306
|
+
value: name
|
|
1307
|
+
}))
|
|
1308
|
+
});
|
|
1309
|
+
await openProject(projectName, ctx);
|
|
1310
|
+
}
|
|
1311
|
+
async function switchBranch(projectName, ctx) {
|
|
1312
|
+
const { config, log } = ctx;
|
|
1313
|
+
const projects = config.getProjects();
|
|
1314
|
+
const branchPrefix = `${projectName}#`;
|
|
1315
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1316
|
+
if (branches.length === 0) {
|
|
1317
|
+
log.info(`No branch configurations found for '${projectName}'.`);
|
|
1318
|
+
log.info('Use "Start a branch" to create one.');
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const branchConfig = await select({
|
|
1322
|
+
message: "Select a branch configuration:",
|
|
1323
|
+
choices: branches.map((name) => ({
|
|
1324
|
+
name: name.substring(branchPrefix.length),
|
|
1325
|
+
value: name
|
|
1326
|
+
}))
|
|
1327
|
+
});
|
|
1328
|
+
await openProject(branchConfig, ctx);
|
|
1329
|
+
}
|
|
1330
|
+
async function manageProjects(ctx) {
|
|
1331
|
+
const { config } = ctx;
|
|
1332
|
+
const projects = config.getProjects();
|
|
1333
|
+
const hasProjects = Object.keys(projects).length > 0;
|
|
1334
|
+
const choices = [
|
|
1335
|
+
{ name: "Create new project", value: "create" },
|
|
1336
|
+
...hasProjects ? [
|
|
1337
|
+
{ name: "Edit project", value: "edit" },
|
|
1338
|
+
{ name: "Delete project", value: "delete" },
|
|
1339
|
+
{ name: "List projects", value: "list" }
|
|
1340
|
+
] : [],
|
|
1341
|
+
{ name: "Back", value: "back" }
|
|
1342
|
+
];
|
|
1343
|
+
const action = await select({
|
|
1344
|
+
message: "Manage projects:",
|
|
1345
|
+
choices
|
|
1346
|
+
});
|
|
1347
|
+
switch (action) {
|
|
1348
|
+
case "create":
|
|
1349
|
+
await createProjectManage(ctx);
|
|
1350
|
+
break;
|
|
1351
|
+
case "edit":
|
|
1352
|
+
await editProjectManage(ctx);
|
|
1353
|
+
break;
|
|
1354
|
+
case "delete":
|
|
1355
|
+
await deleteProjectManage(ctx);
|
|
1356
|
+
break;
|
|
1357
|
+
case "list":
|
|
1358
|
+
listProjectsManage(ctx);
|
|
1359
|
+
break;
|
|
1360
|
+
case "back":
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
await manageProjects(ctx);
|
|
1364
|
+
}
|
|
1365
|
+
async function manageBranches(projectName, ctx) {
|
|
1366
|
+
const { config } = ctx;
|
|
1367
|
+
const projects = config.getProjects();
|
|
1368
|
+
const branchPrefix = `${projectName}#`;
|
|
1369
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1370
|
+
const choices = [
|
|
1371
|
+
{ name: "Create new branch config", value: "create" },
|
|
1372
|
+
...branches.length > 0 ? [
|
|
1373
|
+
{ name: "Edit branch config", value: "edit" },
|
|
1374
|
+
{ name: "Delete branch config", value: "delete" },
|
|
1375
|
+
{ name: "List branch configs", value: "list" }
|
|
1376
|
+
] : [],
|
|
1377
|
+
{ name: "Back", value: "back" }
|
|
1378
|
+
];
|
|
1379
|
+
const action = await select({
|
|
1380
|
+
message: `Manage branches for '${projectName}':`,
|
|
1381
|
+
choices
|
|
1382
|
+
});
|
|
1383
|
+
switch (action) {
|
|
1384
|
+
case "create":
|
|
1385
|
+
await initBranch(projectName, ctx);
|
|
1386
|
+
break;
|
|
1387
|
+
case "edit":
|
|
1388
|
+
await editBranchManage(projectName, ctx);
|
|
1389
|
+
break;
|
|
1390
|
+
case "delete":
|
|
1391
|
+
await deleteBranchManage(projectName, ctx);
|
|
1392
|
+
break;
|
|
1393
|
+
case "list":
|
|
1394
|
+
listBranchesManage(projectName, ctx);
|
|
1395
|
+
break;
|
|
1396
|
+
case "back":
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
await manageBranches(projectName, ctx);
|
|
1400
|
+
}
|
|
1401
|
+
async function openProject(projectName, ctx) {
|
|
1402
|
+
const { config, log } = ctx;
|
|
1403
|
+
const projects = config.getProjects();
|
|
1404
|
+
if (!(projectName in projects)) {
|
|
1405
|
+
log.error(`Project '${projectName}' not found.`);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const projectConfig = projects[projectName];
|
|
1409
|
+
const projectCfg = { ...projectConfig, name: projectName };
|
|
1410
|
+
const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
|
|
1411
|
+
log.info(`Opening project '${projectName}'...`);
|
|
1412
|
+
const events = Object.keys(projectConfig.events).filter(
|
|
1413
|
+
(e) => projectConfig.events[e]
|
|
1414
|
+
);
|
|
1415
|
+
for (const event of events) {
|
|
1416
|
+
const eventHandler = EventRegistry.getEventByName(event);
|
|
1417
|
+
if (eventHandler) {
|
|
1418
|
+
await eventHandler.processing.processEvent({
|
|
1419
|
+
project: projectEnv.project,
|
|
1420
|
+
isShellMode: false,
|
|
1421
|
+
shellCommands: []
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
async function createProjectManage(ctx) {
|
|
1427
|
+
const { config, log } = ctx;
|
|
1428
|
+
const defaults = config.getDefaults();
|
|
1429
|
+
const projects = config.getProjects();
|
|
1430
|
+
const name = await input4({
|
|
1431
|
+
message: "Project name:",
|
|
1432
|
+
validate: (value) => {
|
|
1433
|
+
if (!value.trim()) return "Name is required";
|
|
1434
|
+
if (!/^[\w-]+$/.test(value))
|
|
1435
|
+
return "Name can only contain letters, numbers, underscores, and hyphens";
|
|
1436
|
+
if (value in projects) return "Project already exists";
|
|
1437
|
+
return true;
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
const defaultPath = defaults?.base ? File3.from(defaults.base).join(name).path : name;
|
|
1441
|
+
const pathInput = await input4({
|
|
1442
|
+
message: "Project path:",
|
|
1443
|
+
default: defaultPath
|
|
1444
|
+
});
|
|
1445
|
+
let relativePath = pathInput;
|
|
1446
|
+
if (defaults?.base) {
|
|
1447
|
+
const baseDir = File3.from(defaults.base);
|
|
1448
|
+
const pathFile = File3.from(pathInput);
|
|
1449
|
+
try {
|
|
1450
|
+
if (pathFile.isAbsolute()) {
|
|
1451
|
+
relativePath = pathFile.relativize(baseDir.path).path;
|
|
287
1452
|
}
|
|
288
1453
|
} catch {
|
|
289
|
-
|
|
1454
|
+
relativePath = pathInput;
|
|
290
1455
|
}
|
|
291
|
-
basePath = answerFile.relativize(defaultBase.path).path;
|
|
292
1456
|
}
|
|
293
1457
|
const ide = await select({
|
|
294
|
-
message: "
|
|
1458
|
+
message: "Select IDE:",
|
|
295
1459
|
choices: IDE_CHOICES
|
|
296
1460
|
});
|
|
1461
|
+
const availableEvents = EventRegistry.getEventsForManageUI();
|
|
297
1462
|
const selectedEvents = await checkbox({
|
|
298
|
-
message: "
|
|
299
|
-
choices:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
1463
|
+
message: "Select events to enable:",
|
|
1464
|
+
choices: availableEvents.map((e) => ({
|
|
1465
|
+
name: `${e.name} - ${e.description}`,
|
|
1466
|
+
value: e.value,
|
|
1467
|
+
checked: e.value === "cwd" || e.value === "ide"
|
|
1468
|
+
}))
|
|
303
1469
|
});
|
|
304
|
-
const events = {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1470
|
+
const events = {};
|
|
1471
|
+
for (const eventName of selectedEvents) {
|
|
1472
|
+
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1473
|
+
if (eventHandler) {
|
|
1474
|
+
const eventConfig = await eventHandler.configuration.configureInteractive();
|
|
1475
|
+
events[eventName] = eventConfig;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
308
1478
|
const projectConfig = {
|
|
309
|
-
path:
|
|
1479
|
+
path: relativePath,
|
|
310
1480
|
ide,
|
|
311
1481
|
events
|
|
312
1482
|
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1483
|
+
const confirmed = await confirm3({
|
|
1484
|
+
message: "Save this project?",
|
|
1485
|
+
default: true
|
|
1486
|
+
});
|
|
1487
|
+
if (confirmed) {
|
|
1488
|
+
config.setProject(name, projectConfig);
|
|
1489
|
+
log.info(`Project '${name}' created successfully!`);
|
|
1490
|
+
}
|
|
317
1491
|
}
|
|
318
|
-
async function
|
|
1492
|
+
async function editProjectManage(ctx) {
|
|
319
1493
|
const { config, log } = ctx;
|
|
320
1494
|
const projects = config.getProjects();
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
];
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// src/commands/index.ts
|
|
351
|
-
import { Command as Command8 } from "commander";
|
|
352
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
353
|
-
import { join, dirname } from "path";
|
|
354
|
-
import { fileURLToPath } from "url";
|
|
355
|
-
import loog from "loog";
|
|
356
|
-
import omelette from "omelette";
|
|
357
|
-
import File7 from "phylo";
|
|
358
|
-
|
|
359
|
-
// src/lib/config.ts
|
|
360
|
-
import Conf from "conf";
|
|
361
|
-
var TRANSIENT_PROPS = ["pkg", "work"];
|
|
362
|
-
var Config = class {
|
|
363
|
-
_transient = {};
|
|
364
|
-
_store;
|
|
365
|
-
constructor() {
|
|
366
|
-
this._store = new Conf({
|
|
367
|
-
projectName: "workon"
|
|
368
|
-
});
|
|
1495
|
+
const defaults = config.getDefaults();
|
|
1496
|
+
const baseProjects = Object.keys(projects).filter((name2) => !name2.includes("#"));
|
|
1497
|
+
if (baseProjects.length === 0) {
|
|
1498
|
+
log.info("No projects to edit.");
|
|
1499
|
+
return;
|
|
369
1500
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
1501
|
+
const name = await select({
|
|
1502
|
+
message: "Select project to edit:",
|
|
1503
|
+
choices: baseProjects.map((n) => ({ name: n, value: n }))
|
|
1504
|
+
});
|
|
1505
|
+
const project = projects[name];
|
|
1506
|
+
const pathInput = await input4({
|
|
1507
|
+
message: "Project path:",
|
|
1508
|
+
default: project.path
|
|
1509
|
+
});
|
|
1510
|
+
let relativePath = pathInput;
|
|
1511
|
+
if (defaults?.base) {
|
|
1512
|
+
const baseDir = File3.from(defaults.base);
|
|
1513
|
+
const pathFile = File3.from(pathInput);
|
|
1514
|
+
try {
|
|
1515
|
+
if (pathFile.isAbsolute()) {
|
|
1516
|
+
relativePath = pathFile.relativize(baseDir.path).path;
|
|
1517
|
+
}
|
|
1518
|
+
} catch {
|
|
1519
|
+
relativePath = pathInput;
|
|
374
1520
|
}
|
|
375
|
-
return this._store.get(key, defaultValue);
|
|
376
1521
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
1522
|
+
const ide = await select({
|
|
1523
|
+
message: "Select IDE:",
|
|
1524
|
+
choices: IDE_CHOICES,
|
|
1525
|
+
default: project.ide || "vscode"
|
|
1526
|
+
});
|
|
1527
|
+
const keepEvents = await confirm3({
|
|
1528
|
+
message: "Keep existing event configuration?",
|
|
1529
|
+
default: true
|
|
1530
|
+
});
|
|
1531
|
+
let events = project.events;
|
|
1532
|
+
if (!keepEvents) {
|
|
1533
|
+
const availableEvents = EventRegistry.getEventsForManageUI();
|
|
1534
|
+
const currentEvents = Object.keys(project.events);
|
|
1535
|
+
const selectedEvents = await checkbox({
|
|
1536
|
+
message: "Select events to enable:",
|
|
1537
|
+
choices: availableEvents.map((e) => ({
|
|
1538
|
+
name: `${e.name} - ${e.description}`,
|
|
1539
|
+
value: e.value,
|
|
1540
|
+
checked: currentEvents.includes(e.value)
|
|
1541
|
+
}))
|
|
1542
|
+
});
|
|
1543
|
+
events = {};
|
|
1544
|
+
for (const eventName of selectedEvents) {
|
|
1545
|
+
if (project.events[eventName]) {
|
|
1546
|
+
events[eventName] = project.events[eventName];
|
|
384
1547
|
} else {
|
|
385
|
-
|
|
1548
|
+
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1549
|
+
if (eventHandler) {
|
|
1550
|
+
const eventConfig = await eventHandler.configuration.configureInteractive();
|
|
1551
|
+
events[eventName] = eventConfig;
|
|
1552
|
+
}
|
|
386
1553
|
}
|
|
387
1554
|
}
|
|
388
1555
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
this._store.delete(key);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
getProjects() {
|
|
405
|
-
return this.get("projects") ?? {};
|
|
406
|
-
}
|
|
407
|
-
getProject(name) {
|
|
408
|
-
const projects = this.getProjects();
|
|
409
|
-
return projects[name];
|
|
410
|
-
}
|
|
411
|
-
setProject(name, config) {
|
|
412
|
-
const projects = this.getProjects();
|
|
413
|
-
projects[name] = config;
|
|
414
|
-
this.set("projects", projects);
|
|
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;
|
|
1556
|
+
const updatedConfig = {
|
|
1557
|
+
path: relativePath,
|
|
1558
|
+
ide,
|
|
1559
|
+
events
|
|
1560
|
+
};
|
|
1561
|
+
const confirmed = await confirm3({
|
|
1562
|
+
message: "Save changes?",
|
|
1563
|
+
default: true
|
|
1564
|
+
});
|
|
1565
|
+
if (confirmed) {
|
|
1566
|
+
config.setProject(name, updatedConfig);
|
|
1567
|
+
log.info(`Project '${name}' updated successfully!`);
|
|
429
1568
|
}
|
|
430
|
-
|
|
431
|
-
|
|
1569
|
+
}
|
|
1570
|
+
async function deleteProjectManage(ctx) {
|
|
1571
|
+
const { config, log } = ctx;
|
|
1572
|
+
const projects = config.getProjects();
|
|
1573
|
+
const baseProjects = Object.keys(projects).filter((name2) => !name2.includes("#"));
|
|
1574
|
+
if (baseProjects.length === 0) {
|
|
1575
|
+
log.info("No projects to delete.");
|
|
1576
|
+
return;
|
|
432
1577
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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;
|
|
1578
|
+
const name = await select({
|
|
1579
|
+
message: "Select project to delete:",
|
|
1580
|
+
choices: baseProjects.map((n) => ({ name: n, value: n }))
|
|
1581
|
+
});
|
|
1582
|
+
const branchPrefix = `${name}#`;
|
|
1583
|
+
const branches = Object.keys(projects).filter((n) => n.startsWith(branchPrefix));
|
|
1584
|
+
if (branches.length > 0) {
|
|
1585
|
+
log.warn(`This project has ${branches.length} branch configuration(s).`);
|
|
1586
|
+
const deleteAll = await confirm3({
|
|
1587
|
+
message: "Delete all branch configurations as well?",
|
|
1588
|
+
default: false
|
|
1589
|
+
});
|
|
1590
|
+
if (deleteAll) {
|
|
1591
|
+
for (const branch of branches) {
|
|
1592
|
+
config.deleteProject(branch);
|
|
1593
|
+
}
|
|
474
1594
|
}
|
|
475
1595
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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();
|
|
1596
|
+
const confirmed = await confirm3({
|
|
1597
|
+
message: `Are you sure you want to delete '${name}'?`,
|
|
1598
|
+
default: false
|
|
1599
|
+
});
|
|
1600
|
+
if (confirmed) {
|
|
1601
|
+
config.deleteProject(name);
|
|
1602
|
+
log.info(`Project '${name}' deleted.`);
|
|
501
1603
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1604
|
+
}
|
|
1605
|
+
function listProjectsManage(ctx) {
|
|
1606
|
+
const { config } = ctx;
|
|
1607
|
+
const projects = config.getProjects();
|
|
1608
|
+
const defaults = config.getDefaults();
|
|
1609
|
+
console.log("\nConfigured projects:\n");
|
|
1610
|
+
const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
|
|
1611
|
+
for (const name of baseProjects) {
|
|
1612
|
+
const project = projects[name];
|
|
1613
|
+
const fullPath = defaults?.base ? File3.from(defaults.base).join(project.path).path : project.path;
|
|
1614
|
+
console.log(` ${name}`);
|
|
1615
|
+
console.log(` Path: ${fullPath}`);
|
|
1616
|
+
console.log(` IDE: ${project.ide || "not set"}`);
|
|
1617
|
+
console.log(` Events: ${Object.keys(project.events).join(", ") || "none"}`);
|
|
1618
|
+
const branchPrefix = `${name}#`;
|
|
1619
|
+
const branches = Object.keys(projects).filter((n) => n.startsWith(branchPrefix));
|
|
1620
|
+
if (branches.length > 0) {
|
|
1621
|
+
console.log(` Branches: ${branches.length}`);
|
|
505
1622
|
}
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
set branch(branch) {
|
|
509
|
-
this._branch = branch;
|
|
1623
|
+
console.log();
|
|
510
1624
|
}
|
|
511
|
-
|
|
512
|
-
|
|
1625
|
+
}
|
|
1626
|
+
async function editBranchManage(projectName, ctx) {
|
|
1627
|
+
const { config, log } = ctx;
|
|
1628
|
+
const projects = config.getProjects();
|
|
1629
|
+
const branchPrefix = `${projectName}#`;
|
|
1630
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1631
|
+
if (branches.length === 0) {
|
|
1632
|
+
log.info("No branch configurations to edit.");
|
|
1633
|
+
return;
|
|
513
1634
|
}
|
|
514
|
-
|
|
515
|
-
|
|
1635
|
+
const branchName = await select({
|
|
1636
|
+
message: "Select branch configuration to edit:",
|
|
1637
|
+
choices: branches.map((n) => ({
|
|
1638
|
+
name: n.substring(branchPrefix.length),
|
|
1639
|
+
value: n
|
|
1640
|
+
}))
|
|
1641
|
+
});
|
|
1642
|
+
const branch = projects[branchName];
|
|
1643
|
+
const keepEvents = await confirm3({
|
|
1644
|
+
message: "Keep existing event configuration?",
|
|
1645
|
+
default: true
|
|
1646
|
+
});
|
|
1647
|
+
let events = branch.events;
|
|
1648
|
+
if (!keepEvents) {
|
|
1649
|
+
const availableEvents = EventRegistry.getEventsForManageUI();
|
|
1650
|
+
const currentEvents = Object.keys(branch.events);
|
|
1651
|
+
const selectedEvents = await checkbox({
|
|
1652
|
+
message: "Select events to enable:",
|
|
1653
|
+
choices: availableEvents.map((e) => ({
|
|
1654
|
+
name: `${e.name} - ${e.description}`,
|
|
1655
|
+
value: e.value,
|
|
1656
|
+
checked: currentEvents.includes(e.value)
|
|
1657
|
+
}))
|
|
1658
|
+
});
|
|
1659
|
+
events = {};
|
|
1660
|
+
for (const eventName of selectedEvents) {
|
|
1661
|
+
if (branch.events[eventName]) {
|
|
1662
|
+
events[eventName] = branch.events[eventName];
|
|
1663
|
+
} else {
|
|
1664
|
+
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1665
|
+
if (eventHandler) {
|
|
1666
|
+
const eventConfig = await eventHandler.configuration.configureInteractive();
|
|
1667
|
+
events[eventName] = eventConfig;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
516
1671
|
}
|
|
517
|
-
|
|
518
|
-
|
|
1672
|
+
const updatedConfig = {
|
|
1673
|
+
...branch,
|
|
1674
|
+
events
|
|
1675
|
+
};
|
|
1676
|
+
const confirmed = await confirm3({
|
|
1677
|
+
message: "Save changes?",
|
|
1678
|
+
default: true
|
|
1679
|
+
});
|
|
1680
|
+
if (confirmed) {
|
|
1681
|
+
config.setProject(branchName, updatedConfig);
|
|
1682
|
+
log.info(`Branch configuration '${branchName}' updated successfully!`);
|
|
519
1683
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
$isProjectEnvironment = true;
|
|
530
|
-
project;
|
|
531
|
-
constructor(projectCfg) {
|
|
532
|
-
this.project = new Project(projectCfg.name, projectCfg);
|
|
1684
|
+
}
|
|
1685
|
+
async function deleteBranchManage(projectName, ctx) {
|
|
1686
|
+
const { config, log } = ctx;
|
|
1687
|
+
const projects = config.getProjects();
|
|
1688
|
+
const branchPrefix = `${projectName}#`;
|
|
1689
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1690
|
+
if (branches.length === 0) {
|
|
1691
|
+
log.info("No branch configurations to delete.");
|
|
1692
|
+
return;
|
|
533
1693
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
1694
|
+
const branchName = await select({
|
|
1695
|
+
message: "Select branch configuration to delete:",
|
|
1696
|
+
choices: branches.map((n) => ({
|
|
1697
|
+
name: n.substring(branchPrefix.length),
|
|
1698
|
+
value: n
|
|
1699
|
+
}))
|
|
1700
|
+
});
|
|
1701
|
+
const confirmed = await confirm3({
|
|
1702
|
+
message: `Are you sure you want to delete '${branchName}'?`,
|
|
1703
|
+
default: false
|
|
1704
|
+
});
|
|
1705
|
+
if (confirmed) {
|
|
1706
|
+
config.deleteProject(branchName);
|
|
1707
|
+
log.info(`Branch configuration '${branchName}' deleted.`);
|
|
537
1708
|
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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 {
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return this.getProjectEnvironment(base, matching);
|
|
1709
|
+
}
|
|
1710
|
+
function listBranchesManage(projectName, ctx) {
|
|
1711
|
+
const { config } = ctx;
|
|
1712
|
+
const projects = config.getProjects();
|
|
1713
|
+
const branchPrefix = `${projectName}#`;
|
|
1714
|
+
const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
|
|
1715
|
+
console.log(`
|
|
1716
|
+
Branch configurations for '${projectName}':
|
|
1717
|
+
`);
|
|
1718
|
+
for (const branchName of branches) {
|
|
1719
|
+
const branch = projects[branchName];
|
|
1720
|
+
const shortName = branchName.substring(branchPrefix.length);
|
|
1721
|
+
console.log(` ${shortName}`);
|
|
1722
|
+
console.log(` Events: ${Object.keys(branch.events).join(", ") || "none"}`);
|
|
1723
|
+
console.log();
|
|
574
1724
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
return this.projects;
|
|
583
|
-
}
|
|
584
|
-
const baseDir = File2.from(defaults.base);
|
|
585
|
-
const projectsMap = this.config.getProjects();
|
|
586
|
-
this.projects = Object.entries(projectsMap).map(([name, project]) => ({
|
|
587
|
-
...project,
|
|
588
|
-
name,
|
|
589
|
-
path: baseDir.join(project.path)
|
|
590
|
-
}));
|
|
591
|
-
return this.projects;
|
|
592
|
-
}
|
|
593
|
-
static getProjectEnvironment(base, _matching) {
|
|
594
|
-
const exactName = `${base.name}#${base.branch}`;
|
|
595
|
-
const exactProj = this.projects.find((p) => p.name === exactName);
|
|
596
|
-
const toProjectConfig = (p) => ({
|
|
597
|
-
name: p.name,
|
|
598
|
-
path: p.path.path,
|
|
599
|
-
// Convert PhyloFile to string path
|
|
600
|
-
ide: p.ide,
|
|
601
|
-
homepage: p.homepage,
|
|
602
|
-
events: p.events,
|
|
603
|
-
branch: p.branch,
|
|
604
|
-
exactName
|
|
605
|
-
});
|
|
606
|
-
if (exactProj) {
|
|
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: () => {
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
this.configured = true;
|
|
629
|
-
}
|
|
1725
|
+
}
|
|
1726
|
+
var init_interactive = __esm({
|
|
1727
|
+
"src/commands/interactive.ts"() {
|
|
1728
|
+
"use strict";
|
|
1729
|
+
init_environment();
|
|
1730
|
+
init_registry();
|
|
1731
|
+
init_constants();
|
|
630
1732
|
}
|
|
631
|
-
};
|
|
1733
|
+
});
|
|
1734
|
+
|
|
1735
|
+
// src/commands/index.ts
|
|
1736
|
+
init_config();
|
|
1737
|
+
init_environment();
|
|
1738
|
+
init_registry();
|
|
1739
|
+
import { Command as Command8 } from "commander";
|
|
1740
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1741
|
+
import { join, dirname } from "path";
|
|
1742
|
+
import { fileURLToPath } from "url";
|
|
1743
|
+
import loog from "loog";
|
|
1744
|
+
import omelette from "omelette";
|
|
1745
|
+
import File7 from "phylo";
|
|
632
1746
|
|
|
633
1747
|
// src/commands/open.ts
|
|
1748
|
+
init_environment();
|
|
634
1749
|
import { Command } from "commander";
|
|
635
1750
|
import File4 from "phylo";
|
|
636
1751
|
|
|
637
1752
|
// src/lib/tmux.ts
|
|
638
|
-
import { exec as execCallback, spawn } from "child_process";
|
|
1753
|
+
import { exec as execCallback, spawn as spawn7 } from "child_process";
|
|
639
1754
|
import { promisify } from "util";
|
|
1755
|
+
|
|
1756
|
+
// src/lib/sanitize.ts
|
|
1757
|
+
function sanitizeForShell(input6) {
|
|
1758
|
+
if (!input6) return "";
|
|
1759
|
+
return input6.replace(/[^a-zA-Z0-9_\-.]/g, "_");
|
|
1760
|
+
}
|
|
1761
|
+
function escapeForSingleQuotes(input6) {
|
|
1762
|
+
if (!input6) return "";
|
|
1763
|
+
return input6.replace(/'/g, "'\\''");
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// src/lib/tmux.ts
|
|
640
1767
|
var exec = promisify(execCallback);
|
|
641
1768
|
var TmuxManager = class {
|
|
642
1769
|
sessionPrefix = "workon-";
|
|
@@ -650,755 +1777,176 @@ var TmuxManager = class {
|
|
|
650
1777
|
}
|
|
651
1778
|
async sessionExists(sessionName) {
|
|
652
1779
|
try {
|
|
653
|
-
await exec(`tmux has-session -t
|
|
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
|
-
});
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
726
|
-
const sessionName = this.getSessionName(projectName);
|
|
727
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
728
|
-
return [
|
|
729
|
-
`# Create tmux split session for ${projectName}`,
|
|
730
|
-
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
731
|
-
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
732
|
-
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
733
|
-
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
734
|
-
this.getAttachCommand(sessionName)
|
|
735
|
-
];
|
|
736
|
-
}
|
|
737
|
-
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
738
|
-
const sessionName = this.getSessionName(projectName);
|
|
739
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
740
|
-
return [
|
|
741
|
-
`# Create tmux three-pane session for ${projectName}`,
|
|
742
|
-
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
743
|
-
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
744
|
-
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
745
|
-
`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`,
|
|
746
|
-
`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`,
|
|
747
|
-
`tmux resize-pane -t "${sessionName}:0.2" -y 10`,
|
|
748
|
-
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
749
|
-
this.getAttachCommand(sessionName)
|
|
750
|
-
];
|
|
751
|
-
}
|
|
752
|
-
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
753
|
-
const sessionName = this.getSessionName(projectName);
|
|
754
|
-
return [
|
|
755
|
-
`# Create tmux two-pane session with npm for ${projectName}`,
|
|
756
|
-
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
757
|
-
`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`,
|
|
758
|
-
`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`,
|
|
759
|
-
`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`,
|
|
760
|
-
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
761
|
-
this.getAttachCommand(sessionName)
|
|
762
|
-
];
|
|
763
|
-
}
|
|
764
|
-
getAttachCommand(sessionName) {
|
|
765
|
-
if (process.env.TMUX) {
|
|
766
|
-
return `tmux switch-client -t "${sessionName}"`;
|
|
767
|
-
}
|
|
768
|
-
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
769
|
-
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
770
|
-
if (useiTermIntegration) {
|
|
771
|
-
return `tmux -CC attach-session -t "${sessionName}"`;
|
|
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
|
-
}
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
// src/events/core/cwd.ts
|
|
786
|
-
import { spawn as spawn2 } from "child_process";
|
|
787
|
-
var CwdEvent = class {
|
|
788
|
-
static get metadata() {
|
|
789
|
-
return {
|
|
790
|
-
name: "cwd",
|
|
791
|
-
displayName: "Change directory (cwd)",
|
|
792
|
-
description: "Change current working directory to project path",
|
|
793
|
-
category: "core",
|
|
794
|
-
requiresTmux: false,
|
|
795
|
-
dependencies: []
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
static get validation() {
|
|
799
|
-
return {
|
|
800
|
-
validateConfig(config) {
|
|
801
|
-
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
802
|
-
return true;
|
|
803
|
-
}
|
|
804
|
-
return "cwd config must be a boolean (true/false)";
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
static get configuration() {
|
|
809
|
-
return {
|
|
810
|
-
async configureInteractive() {
|
|
811
|
-
return true;
|
|
812
|
-
},
|
|
813
|
-
getDefaultConfig() {
|
|
814
|
-
return true;
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
static get processing() {
|
|
819
|
-
return {
|
|
820
|
-
async processEvent(context) {
|
|
821
|
-
const { project, isShellMode, shellCommands } = context;
|
|
822
|
-
const projectPath = project.path.path;
|
|
823
|
-
if (isShellMode) {
|
|
824
|
-
shellCommands.push(`cd "${projectPath}"`);
|
|
825
|
-
} else {
|
|
826
|
-
const shell = process.env.SHELL || "/bin/bash";
|
|
827
|
-
spawn2(shell, [], {
|
|
828
|
-
cwd: projectPath,
|
|
829
|
-
stdio: "inherit"
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
},
|
|
833
|
-
generateShellCommand(context) {
|
|
834
|
-
const projectPath = context.project.path.path;
|
|
835
|
-
return [`cd "${projectPath}"`];
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
static get tmux() {
|
|
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
|
-
};
|
|
853
|
-
|
|
854
|
-
// src/events/core/ide.ts
|
|
855
|
-
import { spawn as spawn3 } from "child_process";
|
|
856
|
-
var IdeEvent = class {
|
|
857
|
-
static get metadata() {
|
|
858
|
-
return {
|
|
859
|
-
name: "ide",
|
|
860
|
-
displayName: "Open in IDE",
|
|
861
|
-
description: "Open project in configured IDE/editor",
|
|
862
|
-
category: "core",
|
|
863
|
-
requiresTmux: false,
|
|
864
|
-
dependencies: []
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
static get validation() {
|
|
868
|
-
return {
|
|
869
|
-
validateConfig(config) {
|
|
870
|
-
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
871
|
-
return true;
|
|
872
|
-
}
|
|
873
|
-
return "ide config must be a boolean (true/false)";
|
|
874
|
-
}
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
static get configuration() {
|
|
878
|
-
return {
|
|
879
|
-
async configureInteractive() {
|
|
880
|
-
return true;
|
|
881
|
-
},
|
|
882
|
-
getDefaultConfig() {
|
|
883
|
-
return true;
|
|
884
|
-
}
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
static get processing() {
|
|
888
|
-
return {
|
|
889
|
-
async processEvent(context) {
|
|
890
|
-
const { project, isShellMode, shellCommands } = context;
|
|
891
|
-
const projectPath = project.path.path;
|
|
892
|
-
const ide = project.ide || "code";
|
|
893
|
-
if (isShellMode) {
|
|
894
|
-
shellCommands.push(`${ide} "${projectPath}" &`);
|
|
895
|
-
} else {
|
|
896
|
-
spawn3(ide, [projectPath], {
|
|
897
|
-
detached: true,
|
|
898
|
-
stdio: "ignore"
|
|
899
|
-
}).unref();
|
|
900
|
-
}
|
|
901
|
-
},
|
|
902
|
-
generateShellCommand(context) {
|
|
903
|
-
const projectPath = context.project.path.path;
|
|
904
|
-
const ide = context.project.ide || "code";
|
|
905
|
-
return [`${ide} "${projectPath}" &`];
|
|
906
|
-
}
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
static get tmux() {
|
|
910
|
-
return null;
|
|
911
|
-
}
|
|
912
|
-
static get help() {
|
|
913
|
-
return {
|
|
914
|
-
usage: "ide: true | false",
|
|
915
|
-
description: "Open the project in the configured IDE",
|
|
916
|
-
examples: [
|
|
917
|
-
{ config: true, description: "Enable IDE opening" },
|
|
918
|
-
{ config: false, description: "Disable IDE opening" }
|
|
919
|
-
]
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
// src/events/core/web.ts
|
|
925
|
-
import { spawn as spawn4 } from "child_process";
|
|
926
|
-
import { platform } from "os";
|
|
927
|
-
var WebEvent = class _WebEvent {
|
|
928
|
-
static get metadata() {
|
|
929
|
-
return {
|
|
930
|
-
name: "web",
|
|
931
|
-
displayName: "Open homepage in browser",
|
|
932
|
-
description: "Open project homepage in web browser",
|
|
933
|
-
category: "core",
|
|
934
|
-
requiresTmux: false,
|
|
935
|
-
dependencies: []
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
static get validation() {
|
|
939
|
-
return {
|
|
940
|
-
validateConfig(config) {
|
|
941
|
-
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
942
|
-
return true;
|
|
943
|
-
}
|
|
944
|
-
return "web config must be a boolean (true/false)";
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
static get configuration() {
|
|
949
|
-
return {
|
|
950
|
-
async configureInteractive() {
|
|
951
|
-
return true;
|
|
952
|
-
},
|
|
953
|
-
getDefaultConfig() {
|
|
954
|
-
return true;
|
|
955
|
-
}
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
static getOpenCommand() {
|
|
959
|
-
const os = platform();
|
|
960
|
-
switch (os) {
|
|
961
|
-
case "darwin":
|
|
962
|
-
return "open";
|
|
963
|
-
case "win32":
|
|
964
|
-
return "start";
|
|
965
|
-
default:
|
|
966
|
-
return "xdg-open";
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
static get processing() {
|
|
970
|
-
return {
|
|
971
|
-
async processEvent(context) {
|
|
972
|
-
const { project, isShellMode, shellCommands } = context;
|
|
973
|
-
const homepage = project.homepage;
|
|
974
|
-
if (!homepage) {
|
|
975
|
-
console.warn("No homepage configured for project");
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
const openCmd = _WebEvent.getOpenCommand();
|
|
979
|
-
if (isShellMode) {
|
|
980
|
-
shellCommands.push(`${openCmd} "${homepage}" &`);
|
|
981
|
-
} else {
|
|
982
|
-
spawn4(openCmd, [homepage], {
|
|
983
|
-
detached: true,
|
|
984
|
-
stdio: "ignore"
|
|
985
|
-
}).unref();
|
|
986
|
-
}
|
|
987
|
-
},
|
|
988
|
-
generateShellCommand(context) {
|
|
989
|
-
const homepage = context.project.homepage;
|
|
990
|
-
if (!homepage) return [];
|
|
991
|
-
const openCmd = _WebEvent.getOpenCommand();
|
|
992
|
-
return [`${openCmd} "${homepage}" &`];
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
static get tmux() {
|
|
997
|
-
return null;
|
|
998
|
-
}
|
|
999
|
-
static get help() {
|
|
1000
|
-
return {
|
|
1001
|
-
usage: "web: true | false",
|
|
1002
|
-
description: "Open the project homepage in the default browser",
|
|
1003
|
-
examples: [
|
|
1004
|
-
{ config: true, description: "Enable browser opening" },
|
|
1005
|
-
{ config: false, description: "Disable browser opening" }
|
|
1006
|
-
]
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
// src/events/extensions/claude.ts
|
|
1012
|
-
import { spawn as spawn5 } from "child_process";
|
|
1013
|
-
import { input, confirm } from "@inquirer/prompts";
|
|
1014
|
-
var ClaudeEvent = class _ClaudeEvent {
|
|
1015
|
-
static get metadata() {
|
|
1016
|
-
return {
|
|
1017
|
-
name: "claude",
|
|
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
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
static get validation() {
|
|
1026
|
-
return {
|
|
1027
|
-
validateConfig(config) {
|
|
1028
|
-
if (typeof config === "boolean" || config === "true" || config === "false") {
|
|
1029
|
-
return true;
|
|
1030
|
-
}
|
|
1031
|
-
if (typeof config === "object" && config !== null) {
|
|
1032
|
-
const cfg = config;
|
|
1033
|
-
if (cfg.flags !== void 0) {
|
|
1034
|
-
if (!Array.isArray(cfg.flags)) {
|
|
1035
|
-
return "claude.flags must be an array of strings";
|
|
1036
|
-
}
|
|
1037
|
-
for (const flag of cfg.flags) {
|
|
1038
|
-
if (typeof flag !== "string") {
|
|
1039
|
-
return "claude.flags must contain only strings";
|
|
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";
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
static get configuration() {
|
|
1056
|
-
return {
|
|
1057
|
-
async configureInteractive() {
|
|
1058
|
-
const useAdvanced = await confirm({
|
|
1059
|
-
message: "Configure advanced Claude options?",
|
|
1060
|
-
default: false
|
|
1061
|
-
});
|
|
1062
|
-
if (!useAdvanced) {
|
|
1063
|
-
return true;
|
|
1064
|
-
}
|
|
1065
|
-
const flagsInput = await input({
|
|
1066
|
-
message: "Enter Claude flags (comma-separated, e.g., --resume, --debug):",
|
|
1067
|
-
default: ""
|
|
1068
|
-
});
|
|
1069
|
-
const flags = flagsInput.split(",").map((f) => f.trim()).filter((f) => f.length > 0 && f.startsWith("-"));
|
|
1070
|
-
const splitTerminal = await confirm({
|
|
1071
|
-
message: "Use split terminal layout (Claude + shell)?",
|
|
1072
|
-
default: true
|
|
1073
|
-
});
|
|
1074
|
-
if (flags.length === 0 && !splitTerminal) {
|
|
1075
|
-
return true;
|
|
1076
|
-
}
|
|
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
|
-
}
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
static getClaudeCommand(config) {
|
|
1088
|
-
if (typeof config === "boolean" || config === void 0) {
|
|
1089
|
-
return "claude";
|
|
1780
|
+
await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
1781
|
+
return true;
|
|
1782
|
+
} catch {
|
|
1783
|
+
return false;
|
|
1090
1784
|
}
|
|
1091
|
-
const flags = config.flags || [];
|
|
1092
|
-
return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
|
|
1093
1785
|
}
|
|
1094
|
-
|
|
1095
|
-
return {
|
|
1096
|
-
async processEvent(context) {
|
|
1097
|
-
const { project, isShellMode, shellCommands } = context;
|
|
1098
|
-
const claudeConfig = project.events.claude;
|
|
1099
|
-
const claudeCommand = _ClaudeEvent.getClaudeCommand(claudeConfig);
|
|
1100
|
-
if (isShellMode) {
|
|
1101
|
-
shellCommands.push(claudeCommand);
|
|
1102
|
-
} else {
|
|
1103
|
-
const args = claudeCommand.split(" ").slice(1);
|
|
1104
|
-
spawn5("claude", args, {
|
|
1105
|
-
cwd: project.path.path,
|
|
1106
|
-
stdio: "inherit"
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
},
|
|
1110
|
-
generateShellCommand(context) {
|
|
1111
|
-
const claudeConfig = context.project.events.claude;
|
|
1112
|
-
return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1786
|
+
getSessionName(projectName) {
|
|
1787
|
+
return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
|
|
1115
1788
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return "three-pane";
|
|
1124
|
-
}
|
|
1125
|
-
return "split";
|
|
1126
|
-
}
|
|
1127
|
-
};
|
|
1789
|
+
async killSession(sessionName) {
|
|
1790
|
+
try {
|
|
1791
|
+
await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
|
|
1792
|
+
return true;
|
|
1793
|
+
} catch {
|
|
1794
|
+
return false;
|
|
1795
|
+
}
|
|
1128
1796
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
};
|
|
1797
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
1798
|
+
const sessionName = this.getSessionName(projectName);
|
|
1799
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1800
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1801
|
+
if (await this.sessionExists(sessionName)) {
|
|
1802
|
+
await this.killSession(sessionName);
|
|
1803
|
+
}
|
|
1804
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1805
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1806
|
+
await exec(
|
|
1807
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
1808
|
+
);
|
|
1809
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
1810
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1811
|
+
return sessionName;
|
|
1142
1812
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1813
|
+
async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1814
|
+
const sessionName = this.getSessionName(projectName);
|
|
1815
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1816
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1817
|
+
if (await this.sessionExists(sessionName)) {
|
|
1818
|
+
await this.killSession(sessionName);
|
|
1819
|
+
}
|
|
1820
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1821
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1822
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1823
|
+
await exec(
|
|
1824
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
|
|
1825
|
+
);
|
|
1826
|
+
await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
|
|
1827
|
+
await exec(
|
|
1828
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
1829
|
+
);
|
|
1830
|
+
await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
|
|
1831
|
+
await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
|
|
1832
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1833
|
+
return sessionName;
|
|
1158
1834
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
};
|
|
1835
|
+
async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1836
|
+
const sessionName = this.getSessionName(projectName);
|
|
1837
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1838
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1839
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1840
|
+
if (await this.sessionExists(sessionName)) {
|
|
1841
|
+
await this.killSession(sessionName);
|
|
1842
|
+
}
|
|
1843
|
+
await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
|
|
1844
|
+
await exec(
|
|
1845
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
|
|
1846
|
+
);
|
|
1847
|
+
await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
|
|
1848
|
+
await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
|
|
1849
|
+
return sessionName;
|
|
1188
1850
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1851
|
+
async attachToSession(sessionName) {
|
|
1852
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1853
|
+
if (process.env.TMUX) {
|
|
1854
|
+
await exec(`tmux switch-client -t '${escapedSession}'`);
|
|
1855
|
+
} else {
|
|
1856
|
+
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
|
|
1857
|
+
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1858
|
+
if (useiTermIntegration) {
|
|
1859
|
+
spawn7("tmux", ["-CC", "attach-session", "-t", sessionName], {
|
|
1860
|
+
stdio: "inherit",
|
|
1861
|
+
detached: true
|
|
1195
1862
|
});
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1863
|
+
} else {
|
|
1864
|
+
spawn7("tmux", ["attach-session", "-t", sessionName], {
|
|
1865
|
+
stdio: "inherit",
|
|
1866
|
+
detached: true
|
|
1199
1867
|
});
|
|
1200
|
-
const services = servicesInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1201
|
-
if (composeFile === "docker-compose.yml" && services.length === 0) {
|
|
1202
|
-
return { compose_file: "docker-compose.yml" };
|
|
1203
|
-
}
|
|
1204
|
-
if (services.length === 0) {
|
|
1205
|
-
return composeFile;
|
|
1206
|
-
}
|
|
1207
|
-
return {
|
|
1208
|
-
compose_file: composeFile,
|
|
1209
|
-
services
|
|
1210
|
-
};
|
|
1211
|
-
},
|
|
1212
|
-
getDefaultConfig() {
|
|
1213
|
-
return { compose_file: "docker-compose.yml" };
|
|
1214
1868
|
}
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
static getDockerCommand(config) {
|
|
1218
|
-
if (typeof config === "boolean" || config === void 0) {
|
|
1219
|
-
return "docker-compose up -d";
|
|
1220
1869
|
}
|
|
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
1870
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
stdio: "inherit"
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
},
|
|
1246
|
-
generateShellCommand(context) {
|
|
1247
|
-
const dockerConfig = context.project.events.docker;
|
|
1248
|
-
return [_DockerEvent.getDockerCommand(dockerConfig)];
|
|
1249
|
-
}
|
|
1250
|
-
};
|
|
1871
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
1872
|
+
const sessionName = this.getSessionName(projectName);
|
|
1873
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1874
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1875
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1876
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1877
|
+
return [
|
|
1878
|
+
`# Create tmux split session for ${sanitizeForShell(projectName)}`,
|
|
1879
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1880
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
1881
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
1882
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1883
|
+
this.getAttachCommand(sessionName)
|
|
1884
|
+
];
|
|
1251
1885
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1886
|
+
buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
|
|
1887
|
+
const sessionName = this.getSessionName(projectName);
|
|
1888
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1889
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1890
|
+
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1891
|
+
const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
|
|
1892
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1893
|
+
return [
|
|
1894
|
+
`# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
|
|
1895
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1896
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
|
|
1897
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
|
|
1898
|
+
`tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
1899
|
+
`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
|
|
1900
|
+
`tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
|
|
1901
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1902
|
+
this.getAttachCommand(sessionName)
|
|
1903
|
+
];
|
|
1254
1904
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1905
|
+
buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
|
|
1906
|
+
const sessionName = this.getSessionName(projectName);
|
|
1907
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1908
|
+
const escapedPath = escapeForSingleQuotes(projectPath);
|
|
1909
|
+
const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
|
|
1910
|
+
return [
|
|
1911
|
+
`# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
|
|
1912
|
+
`tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
|
|
1913
|
+
`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
|
|
1914
|
+
`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
|
|
1915
|
+
`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
|
|
1916
|
+
`tmux select-pane -t '${escapedSession}:0.0'`,
|
|
1917
|
+
this.getAttachCommand(sessionName)
|
|
1918
|
+
];
|
|
1268
1919
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
* Initialize the registry by registering all events
|
|
1279
|
-
*/
|
|
1280
|
-
async initialize() {
|
|
1281
|
-
if (this._initialized) return;
|
|
1282
|
-
this.registerEvents();
|
|
1283
|
-
this._initialized = true;
|
|
1284
|
-
}
|
|
1285
|
-
/**
|
|
1286
|
-
* Register all event classes
|
|
1287
|
-
*/
|
|
1288
|
-
registerEvents() {
|
|
1289
|
-
for (const EventClass of ALL_EVENTS) {
|
|
1290
|
-
if (this.isValidEvent(EventClass)) {
|
|
1291
|
-
const metadata = EventClass.metadata;
|
|
1292
|
-
this._events.set(metadata.name, EventClass);
|
|
1293
|
-
}
|
|
1920
|
+
getAttachCommand(sessionName) {
|
|
1921
|
+
const escapedSession = escapeForSingleQuotes(sessionName);
|
|
1922
|
+
if (process.env.TMUX) {
|
|
1923
|
+
return `tmux switch-client -t '${escapedSession}'`;
|
|
1924
|
+
}
|
|
1925
|
+
const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
|
|
1926
|
+
const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
|
|
1927
|
+
if (useiTermIntegration) {
|
|
1928
|
+
return `tmux -CC attach-session -t '${escapedSession}'`;
|
|
1294
1929
|
}
|
|
1930
|
+
return `tmux attach-session -t '${escapedSession}'`;
|
|
1295
1931
|
}
|
|
1296
|
-
|
|
1297
|
-
* Validate if a class is a proper event
|
|
1298
|
-
*/
|
|
1299
|
-
isValidEvent(EventClass) {
|
|
1932
|
+
async listWorkonSessions() {
|
|
1300
1933
|
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";
|
|
1934
|
+
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
1935
|
+
return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
|
|
1304
1936
|
} catch {
|
|
1305
|
-
return
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Get all valid event names from registered events
|
|
1310
|
-
*/
|
|
1311
|
-
getValidEventNames() {
|
|
1312
|
-
this.ensureInitialized();
|
|
1313
|
-
return Array.from(this._events.keys());
|
|
1314
|
-
}
|
|
1315
|
-
/**
|
|
1316
|
-
* Get event by name
|
|
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
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
return events.sort((a, b) => a.name.localeCompare(b.name));
|
|
1337
|
-
}
|
|
1338
|
-
/**
|
|
1339
|
-
* Get events that support tmux integration
|
|
1340
|
-
*/
|
|
1341
|
-
getTmuxEnabledEvents() {
|
|
1342
|
-
this.ensureInitialized();
|
|
1343
|
-
const tmuxEvents = [];
|
|
1344
|
-
for (const [name, EventClass] of this._events) {
|
|
1345
|
-
const tmux = EventClass.tmux;
|
|
1346
|
-
if (tmux) {
|
|
1347
|
-
tmuxEvents.push({
|
|
1348
|
-
name,
|
|
1349
|
-
event: EventClass,
|
|
1350
|
-
priority: tmux.getLayoutPriority ? tmux.getLayoutPriority() : 0
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
return tmuxEvents.sort((a, b) => b.priority - a.priority);
|
|
1355
|
-
}
|
|
1356
|
-
/**
|
|
1357
|
-
* Get all available events with their metadata
|
|
1358
|
-
*/
|
|
1359
|
-
getAllEvents() {
|
|
1360
|
-
this.ensureInitialized();
|
|
1361
|
-
const events = [];
|
|
1362
|
-
for (const [name, EventClass] of this._events) {
|
|
1363
|
-
const typedClass = EventClass;
|
|
1364
|
-
events.push({
|
|
1365
|
-
name,
|
|
1366
|
-
metadata: typedClass.metadata,
|
|
1367
|
-
hasValidation: !!typedClass.validation,
|
|
1368
|
-
hasConfiguration: !!typedClass.configuration,
|
|
1369
|
-
hasProcessing: !!typedClass.processing,
|
|
1370
|
-
hasTmux: !!typedClass.tmux,
|
|
1371
|
-
hasHelp: !!typedClass.help
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1374
|
-
return events;
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Ensure registry is initialized
|
|
1378
|
-
*/
|
|
1379
|
-
ensureInitialized() {
|
|
1380
|
-
if (!this._initialized) {
|
|
1381
|
-
throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
|
|
1937
|
+
return [];
|
|
1382
1938
|
}
|
|
1383
1939
|
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Clear the registry (useful for testing)
|
|
1386
|
-
*/
|
|
1387
|
-
clear() {
|
|
1388
|
-
this._events.clear();
|
|
1389
|
-
this._initialized = false;
|
|
1390
|
-
}
|
|
1391
1940
|
};
|
|
1392
|
-
var EventRegistry = new EventRegistryClass();
|
|
1393
1941
|
|
|
1394
1942
|
// src/commands/open.ts
|
|
1943
|
+
init_registry();
|
|
1395
1944
|
function createOpenCommand(ctx) {
|
|
1396
1945
|
const { config, log } = ctx;
|
|
1397
1946
|
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) => {
|
|
1398
1947
|
if (options.debug) {
|
|
1399
1948
|
log.setLogLevel("debug");
|
|
1400
1949
|
}
|
|
1401
|
-
await EventRegistry.initialize();
|
|
1402
1950
|
if (projectArg) {
|
|
1403
1951
|
await processProject(projectArg, options, ctx);
|
|
1404
1952
|
} else {
|
|
@@ -1505,32 +2053,34 @@ function resolveCommandDependencies(requestedCommands, project) {
|
|
|
1505
2053
|
}
|
|
1506
2054
|
return [...new Set(resolved)];
|
|
1507
2055
|
}
|
|
1508
|
-
|
|
2056
|
+
function getClaudeArgs(project) {
|
|
2057
|
+
const claudeConfig = project.events.claude;
|
|
2058
|
+
return typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
|
|
2059
|
+
}
|
|
2060
|
+
async function getNpmCommand(project) {
|
|
2061
|
+
const npmConfig = project.events.npm;
|
|
2062
|
+
const { NpmEvent: NpmEvent2 } = await Promise.resolve().then(() => (init_npm(), npm_exports));
|
|
2063
|
+
return NpmEvent2.getNpmCommand(npmConfig);
|
|
2064
|
+
}
|
|
2065
|
+
async function handleTmuxLayout(project, layout, options, shellCommands, events, ctx) {
|
|
1509
2066
|
const { log } = ctx;
|
|
1510
2067
|
const tmux = new TmuxManager();
|
|
1511
|
-
const
|
|
1512
|
-
const claudeArgs = typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
|
|
2068
|
+
const { isShellMode, dryRun } = options;
|
|
1513
2069
|
let tmuxHandled = false;
|
|
1514
2070
|
if (isShellMode) {
|
|
1515
2071
|
if (await tmux.isTmuxAvailable()) {
|
|
1516
|
-
const commands = tmux
|
|
2072
|
+
const commands = buildLayoutShellCommands(tmux, project, layout);
|
|
1517
2073
|
shellCommands.push(...commands);
|
|
1518
2074
|
tmuxHandled = true;
|
|
1519
2075
|
} else {
|
|
1520
2076
|
log.debug("Tmux not available, falling back to normal mode");
|
|
1521
|
-
shellCommands
|
|
1522
|
-
const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
|
|
1523
|
-
shellCommands.push(claudeCommand);
|
|
2077
|
+
buildFallbackCommands(shellCommands, project, layout);
|
|
1524
2078
|
tmuxHandled = true;
|
|
1525
2079
|
}
|
|
1526
2080
|
} else if (!dryRun) {
|
|
1527
2081
|
if (await tmux.isTmuxAvailable()) {
|
|
1528
2082
|
try {
|
|
1529
|
-
const sessionName = await tmux
|
|
1530
|
-
project.name,
|
|
1531
|
-
project.path.path,
|
|
1532
|
-
claudeArgs
|
|
1533
|
-
);
|
|
2083
|
+
const sessionName = await createTmuxSession(tmux, project, layout);
|
|
1534
2084
|
await tmux.attachToSession(sessionName);
|
|
1535
2085
|
tmuxHandled = true;
|
|
1536
2086
|
} catch (error) {
|
|
@@ -1540,139 +2090,103 @@ async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands,
|
|
|
1540
2090
|
log.debug("Tmux not available, falling back to normal event processing");
|
|
1541
2091
|
}
|
|
1542
2092
|
} else {
|
|
1543
|
-
log.info(
|
|
2093
|
+
log.info(layout.dryRunMessage);
|
|
1544
2094
|
tmuxHandled = true;
|
|
1545
2095
|
}
|
|
1546
2096
|
if (!tmuxHandled && !dryRun) {
|
|
1547
|
-
for (const event of events.filter((e) =>
|
|
2097
|
+
for (const event of events.filter((e) => layout.handledEvents.includes(e))) {
|
|
1548
2098
|
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
1549
2099
|
}
|
|
1550
2100
|
}
|
|
1551
2101
|
if (!dryRun) {
|
|
1552
|
-
for (const event of events.filter((e) => !
|
|
2102
|
+
for (const event of events.filter((e) => !layout.handledEvents.includes(e))) {
|
|
1553
2103
|
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
1554
2104
|
}
|
|
1555
2105
|
}
|
|
1556
2106
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
const { NpmEvent: NpmEvent2 } = await Promise.resolve().then(() => (init_npm(), npm_exports));
|
|
1564
|
-
const npmCommand = NpmEvent2.getNpmCommand(npmConfig);
|
|
1565
|
-
let tmuxHandled = false;
|
|
1566
|
-
if (isShellMode) {
|
|
1567
|
-
if (await tmux.isTmuxAvailable()) {
|
|
1568
|
-
const commands = tmux.buildThreePaneShellCommands(
|
|
2107
|
+
function buildLayoutShellCommands(tmux, project, layout) {
|
|
2108
|
+
switch (layout.type) {
|
|
2109
|
+
case "split-claude":
|
|
2110
|
+
return tmux.buildShellCommands(project.name, project.path.path, layout.claudeArgs);
|
|
2111
|
+
case "three-pane":
|
|
2112
|
+
return tmux.buildThreePaneShellCommands(
|
|
1569
2113
|
project.name,
|
|
1570
2114
|
project.path.path,
|
|
1571
|
-
claudeArgs,
|
|
1572
|
-
npmCommand
|
|
2115
|
+
layout.claudeArgs,
|
|
2116
|
+
layout.npmCommand
|
|
1573
2117
|
);
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
} else {
|
|
1577
|
-
log.debug("Tmux not available, falling back to normal mode");
|
|
1578
|
-
shellCommands.push(`cd "${project.path.path}"`);
|
|
1579
|
-
shellCommands.push(claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude");
|
|
1580
|
-
shellCommands.push(npmCommand);
|
|
1581
|
-
tmuxHandled = true;
|
|
1582
|
-
}
|
|
1583
|
-
} else if (!dryRun) {
|
|
1584
|
-
if (await tmux.isTmuxAvailable()) {
|
|
1585
|
-
try {
|
|
1586
|
-
const sessionName = await tmux.createThreePaneSession(
|
|
1587
|
-
project.name,
|
|
1588
|
-
project.path.path,
|
|
1589
|
-
claudeArgs,
|
|
1590
|
-
npmCommand
|
|
1591
|
-
);
|
|
1592
|
-
await tmux.attachToSession(sessionName);
|
|
1593
|
-
tmuxHandled = true;
|
|
1594
|
-
} catch (error) {
|
|
1595
|
-
log.debug(`Failed to create tmux session: ${error.message}`);
|
|
1596
|
-
}
|
|
1597
|
-
} else {
|
|
1598
|
-
log.debug("Tmux not available, falling back to normal event processing");
|
|
1599
|
-
}
|
|
1600
|
-
} else {
|
|
1601
|
-
log.info(`Would create three-pane tmux session '${project.name}' with Claude and NPM`);
|
|
1602
|
-
tmuxHandled = true;
|
|
2118
|
+
case "two-pane-npm":
|
|
2119
|
+
return tmux.buildTwoPaneNpmShellCommands(project.name, project.path.path, layout.npmCommand);
|
|
1603
2120
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2121
|
+
}
|
|
2122
|
+
function buildFallbackCommands(shellCommands, project, layout) {
|
|
2123
|
+
shellCommands.push(`cd "${project.path.path}"`);
|
|
2124
|
+
if (layout.type === "split-claude" || layout.type === "three-pane") {
|
|
2125
|
+
const claudeCommand = layout.claudeArgs.length > 0 ? `claude ${layout.claudeArgs.join(" ")}` : "claude";
|
|
2126
|
+
shellCommands.push(claudeCommand);
|
|
1608
2127
|
}
|
|
1609
|
-
if (
|
|
1610
|
-
|
|
1611
|
-
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
1612
|
-
}
|
|
2128
|
+
if (layout.npmCommand) {
|
|
2129
|
+
shellCommands.push(layout.npmCommand);
|
|
1613
2130
|
}
|
|
1614
2131
|
}
|
|
1615
|
-
async function
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
let tmuxHandled = false;
|
|
1622
|
-
if (isShellMode) {
|
|
1623
|
-
if (await tmux.isTmuxAvailable()) {
|
|
1624
|
-
const commands = tmux.buildTwoPaneNpmShellCommands(
|
|
2132
|
+
async function createTmuxSession(tmux, project, layout) {
|
|
2133
|
+
switch (layout.type) {
|
|
2134
|
+
case "split-claude":
|
|
2135
|
+
return tmux.createSplitSession(project.name, project.path.path, layout.claudeArgs);
|
|
2136
|
+
case "three-pane":
|
|
2137
|
+
return tmux.createThreePaneSession(
|
|
1625
2138
|
project.name,
|
|
1626
2139
|
project.path.path,
|
|
1627
|
-
|
|
2140
|
+
layout.claudeArgs,
|
|
2141
|
+
layout.npmCommand
|
|
1628
2142
|
);
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
} else {
|
|
1632
|
-
log.debug("Tmux not available, falling back to normal mode");
|
|
1633
|
-
shellCommands.push(`cd "${project.path.path}"`);
|
|
1634
|
-
shellCommands.push(npmCommand);
|
|
1635
|
-
tmuxHandled = true;
|
|
1636
|
-
}
|
|
1637
|
-
} else if (!dryRun) {
|
|
1638
|
-
if (await tmux.isTmuxAvailable()) {
|
|
1639
|
-
try {
|
|
1640
|
-
const sessionName = await tmux.createTwoPaneNpmSession(
|
|
1641
|
-
project.name,
|
|
1642
|
-
project.path.path,
|
|
1643
|
-
npmCommand
|
|
1644
|
-
);
|
|
1645
|
-
await tmux.attachToSession(sessionName);
|
|
1646
|
-
tmuxHandled = true;
|
|
1647
|
-
} catch (error) {
|
|
1648
|
-
log.debug(`Failed to create tmux session: ${error.message}`);
|
|
1649
|
-
}
|
|
1650
|
-
} else {
|
|
1651
|
-
log.debug("Tmux not available, falling back to normal event processing");
|
|
1652
|
-
}
|
|
1653
|
-
} else {
|
|
1654
|
-
log.info(`Would create two-pane tmux session '${project.name}' with NPM`);
|
|
1655
|
-
tmuxHandled = true;
|
|
1656
|
-
}
|
|
1657
|
-
if (!tmuxHandled && !dryRun) {
|
|
1658
|
-
for (const event of events.filter((e) => ["cwd", "npm"].includes(e))) {
|
|
1659
|
-
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
if (!dryRun) {
|
|
1663
|
-
for (const event of events.filter((e) => !["cwd", "npm"].includes(e))) {
|
|
1664
|
-
await processEvent(event, { project, isShellMode, shellCommands }, ctx);
|
|
1665
|
-
}
|
|
2143
|
+
case "two-pane-npm":
|
|
2144
|
+
return tmux.createTwoPaneNpmSession(project.name, project.path.path, layout.npmCommand);
|
|
1666
2145
|
}
|
|
1667
2146
|
}
|
|
2147
|
+
async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands, events, ctx) {
|
|
2148
|
+
const layout = {
|
|
2149
|
+
type: "split-claude",
|
|
2150
|
+
handledEvents: ["cwd", "claude"],
|
|
2151
|
+
dryRunMessage: `Would create split tmux session '${project.name}' with Claude`,
|
|
2152
|
+
claudeArgs: getClaudeArgs(project),
|
|
2153
|
+
npmCommand: null
|
|
2154
|
+
};
|
|
2155
|
+
await handleTmuxLayout(project, layout, { isShellMode, dryRun }, shellCommands, events, ctx);
|
|
2156
|
+
}
|
|
2157
|
+
async function handleThreePaneLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
|
|
2158
|
+
const layout = {
|
|
2159
|
+
type: "three-pane",
|
|
2160
|
+
handledEvents: ["cwd", "claude", "npm"],
|
|
2161
|
+
dryRunMessage: `Would create three-pane tmux session '${project.name}' with Claude and NPM`,
|
|
2162
|
+
claudeArgs: getClaudeArgs(project),
|
|
2163
|
+
npmCommand: await getNpmCommand(project)
|
|
2164
|
+
};
|
|
2165
|
+
await handleTmuxLayout(project, layout, { isShellMode, dryRun }, shellCommands, events, ctx);
|
|
2166
|
+
}
|
|
2167
|
+
async function handleTwoPaneNpmLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
|
|
2168
|
+
const layout = {
|
|
2169
|
+
type: "two-pane-npm",
|
|
2170
|
+
handledEvents: ["cwd", "npm"],
|
|
2171
|
+
dryRunMessage: `Would create two-pane tmux session '${project.name}' with NPM`,
|
|
2172
|
+
claudeArgs: [],
|
|
2173
|
+
npmCommand: await getNpmCommand(project)
|
|
2174
|
+
};
|
|
2175
|
+
await handleTmuxLayout(project, layout, { isShellMode, dryRun }, shellCommands, events, ctx);
|
|
2176
|
+
}
|
|
1668
2177
|
async function processEvent(event, context, ctx) {
|
|
1669
2178
|
const { log } = ctx;
|
|
1670
2179
|
log.debug(`Processing event ${event}`);
|
|
1671
2180
|
const eventHandler = EventRegistry.getEventByName(event);
|
|
1672
|
-
if (eventHandler
|
|
1673
|
-
await eventHandler.processing.processEvent(context);
|
|
1674
|
-
} else {
|
|
2181
|
+
if (!eventHandler) {
|
|
1675
2182
|
log.debug(`No event handler found for: ${event}`);
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
try {
|
|
2186
|
+
await eventHandler.processing.processEvent(context);
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
log.error(`Failed to process event '${event}': ${error.message}`);
|
|
2189
|
+
log.debug(`Event error stack: ${error.stack}`);
|
|
1676
2190
|
}
|
|
1677
2191
|
}
|
|
1678
2192
|
async function showProjectHelp(projectName, ctx) {
|
|
@@ -1690,17 +2204,16 @@ Available commands for '${projectName}':`);
|
|
|
1690
2204
|
for (const eventName of configuredEvents) {
|
|
1691
2205
|
const eventHandler = EventRegistry.getEventByName(eventName);
|
|
1692
2206
|
if (eventHandler) {
|
|
1693
|
-
const
|
|
1694
|
-
const config2 = projectConfig.events[eventName];
|
|
2207
|
+
const eventConfig = projectConfig.events[eventName];
|
|
1695
2208
|
let configDesc = "";
|
|
1696
|
-
if (
|
|
1697
|
-
if (typeof
|
|
1698
|
-
configDesc = ` (${JSON.stringify(
|
|
2209
|
+
if (eventConfig !== true && eventConfig !== "true") {
|
|
2210
|
+
if (typeof eventConfig === "object") {
|
|
2211
|
+
configDesc = ` (${JSON.stringify(eventConfig)})`;
|
|
1699
2212
|
} else {
|
|
1700
|
-
configDesc = ` (${
|
|
2213
|
+
configDesc = ` (${eventConfig})`;
|
|
1701
2214
|
}
|
|
1702
2215
|
}
|
|
1703
|
-
console.log(` ${eventName.padEnd(8)} - ${metadata.description}${configDesc}`);
|
|
2216
|
+
console.log(` ${eventName.padEnd(8)} - ${eventHandler.metadata.description}${configDesc}`);
|
|
1704
2217
|
}
|
|
1705
2218
|
}
|
|
1706
2219
|
console.log("\nUsage examples:");
|
|
@@ -1806,25 +2319,17 @@ function createConfigCommand(ctx) {
|
|
|
1806
2319
|
}
|
|
1807
2320
|
|
|
1808
2321
|
// src/commands/manage.ts
|
|
2322
|
+
init_registry();
|
|
2323
|
+
init_constants();
|
|
1809
2324
|
import { Command as Command6 } from "commander";
|
|
1810
|
-
import { select as select2, input as input5, confirm as
|
|
2325
|
+
import { select as select2, input as input5, confirm as confirm4, checkbox as checkbox2 } from "@inquirer/prompts";
|
|
1811
2326
|
import File5 from "phylo";
|
|
1812
|
-
var IDE_CHOICES2 = [
|
|
1813
|
-
{ name: "Visual Studio Code", value: "vscode" },
|
|
1814
|
-
{ name: "Visual Studio Code (code)", value: "code" },
|
|
1815
|
-
{ name: "IntelliJ IDEA", value: "idea" },
|
|
1816
|
-
{ name: "Atom", value: "atom" },
|
|
1817
|
-
{ name: "Sublime Text", value: "subl" },
|
|
1818
|
-
{ name: "Vim", value: "vim" },
|
|
1819
|
-
{ name: "Emacs", value: "emacs" }
|
|
1820
|
-
];
|
|
1821
2327
|
function createManageCommand(ctx) {
|
|
1822
2328
|
const { log } = ctx;
|
|
1823
2329
|
return new Command6("manage").description("Interactive project management").option("-d, --debug", "Enable debug logging").action(async (options) => {
|
|
1824
2330
|
if (options.debug) {
|
|
1825
2331
|
log.setLogLevel("debug");
|
|
1826
2332
|
}
|
|
1827
|
-
await EventRegistry.initialize();
|
|
1828
2333
|
await mainMenu(ctx);
|
|
1829
2334
|
});
|
|
1830
2335
|
}
|
|
@@ -1906,7 +2411,7 @@ async function createProject(ctx) {
|
|
|
1906
2411
|
}
|
|
1907
2412
|
const ide = await select2({
|
|
1908
2413
|
message: "Select IDE:",
|
|
1909
|
-
choices:
|
|
2414
|
+
choices: IDE_CHOICES
|
|
1910
2415
|
});
|
|
1911
2416
|
const homepage = await input5({
|
|
1912
2417
|
message: "Project homepage URL (optional):",
|
|
@@ -1939,7 +2444,7 @@ async function createProject(ctx) {
|
|
|
1939
2444
|
}
|
|
1940
2445
|
console.log("\nProject configuration:");
|
|
1941
2446
|
console.log(JSON.stringify(projectConfig, null, 2));
|
|
1942
|
-
const confirmed = await
|
|
2447
|
+
const confirmed = await confirm4({
|
|
1943
2448
|
message: "Save this project?",
|
|
1944
2449
|
default: true
|
|
1945
2450
|
});
|
|
@@ -1983,14 +2488,14 @@ async function editProject(ctx) {
|
|
|
1983
2488
|
}
|
|
1984
2489
|
const ide = await select2({
|
|
1985
2490
|
message: "Select IDE:",
|
|
1986
|
-
choices:
|
|
2491
|
+
choices: IDE_CHOICES,
|
|
1987
2492
|
default: project.ide || "vscode"
|
|
1988
2493
|
});
|
|
1989
2494
|
const homepage = await input5({
|
|
1990
2495
|
message: "Project homepage URL:",
|
|
1991
2496
|
default: project.homepage || ""
|
|
1992
2497
|
});
|
|
1993
|
-
const keepEvents = await
|
|
2498
|
+
const keepEvents = await confirm4({
|
|
1994
2499
|
message: "Keep existing event configuration?",
|
|
1995
2500
|
default: true
|
|
1996
2501
|
});
|
|
@@ -2029,7 +2534,7 @@ async function editProject(ctx) {
|
|
|
2029
2534
|
}
|
|
2030
2535
|
console.log("\nUpdated configuration:");
|
|
2031
2536
|
console.log(JSON.stringify(updatedConfig, null, 2));
|
|
2032
|
-
const confirmed = await
|
|
2537
|
+
const confirmed = await confirm4({
|
|
2033
2538
|
message: "Save changes?",
|
|
2034
2539
|
default: true
|
|
2035
2540
|
});
|
|
@@ -2052,7 +2557,7 @@ async function deleteProject(ctx) {
|
|
|
2052
2557
|
message: "Select project to delete:",
|
|
2053
2558
|
choices: projectNames.map((n) => ({ name: n, value: n }))
|
|
2054
2559
|
});
|
|
2055
|
-
const confirmed = await
|
|
2560
|
+
const confirmed = await confirm4({
|
|
2056
2561
|
message: `Are you sure you want to delete '${name}'?`,
|
|
2057
2562
|
default: false
|
|
2058
2563
|
});
|
|
@@ -2086,7 +2591,7 @@ import { Command as Command7 } from "commander";
|
|
|
2086
2591
|
import { existsSync, readFileSync } from "fs";
|
|
2087
2592
|
import { basename, resolve } from "path";
|
|
2088
2593
|
import File6 from "phylo";
|
|
2089
|
-
import { confirm as
|
|
2594
|
+
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
2090
2595
|
function createAddCommand(ctx) {
|
|
2091
2596
|
const { log } = ctx;
|
|
2092
2597
|
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(
|
|
@@ -2130,7 +2635,7 @@ async function addProject(pathArg, options, ctx) {
|
|
|
2130
2635
|
process.exit(1);
|
|
2131
2636
|
}
|
|
2132
2637
|
if (projectName in projects && !options.force) {
|
|
2133
|
-
const overwrite = await
|
|
2638
|
+
const overwrite = await confirm5({
|
|
2134
2639
|
message: `Project '${projectName}' already exists. Overwrite?`,
|
|
2135
2640
|
default: false
|
|
2136
2641
|
});
|
|
@@ -2245,32 +2750,42 @@ function createCli() {
|
|
|
2245
2750
|
config.set("pkg", packageJson);
|
|
2246
2751
|
EnvironmentRecognizer.configure(config, log);
|
|
2247
2752
|
const completion = setupCompletion(config);
|
|
2248
|
-
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) => {
|
|
2753
|
+
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", async (thisCommand) => {
|
|
2249
2754
|
const opts = thisCommand.opts();
|
|
2250
2755
|
if (opts.debug) {
|
|
2251
2756
|
log.setLogLevel("debug");
|
|
2252
2757
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
completion
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2758
|
+
await EventRegistry.initialize();
|
|
2759
|
+
}).action(
|
|
2760
|
+
async (project, options) => {
|
|
2761
|
+
if (options.debug) {
|
|
2762
|
+
log.setLogLevel("debug");
|
|
2763
|
+
}
|
|
2764
|
+
if (options.completion) {
|
|
2765
|
+
log.debug("Setting up command-line completion");
|
|
2766
|
+
completion.setupShellInitFile();
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
if (options.init) {
|
|
2770
|
+
log.debug("Generating shell integration function");
|
|
2771
|
+
outputShellInit(program2);
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
if (project) {
|
|
2775
|
+
const args = ["open", project];
|
|
2776
|
+
if (options.shell) args.push("--shell");
|
|
2777
|
+
if (options.debug) args.push("--debug");
|
|
2778
|
+
await program2.parseAsync(["node", "workon", ...args]);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
const environment = await EnvironmentRecognizer.recognize(File7.cwd());
|
|
2782
|
+
program2.setOptionValue("_environment", environment);
|
|
2783
|
+
program2.setOptionValue("_config", config);
|
|
2784
|
+
program2.setOptionValue("_log", log);
|
|
2785
|
+
const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
2786
|
+
await runInteractive2({ config, log, environment });
|
|
2266
2787
|
}
|
|
2267
|
-
|
|
2268
|
-
program2.setOptionValue("_environment", environment);
|
|
2269
|
-
program2.setOptionValue("_config", config);
|
|
2270
|
-
program2.setOptionValue("_log", log);
|
|
2271
|
-
const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
2272
|
-
await runInteractive2({ config, log, environment });
|
|
2273
|
-
});
|
|
2788
|
+
);
|
|
2274
2789
|
program2.setOptionValue("_config", config);
|
|
2275
2790
|
program2.setOptionValue("_log", log);
|
|
2276
2791
|
program2.addCommand(createOpenCommand({ config, log }));
|