dev-prism 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -11
- package/bin/dev-prism.js +23 -235
- package/dist/chunk-3ATDGV4Y.js +22 -0
- package/dist/chunk-3MSC3CGG.js +78 -0
- package/dist/chunk-3NW2OWIU.js +78 -0
- package/dist/chunk-3TRRZEFR.js +38 -0
- package/dist/chunk-4UNCSJRM.js +70 -0
- package/dist/chunk-63II3EL4.js +98 -0
- package/dist/chunk-6YMQTISJ.js +84 -0
- package/dist/chunk-7YGOMAJG.js +106 -0
- package/dist/chunk-AOM6BONB.js +98 -0
- package/dist/chunk-AW2FJGXA.js +38 -0
- package/dist/chunk-C4WLIOBR.js +67 -0
- package/dist/chunk-D6QWWXZD.js +49 -0
- package/dist/chunk-FKTFCSU7.js +78 -0
- package/dist/chunk-GKXXK2ZH.js +203 -0
- package/dist/chunk-H4HPDIY3.js +95 -0
- package/dist/chunk-HCCZKLC4.js +64 -0
- package/dist/chunk-HZUN6NRB.js +70 -0
- package/dist/chunk-J36LRUXM.js +60 -0
- package/dist/chunk-JHR4WADC.js +200 -0
- package/dist/chunk-JIU574KX.js +41 -0
- package/dist/chunk-KZJE62TK.js +203 -0
- package/dist/chunk-LNIOSGC4.js +78 -0
- package/dist/chunk-LOVO4P3Y.js +41 -0
- package/dist/chunk-NNVP5F6I.js +77 -0
- package/dist/chunk-OL25TBYX.js +67 -0
- package/dist/chunk-P3ETW2KK.js +166 -0
- package/dist/chunk-QUMZI5KK.js +98 -0
- package/dist/chunk-RC2AUYZ7.js +49 -0
- package/dist/chunk-SSQ7XBY2.js +30 -0
- package/dist/chunk-SUMJLXT7.js +30 -0
- package/dist/chunk-UHI2QJFI.js +200 -0
- package/dist/chunk-X5A6H4Q7.js +70 -0
- package/dist/chunk-X6FHBEAS.js +200 -0
- package/dist/chunk-Y3GR6XK7.js +71 -0
- package/dist/commands/claude.js +3 -102
- package/dist/commands/create.js +6 -5
- package/dist/commands/destroy.js +4 -3
- package/dist/commands/info.d.ts +3 -0
- package/dist/commands/info.js +7 -0
- package/dist/commands/list.d.ts +3 -1
- package/dist/commands/list.js +4 -4
- package/dist/commands/logs.d.ts +8 -0
- package/dist/commands/logs.js +8 -0
- package/dist/commands/prune.d.ts +6 -0
- package/dist/commands/prune.js +10 -0
- package/dist/commands/start.d.ts +7 -0
- package/dist/commands/start.js +9 -0
- package/dist/commands/stop-all.d.ts +3 -0
- package/dist/commands/stop-all.js +9 -0
- package/dist/commands/stop.d.ts +3 -0
- package/dist/commands/stop.js +7 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +60 -10
- package/dist/lib/env.d.ts +2 -1
- package/dist/lib/env.js +3 -1
- package/dist/lib/ports.js +1 -1
- package/dist/lib/store.d.ts +46 -0
- package/dist/lib/store.js +6 -0
- package/dist/lib/worktree.d.ts +2 -12
- package/dist/lib/worktree.js +1 -5
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -17,10 +17,13 @@ All Docker configuration lives in `docker-compose.session.yml` in your project -
|
|
|
17
17
|
|
|
18
18
|
## Features
|
|
19
19
|
|
|
20
|
-
- **Git worktrees** for isolated working directories
|
|
20
|
+
- **Git worktrees** for isolated working directories (or in-place mode)
|
|
21
21
|
- **Docker Compose** handles all container orchestration
|
|
22
|
-
- **Unique ports** per session (calculated from session ID)
|
|
22
|
+
- **Unique ports** per session (calculated from session ID, displayed as clickable URLs)
|
|
23
|
+
- **Auto-assign** session IDs or choose your own
|
|
24
|
+
- **SQLite tracking** — all sessions stored in a local database
|
|
23
25
|
- **Two modes**: Docker (apps in containers) or Native (apps run locally)
|
|
26
|
+
- **Claude Code integration** built-in (`dev-prism claude`)
|
|
24
27
|
- **Portable**: Works with any project
|
|
25
28
|
|
|
26
29
|
## Installation
|
|
@@ -36,17 +39,27 @@ pnpm add -D dev-prism
|
|
|
36
39
|
### Create a session
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
#
|
|
42
|
+
# Auto-assign session ID
|
|
43
|
+
dev-prism create
|
|
44
|
+
|
|
45
|
+
# Explicit session ID
|
|
40
46
|
dev-prism create 001
|
|
41
47
|
|
|
48
|
+
# Custom branch name
|
|
49
|
+
dev-prism create --branch feature/my-feature
|
|
50
|
+
|
|
42
51
|
# Native mode - only infrastructure in Docker, apps run via pnpm dev
|
|
43
|
-
dev-prism create
|
|
52
|
+
dev-prism create --mode native
|
|
53
|
+
|
|
54
|
+
# Exclude specific apps from Docker
|
|
55
|
+
dev-prism create --without web,widget
|
|
44
56
|
|
|
45
57
|
# In-place mode - use current directory instead of creating worktree
|
|
46
|
-
dev-prism create
|
|
47
|
-
```
|
|
58
|
+
dev-prism create --in-place
|
|
48
59
|
|
|
49
|
-
|
|
60
|
+
# Stream logs after creation instead of detaching
|
|
61
|
+
dev-prism create --no-detach
|
|
62
|
+
```
|
|
50
63
|
|
|
51
64
|
### List sessions
|
|
52
65
|
|
|
@@ -74,11 +87,20 @@ dev-prism stop-all # Stop all sessions
|
|
|
74
87
|
dev-prism logs 001
|
|
75
88
|
```
|
|
76
89
|
|
|
77
|
-
###
|
|
90
|
+
### Cleanup
|
|
78
91
|
|
|
79
92
|
```bash
|
|
80
93
|
dev-prism destroy 001 # Destroy specific session
|
|
81
94
|
dev-prism destroy --all # Destroy all sessions
|
|
95
|
+
dev-prism prune # Remove all stopped sessions
|
|
96
|
+
dev-prism prune -y # Skip confirmation
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Claude Code integration
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
dev-prism claude # Install Claude Code skill + CLAUDE.md
|
|
103
|
+
dev-prism claude --force # Overwrite existing files
|
|
82
104
|
```
|
|
83
105
|
|
|
84
106
|
## Port Allocation
|
|
@@ -171,9 +193,11 @@ services:
|
|
|
171
193
|
|
|
172
194
|
## How It Works
|
|
173
195
|
|
|
174
|
-
1. **Create session**: `dev-prism create
|
|
175
|
-
-
|
|
196
|
+
1. **Create session**: `dev-prism create`
|
|
197
|
+
- Auto-assigns next available session ID (or use explicit ID)
|
|
198
|
+
- Creates git worktree at `../project-sessions/session-001` (or uses current dir with `--in-place`)
|
|
176
199
|
- Generates `.env.session` with calculated ports
|
|
200
|
+
- Records session in local SQLite database
|
|
177
201
|
- Runs `docker compose --env-file .env.session up -d`
|
|
178
202
|
- Runs setup commands
|
|
179
203
|
|
|
@@ -182,6 +206,8 @@ services:
|
|
|
182
206
|
3. **Docker mode** (`--profile apps`): All services including apps run in containers
|
|
183
207
|
4. **Native mode**: Only infrastructure runs; apps use `pnpm dev` with `.env.session`
|
|
184
208
|
|
|
209
|
+
All session state is tracked in a SQLite database (`~/.dev-prism/sessions.db`), making both worktree and in-place sessions first-class citizens across all commands.
|
|
210
|
+
|
|
185
211
|
## Generated Files
|
|
186
212
|
|
|
187
213
|
```
|
|
@@ -207,4 +233,4 @@ To use in another project:
|
|
|
207
233
|
1. Install: `pnpm add -D dev-prism`
|
|
208
234
|
2. Create `session.config.mjs` with port offsets
|
|
209
235
|
3. Create `docker-compose.session.yml` with `${VAR}` placeholders
|
|
210
|
-
4. Run `dev-prism create
|
|
236
|
+
4. Run `dev-prism create`
|
package/bin/dev-prism.js
CHANGED
|
@@ -5,13 +5,19 @@ import { createSession } from '../dist/commands/create.js';
|
|
|
5
5
|
import { destroySession } from '../dist/commands/destroy.js';
|
|
6
6
|
import { listSessions } from '../dist/commands/list.js';
|
|
7
7
|
import { installClaude } from '../dist/commands/claude.js';
|
|
8
|
+
import { showInfo } from '../dist/commands/info.js';
|
|
9
|
+
import { startSession } from '../dist/commands/start.js';
|
|
10
|
+
import { stopSession } from '../dist/commands/stop.js';
|
|
11
|
+
import { stopAllSessions } from '../dist/commands/stop-all.js';
|
|
12
|
+
import { pruneSessions } from '../dist/commands/prune.js';
|
|
13
|
+
import { streamLogs } from '../dist/commands/logs.js';
|
|
8
14
|
|
|
9
15
|
const program = new Command();
|
|
10
16
|
|
|
11
17
|
program
|
|
12
18
|
.name('dev-prism')
|
|
13
19
|
.description('CLI tool for managing isolated parallel development sessions')
|
|
14
|
-
.version('0.
|
|
20
|
+
.version('0.4.0');
|
|
15
21
|
|
|
16
22
|
program
|
|
17
23
|
.command('create [sessionId]')
|
|
@@ -44,61 +50,17 @@ program
|
|
|
44
50
|
program
|
|
45
51
|
.command('list')
|
|
46
52
|
.description('List all active development sessions')
|
|
47
|
-
.
|
|
53
|
+
.option('-a, --all', 'Show sessions across all projects')
|
|
54
|
+
.action(async (options) => {
|
|
48
55
|
const projectRoot = process.cwd();
|
|
49
|
-
await listSessions(projectRoot);
|
|
56
|
+
await listSessions(projectRoot, { all: options.all });
|
|
50
57
|
});
|
|
51
58
|
|
|
52
59
|
program
|
|
53
60
|
.command('info')
|
|
54
61
|
.description('Show session info for current directory (useful for --in-place sessions)')
|
|
55
62
|
.action(async () => {
|
|
56
|
-
|
|
57
|
-
const chalk = (await import('chalk')).default;
|
|
58
|
-
const { existsSync, readFileSync } = await import('node:fs');
|
|
59
|
-
const { resolve } = await import('node:path');
|
|
60
|
-
const docker = await import('../dist/lib/docker.js');
|
|
61
|
-
|
|
62
|
-
const envFile = resolve(cwd, '.env.session');
|
|
63
|
-
if (!existsSync(envFile)) {
|
|
64
|
-
console.log(chalk.yellow('No .env.session found in current directory.'));
|
|
65
|
-
console.log(chalk.gray('Run `dev-prism create --in-place` to create a session here.'));
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Parse .env.session
|
|
70
|
-
const envContent = readFileSync(envFile, 'utf-8');
|
|
71
|
-
const env = {};
|
|
72
|
-
for (const line of envContent.split('\n')) {
|
|
73
|
-
const match = line.match(/^([^=]+)=(.*)$/);
|
|
74
|
-
if (match) {
|
|
75
|
-
env[match[1]] = match[2];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const sessionId = env.SESSION_ID || 'unknown';
|
|
80
|
-
const running = await docker.isRunning({ cwd });
|
|
81
|
-
|
|
82
|
-
console.log(chalk.blue(`\nSession ${sessionId}`));
|
|
83
|
-
console.log(chalk.gray(`Directory: ${cwd}`));
|
|
84
|
-
console.log(running ? chalk.green('Status: running') : chalk.yellow('Status: stopped'));
|
|
85
|
-
|
|
86
|
-
console.log(chalk.gray('\nPorts:'));
|
|
87
|
-
for (const [key, value] of Object.entries(env)) {
|
|
88
|
-
if (key.includes('PORT')) {
|
|
89
|
-
console.log(chalk.gray(` ${key}: ${value}`));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log(chalk.gray('\nURLs:'));
|
|
94
|
-
for (const [key, value] of Object.entries(env)) {
|
|
95
|
-
if (key.includes('APP') || key.includes('WEB') || key.includes('WIDGET')) {
|
|
96
|
-
if (key.includes('PORT')) {
|
|
97
|
-
console.log(chalk.cyan(` ${key.replace('_PORT', '')}: http://localhost:${value}`));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
console.log('');
|
|
63
|
+
await showInfo(process.cwd());
|
|
102
64
|
});
|
|
103
65
|
|
|
104
66
|
program
|
|
@@ -108,19 +70,10 @@ program
|
|
|
108
70
|
.option('-W, --without <apps>', 'Exclude apps (comma-separated: app,web,widget)', (val) => val.split(','))
|
|
109
71
|
.action(async (sessionId, options) => {
|
|
110
72
|
const projectRoot = process.cwd();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const sessionDir = getSessionDir(config, projectRoot, sessionId);
|
|
116
|
-
let profiles;
|
|
117
|
-
if (options.mode === 'docker') {
|
|
118
|
-
const allApps = config.apps ?? [];
|
|
119
|
-
const excludeApps = options.without ?? [];
|
|
120
|
-
profiles = allApps.filter((app) => !excludeApps.includes(app));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await docker.up({ cwd: sessionDir, profiles });
|
|
73
|
+
await startSession(projectRoot, sessionId, {
|
|
74
|
+
mode: options.mode,
|
|
75
|
+
without: options.without,
|
|
76
|
+
});
|
|
124
77
|
});
|
|
125
78
|
|
|
126
79
|
program
|
|
@@ -128,18 +81,7 @@ program
|
|
|
128
81
|
.description('Stop Docker services for a session (without destroying)')
|
|
129
82
|
.action(async (sessionId) => {
|
|
130
83
|
const projectRoot = process.cwd();
|
|
131
|
-
|
|
132
|
-
const { execa } = await import('execa');
|
|
133
|
-
|
|
134
|
-
const config = await loadConfig(projectRoot);
|
|
135
|
-
const sessionDir = getSessionDir(config, projectRoot, sessionId);
|
|
136
|
-
|
|
137
|
-
// Use stop instead of down to preserve volumes
|
|
138
|
-
await execa(
|
|
139
|
-
'docker',
|
|
140
|
-
['compose', '-f', 'docker-compose.session.yml', '--env-file', '.env.session', 'stop'],
|
|
141
|
-
{ cwd: sessionDir, stdio: 'inherit' }
|
|
142
|
-
);
|
|
84
|
+
await stopSession(projectRoot, sessionId);
|
|
143
85
|
});
|
|
144
86
|
|
|
145
87
|
program
|
|
@@ -150,30 +92,11 @@ program
|
|
|
150
92
|
.option('-n, --tail <lines>', 'Number of lines to show from the end', '50')
|
|
151
93
|
.action(async (sessionId, options) => {
|
|
152
94
|
const projectRoot = process.cwd();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
let profileFlags = [];
|
|
159
|
-
if (options.mode === 'docker') {
|
|
160
|
-
const allApps = config.apps ?? [];
|
|
161
|
-
const excludeApps = options.without ?? [];
|
|
162
|
-
const profiles = allApps.filter((app) => !excludeApps.includes(app));
|
|
163
|
-
profileFlags = profiles.flatMap((p) => ['--profile', p]);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const args = [
|
|
167
|
-
'compose',
|
|
168
|
-
'-f', 'docker-compose.session.yml',
|
|
169
|
-
'--env-file', '.env.session',
|
|
170
|
-
...profileFlags,
|
|
171
|
-
'logs',
|
|
172
|
-
'-f',
|
|
173
|
-
'--tail', options.tail,
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
await execa('docker', args, { cwd: sessionDir, stdio: 'inherit' });
|
|
95
|
+
await streamLogs(projectRoot, sessionId, {
|
|
96
|
+
mode: options.mode,
|
|
97
|
+
without: options.without,
|
|
98
|
+
tail: options.tail,
|
|
99
|
+
});
|
|
177
100
|
});
|
|
178
101
|
|
|
179
102
|
program
|
|
@@ -181,62 +104,7 @@ program
|
|
|
181
104
|
.description('Stop all running sessions (preserves data)')
|
|
182
105
|
.action(async () => {
|
|
183
106
|
const projectRoot = process.cwd();
|
|
184
|
-
|
|
185
|
-
const { loadConfig, getSessionDir } = await import('../dist/lib/config.js');
|
|
186
|
-
const { getSessionWorktrees } = await import('../dist/lib/worktree.js');
|
|
187
|
-
const docker = await import('../dist/lib/docker.js');
|
|
188
|
-
const { existsSync } = await import('node:fs');
|
|
189
|
-
const { resolve } = await import('node:path');
|
|
190
|
-
|
|
191
|
-
const config = await loadConfig(projectRoot);
|
|
192
|
-
const sessions = await getSessionWorktrees(projectRoot);
|
|
193
|
-
|
|
194
|
-
if (sessions.length === 0) {
|
|
195
|
-
console.log(chalk.gray('No sessions found.'));
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Find running sessions
|
|
200
|
-
const runningSessions = [];
|
|
201
|
-
for (const session of sessions) {
|
|
202
|
-
const envFile = resolve(session.path, '.env.session');
|
|
203
|
-
if (existsSync(envFile)) {
|
|
204
|
-
const running = await docker.isRunning({ cwd: session.path });
|
|
205
|
-
if (running) {
|
|
206
|
-
runningSessions.push(session);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (runningSessions.length === 0) {
|
|
212
|
-
console.log(chalk.gray('No running sessions found.'));
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
console.log(chalk.blue(`Stopping ${runningSessions.length} running session(s)...\n`));
|
|
217
|
-
|
|
218
|
-
// Get all app profiles and service names to ensure we stop everything
|
|
219
|
-
const allApps = config.apps ?? [];
|
|
220
|
-
const profileFlags = allApps.flatMap((p) => ['--profile', p]);
|
|
221
|
-
// Explicitly list all services to stop (infrastructure + apps)
|
|
222
|
-
const allServices = ['postgres', 'mailpit', ...allApps];
|
|
223
|
-
|
|
224
|
-
const { execa } = await import('execa');
|
|
225
|
-
for (const session of runningSessions) {
|
|
226
|
-
console.log(chalk.gray(` Stopping session ${session.sessionId}...`));
|
|
227
|
-
try {
|
|
228
|
-
await execa(
|
|
229
|
-
'docker',
|
|
230
|
-
['compose', '-f', 'docker-compose.session.yml', '--env-file', '.env.session', ...profileFlags, 'stop', ...allServices],
|
|
231
|
-
{ cwd: session.path, stdio: 'pipe' }
|
|
232
|
-
);
|
|
233
|
-
console.log(chalk.green(` Session ${session.sessionId} stopped.`));
|
|
234
|
-
} catch (error) {
|
|
235
|
-
console.log(chalk.yellow(` Warning: Could not stop session ${session.sessionId}`));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
console.log(chalk.green(`\nStopped ${runningSessions.length} session(s).`));
|
|
107
|
+
await stopAllSessions(projectRoot);
|
|
240
108
|
});
|
|
241
109
|
|
|
242
110
|
program
|
|
@@ -245,87 +113,7 @@ program
|
|
|
245
113
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
246
114
|
.action(async (options) => {
|
|
247
115
|
const projectRoot = process.cwd();
|
|
248
|
-
|
|
249
|
-
const { loadConfig } = await import('../dist/lib/config.js');
|
|
250
|
-
const { getSessionWorktrees, removeWorktree } = await import('../dist/lib/worktree.js');
|
|
251
|
-
const docker = await import('../dist/lib/docker.js');
|
|
252
|
-
const { existsSync } = await import('node:fs');
|
|
253
|
-
const { resolve } = await import('node:path');
|
|
254
|
-
const readline = await import('node:readline');
|
|
255
|
-
|
|
256
|
-
const config = await loadConfig(projectRoot);
|
|
257
|
-
const sessions = await getSessionWorktrees(projectRoot);
|
|
258
|
-
|
|
259
|
-
if (sessions.length === 0) {
|
|
260
|
-
console.log(chalk.gray('No sessions found.'));
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Find stopped sessions
|
|
265
|
-
const stoppedSessions = [];
|
|
266
|
-
for (const session of sessions) {
|
|
267
|
-
const envFile = resolve(session.path, '.env.session');
|
|
268
|
-
let running = false;
|
|
269
|
-
if (existsSync(envFile)) {
|
|
270
|
-
running = await docker.isRunning({ cwd: session.path });
|
|
271
|
-
}
|
|
272
|
-
if (!running) {
|
|
273
|
-
stoppedSessions.push(session);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (stoppedSessions.length === 0) {
|
|
278
|
-
console.log(chalk.gray('No stopped sessions to prune.'));
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
console.log(chalk.yellow(`\nFound ${stoppedSessions.length} stopped session(s) to prune:`));
|
|
283
|
-
for (const session of stoppedSessions) {
|
|
284
|
-
console.log(chalk.gray(` - Session ${session.sessionId} (${session.branch})`));
|
|
285
|
-
}
|
|
286
|
-
console.log('');
|
|
287
|
-
|
|
288
|
-
// Confirm unless --yes flag provided
|
|
289
|
-
if (!options.yes) {
|
|
290
|
-
const rl = readline.createInterface({
|
|
291
|
-
input: process.stdin,
|
|
292
|
-
output: process.stdout,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
const answer = await new Promise((resolve) => {
|
|
296
|
-
rl.question(chalk.red('Are you sure you want to delete these sessions? This cannot be undone. [y/N] '), resolve);
|
|
297
|
-
});
|
|
298
|
-
rl.close();
|
|
299
|
-
|
|
300
|
-
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
301
|
-
console.log(chalk.gray('Cancelled.'));
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
console.log(chalk.blue('\nPruning stopped sessions...\n'));
|
|
307
|
-
|
|
308
|
-
for (const session of stoppedSessions) {
|
|
309
|
-
console.log(chalk.gray(` Removing session ${session.sessionId}...`));
|
|
310
|
-
try {
|
|
311
|
-
// Clean up any docker resources
|
|
312
|
-
const envFile = resolve(session.path, '.env.session');
|
|
313
|
-
if (existsSync(envFile)) {
|
|
314
|
-
try {
|
|
315
|
-
await docker.down({ cwd: session.path });
|
|
316
|
-
} catch {
|
|
317
|
-
// Ignore errors - containers might already be removed
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// Remove worktree and branch
|
|
321
|
-
await removeWorktree(projectRoot, session.path, session.branch);
|
|
322
|
-
console.log(chalk.green(` Session ${session.sessionId} removed.`));
|
|
323
|
-
} catch (error) {
|
|
324
|
-
console.log(chalk.yellow(` Warning: Could not fully remove session ${session.sessionId}`));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
console.log(chalk.green(`\nPruned ${stoppedSessions.length} session(s).`));
|
|
116
|
+
await pruneSessions(projectRoot, { yes: options.yes });
|
|
329
117
|
});
|
|
330
118
|
|
|
331
119
|
program
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/lib/ports.ts
|
|
2
|
+
function calculatePorts(config, sessionId) {
|
|
3
|
+
const sessionNum = parseInt(sessionId, 10);
|
|
4
|
+
const basePort = config.portBase + sessionNum * 100;
|
|
5
|
+
const ports = {};
|
|
6
|
+
for (const [name, offset] of Object.entries(config.ports)) {
|
|
7
|
+
ports[name] = basePort + offset;
|
|
8
|
+
}
|
|
9
|
+
return ports;
|
|
10
|
+
}
|
|
11
|
+
function formatPortsTable(ports) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
for (const [name, port] of Object.entries(ports)) {
|
|
14
|
+
lines.push(` ${name}: http://localhost:${port}`);
|
|
15
|
+
}
|
|
16
|
+
return lines.join("\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
calculatePorts,
|
|
21
|
+
formatPortsTable
|
|
22
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
removeWorktree
|
|
3
|
+
} from "./chunk-Y3GR6XK7.js";
|
|
4
|
+
import {
|
|
5
|
+
down
|
|
6
|
+
} from "./chunk-GBN67HYD.js";
|
|
7
|
+
import {
|
|
8
|
+
loadConfig
|
|
9
|
+
} from "./chunk-25WQHUYW.js";
|
|
10
|
+
import {
|
|
11
|
+
SessionStore
|
|
12
|
+
} from "./chunk-6YMQTISJ.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/destroy.ts
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
import { resolve } from "path";
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
async function destroySession(projectRoot, sessionId, options) {
|
|
19
|
+
const config = await loadConfig(projectRoot);
|
|
20
|
+
const store = new SessionStore();
|
|
21
|
+
try {
|
|
22
|
+
if (options.all) {
|
|
23
|
+
console.log(chalk.blue("Destroying all sessions..."));
|
|
24
|
+
const sessions = store.listByProject(projectRoot);
|
|
25
|
+
if (sessions.length === 0) {
|
|
26
|
+
console.log(chalk.gray("No sessions found."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const session2 of sessions) {
|
|
30
|
+
await destroySingleSession(projectRoot, session2.session_id, session2.session_dir, session2.branch, session2.in_place === 1);
|
|
31
|
+
store.markDestroyed(projectRoot, session2.session_id);
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk.green(`
|
|
34
|
+
Destroyed ${sessions.length} session(s).`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!sessionId) {
|
|
38
|
+
console.error(chalk.red("Error: Session ID required. Use --all to destroy all sessions."));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (!/^\d{3}$/.test(sessionId)) {
|
|
42
|
+
console.error(chalk.red("Error: Session ID must be exactly 3 digits (001-999)"));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const session = store.findSession(projectRoot, sessionId);
|
|
46
|
+
if (!session) {
|
|
47
|
+
console.error(chalk.red(`Error: Session ${sessionId} not found.`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
await destroySingleSession(projectRoot, sessionId, session.session_dir, session.branch, session.in_place === 1);
|
|
51
|
+
store.markDestroyed(projectRoot, sessionId);
|
|
52
|
+
console.log(chalk.green(`
|
|
53
|
+
Session ${sessionId} destroyed.`));
|
|
54
|
+
} finally {
|
|
55
|
+
store.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function destroySingleSession(projectRoot, sessionId, sessionDir, branchName, inPlace) {
|
|
59
|
+
console.log(chalk.blue(`
|
|
60
|
+
Destroying session ${sessionId}...`));
|
|
61
|
+
const envFile = resolve(sessionDir, ".env.session");
|
|
62
|
+
if (existsSync(envFile)) {
|
|
63
|
+
console.log(chalk.gray(" Stopping Docker containers..."));
|
|
64
|
+
try {
|
|
65
|
+
await down({ cwd: sessionDir });
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!inPlace) {
|
|
70
|
+
console.log(chalk.gray(" Removing git worktree..."));
|
|
71
|
+
await removeWorktree(projectRoot, sessionDir, branchName);
|
|
72
|
+
}
|
|
73
|
+
console.log(chalk.green(` Session ${sessionId} destroyed.`));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
destroySession
|
|
78
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
removeWorktree
|
|
3
|
+
} from "./chunk-Y3GR6XK7.js";
|
|
4
|
+
import {
|
|
5
|
+
down
|
|
6
|
+
} from "./chunk-GBN67HYD.js";
|
|
7
|
+
import {
|
|
8
|
+
loadConfig
|
|
9
|
+
} from "./chunk-25WQHUYW.js";
|
|
10
|
+
import {
|
|
11
|
+
SessionStore
|
|
12
|
+
} from "./chunk-H4HPDIY3.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/destroy.ts
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
import { resolve } from "path";
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
async function destroySession(projectRoot, sessionId, options) {
|
|
19
|
+
const config = await loadConfig(projectRoot);
|
|
20
|
+
const store = new SessionStore();
|
|
21
|
+
try {
|
|
22
|
+
if (options.all) {
|
|
23
|
+
console.log(chalk.blue("Destroying all sessions..."));
|
|
24
|
+
const sessions = store.listByProject(projectRoot);
|
|
25
|
+
if (sessions.length === 0) {
|
|
26
|
+
console.log(chalk.gray("No sessions found."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const session2 of sessions) {
|
|
30
|
+
await destroySingleSession(projectRoot, session2.session_id, session2.session_dir, session2.branch, session2.in_place === 1);
|
|
31
|
+
store.markDestroyed(projectRoot, session2.session_id);
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk.green(`
|
|
34
|
+
Destroyed ${sessions.length} session(s).`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!sessionId) {
|
|
38
|
+
console.error(chalk.red("Error: Session ID required. Use --all to destroy all sessions."));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (!/^\d{3}$/.test(sessionId)) {
|
|
42
|
+
console.error(chalk.red("Error: Session ID must be exactly 3 digits (001-999)"));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const session = store.findSession(projectRoot, sessionId);
|
|
46
|
+
if (!session) {
|
|
47
|
+
console.error(chalk.red(`Error: Session ${sessionId} not found.`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
await destroySingleSession(projectRoot, sessionId, session.session_dir, session.branch, session.in_place === 1);
|
|
51
|
+
store.markDestroyed(projectRoot, sessionId);
|
|
52
|
+
console.log(chalk.green(`
|
|
53
|
+
Session ${sessionId} destroyed.`));
|
|
54
|
+
} finally {
|
|
55
|
+
store.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function destroySingleSession(projectRoot, sessionId, sessionDir, branchName, inPlace) {
|
|
59
|
+
console.log(chalk.blue(`
|
|
60
|
+
Destroying session ${sessionId}...`));
|
|
61
|
+
const envFile = resolve(sessionDir, ".env.session");
|
|
62
|
+
if (existsSync(envFile)) {
|
|
63
|
+
console.log(chalk.gray(" Stopping Docker containers..."));
|
|
64
|
+
try {
|
|
65
|
+
await down({ cwd: sessionDir });
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!inPlace) {
|
|
70
|
+
console.log(chalk.gray(" Removing git worktree..."));
|
|
71
|
+
await removeWorktree(projectRoot, sessionDir, branchName);
|
|
72
|
+
}
|
|
73
|
+
console.log(chalk.green(` Session ${sessionId} destroyed.`));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
destroySession
|
|
78
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
up
|
|
3
|
+
} from "./chunk-GBN67HYD.js";
|
|
4
|
+
import {
|
|
5
|
+
loadConfig
|
|
6
|
+
} from "./chunk-25WQHUYW.js";
|
|
7
|
+
import {
|
|
8
|
+
SessionStore
|
|
9
|
+
} from "./chunk-H4HPDIY3.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/start.ts
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
async function startSession(projectRoot, sessionId, options) {
|
|
14
|
+
const config = await loadConfig(projectRoot);
|
|
15
|
+
const store = new SessionStore();
|
|
16
|
+
let sessionDir;
|
|
17
|
+
try {
|
|
18
|
+
const session = store.findSession(projectRoot, sessionId);
|
|
19
|
+
if (!session) {
|
|
20
|
+
console.error(chalk.red(`Error: Session ${sessionId} not found.`));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
sessionDir = session.session_dir;
|
|
24
|
+
} finally {
|
|
25
|
+
store.close();
|
|
26
|
+
}
|
|
27
|
+
let profiles;
|
|
28
|
+
if (options.mode === "docker") {
|
|
29
|
+
const allApps = config.apps ?? [];
|
|
30
|
+
const excludeApps = options.without ?? [];
|
|
31
|
+
profiles = allApps.filter((app) => !excludeApps.includes(app));
|
|
32
|
+
}
|
|
33
|
+
await up({ cwd: sessionDir, profiles });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
startSession
|
|
38
|
+
};
|