dev-prism 0.3.0 → 0.6.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 +262 -70
- package/bin/dev-prism.js +45 -42
- package/dist/chunk-24OM3LGM.js +35 -0
- package/dist/chunk-3FYEGH2G.js +217 -0
- package/dist/chunk-3NW2OWIU.js +78 -0
- package/dist/chunk-3Q454U3I.js +71 -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-76LSQIZI.js +31 -0
- package/dist/chunk-7OSTLJLO.js +219 -0
- package/dist/chunk-7YEZWM6Y.js +97 -0
- package/dist/chunk-AEVARZQ4.js +203 -0
- package/dist/chunk-AIVPJ467.js +70 -0
- package/dist/chunk-AOM6BONB.js +98 -0
- package/dist/chunk-BXYXWNGH.js +30 -0
- package/dist/chunk-CUQVGZBX.js +44 -0
- package/dist/chunk-DQER5GNG.js +72 -0
- package/dist/chunk-DTE5YQMI.js +41 -0
- package/dist/chunk-EXRHG5KQ.js +60 -0
- package/dist/chunk-FKTFCSU7.js +78 -0
- package/dist/chunk-GKXXK2ZH.js +203 -0
- package/dist/chunk-H3L73URT.js +65 -0
- package/dist/chunk-H4HPDIY3.js +95 -0
- package/dist/chunk-HZBJF67X.js +60 -0
- package/dist/chunk-I3U6JK77.js +66 -0
- package/dist/chunk-JMKE3ZKI.js +61 -0
- package/dist/chunk-JZ2VPQXP.js +132 -0
- package/dist/chunk-KDYKLH6P.js +40 -0
- package/dist/chunk-KZJE62TK.js +203 -0
- package/dist/chunk-LAOWFCQL.js +21 -0
- package/dist/chunk-LNIOSGC4.js +78 -0
- package/dist/chunk-MRBTH5PL.js +66 -0
- package/dist/chunk-NNVP5F6I.js +77 -0
- package/dist/chunk-OL25TBYX.js +67 -0
- package/dist/chunk-OOJ6YOGS.js +53 -0
- package/dist/chunk-OPEZFBBI.js +219 -0
- package/dist/chunk-Q5DPX4WL.js +219 -0
- package/dist/chunk-RC2AUYZ7.js +49 -0
- package/dist/chunk-RQ245R7T.js +67 -0
- package/dist/chunk-SD3TON6N.js +32 -0
- package/dist/chunk-SEKH4ZV6.js +60 -0
- package/dist/chunk-SUMJLXT7.js +30 -0
- package/dist/chunk-UKYQN4A3.js +38 -0
- package/dist/chunk-URGGS3XM.js +95 -0
- package/dist/chunk-VUNPVDSO.js +74 -0
- package/dist/chunk-VXP2SPRI.js +51 -0
- package/dist/chunk-W54CPPSK.js +217 -0
- package/dist/chunk-X2PXZRYU.js +41 -0
- package/dist/chunk-X6FHBEAS.js +200 -0
- package/dist/chunk-YSO3IDZZ.js +40 -0
- package/dist/chunk-YY5DA35Z.js +40 -0
- package/dist/chunk-Z2ISJMLW.js +92 -0
- package/dist/chunk-ZKHNUDSL.js +119 -0
- package/dist/commands/create.d.ts +2 -1
- package/dist/commands/create.js +6 -5
- package/dist/commands/destroy.d.ts +1 -1
- package/dist/commands/destroy.js +4 -3
- package/dist/commands/info.d.ts +3 -0
- package/dist/commands/info.js +3 -2
- package/dist/commands/list.d.ts +1 -1
- package/dist/commands/list.js +3 -5
- package/dist/commands/logs.d.ts +8 -0
- package/dist/commands/logs.js +3 -2
- package/dist/commands/prune.d.ts +6 -0
- package/dist/commands/prune.js +4 -3
- package/dist/commands/start.d.ts +7 -0
- package/dist/commands/start.js +3 -2
- package/dist/commands/stop-all.d.ts +3 -0
- package/dist/commands/stop-all.js +3 -4
- package/dist/commands/stop.d.ts +3 -0
- package/dist/commands/stop.js +3 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +36 -18
- package/dist/lib/compose.d.ts +12 -0
- package/dist/lib/compose.js +12 -0
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.test.d.ts +1 -0
- package/dist/lib/config.test.js +32 -0
- package/dist/lib/docker-inspect.d.ts +24 -0
- package/dist/lib/docker-inspect.js +16 -0
- package/dist/lib/env.d.ts +5 -4
- package/dist/lib/env.js +1 -1
- package/dist/lib/env.test.d.ts +1 -0
- package/dist/lib/env.test.js +68 -0
- package/dist/lib/ports.d.ts +3 -3
- package/dist/lib/ports.js +3 -3
- package/dist/lib/ports.test.d.ts +1 -0
- package/dist/lib/ports.test.js +61 -0
- package/dist/lib/session.d.ts +16 -0
- package/dist/lib/session.js +13 -0
- package/dist/lib/store.d.ts +46 -0
- package/dist/lib/store.js +1 -1
- package/dist/lib/store.test.d.ts +1 -0
- package/dist/lib/store.test.js +205 -0
- package/dist/lib/worktree.d.ts +3 -13
- package/dist/lib/worktree.js +1 -1
- package/dist/lib/worktree.test.d.ts +1 -0
- package/dist/lib/worktree.test.js +41 -0
- package/package.json +2 -5
package/README.md
CHANGED
|
@@ -8,20 +8,22 @@ A minimal CLI tool for managing isolated parallel development sessions. Enables
|
|
|
8
8
|
|
|
9
9
|
## Philosophy
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Stateless orchestration, Docker as source of truth.** This tool does the bare minimum:
|
|
12
12
|
1. Creates git worktrees for isolated working directories
|
|
13
|
-
2. Generates
|
|
13
|
+
2. Generates `docker-compose.session.yml` with random port bindings
|
|
14
14
|
3. Runs `docker compose` commands
|
|
15
|
+
4. Discovers ports from running containers and writes `.env.session`
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
No database, no state tracking—everything is derived from Docker's reality.
|
|
17
18
|
|
|
18
19
|
## Features
|
|
19
20
|
|
|
21
|
+
- **Stateless** - No database, Docker is the single source of truth
|
|
20
22
|
- **Git worktrees** for isolated working directories (or in-place mode)
|
|
21
23
|
- **Docker Compose** handles all container orchestration
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
24
|
+
- **Random port allocation** via Docker (zero conflicts)
|
|
25
|
+
- **Automatic port discovery** from running containers
|
|
26
|
+
- **Auto-healing** - commands always sync to Docker reality
|
|
25
27
|
- **Two modes**: Docker (apps in containers) or Native (apps run locally)
|
|
26
28
|
- **Claude Code integration** built-in (`dev-prism claude`)
|
|
27
29
|
- **Portable**: Works with any project
|
|
@@ -34,17 +36,36 @@ npm install -g dev-prism
|
|
|
34
36
|
pnpm add -D dev-prism
|
|
35
37
|
```
|
|
36
38
|
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Create a session with worktree
|
|
43
|
+
dev-prism create
|
|
44
|
+
|
|
45
|
+
# Or create in current directory
|
|
46
|
+
dev-prism create --in-place
|
|
47
|
+
|
|
48
|
+
# List active sessions
|
|
49
|
+
dev-prism list
|
|
50
|
+
|
|
51
|
+
# Check current directory status
|
|
52
|
+
dev-prism info
|
|
53
|
+
|
|
54
|
+
# Stop session in current directory
|
|
55
|
+
dev-prism stop
|
|
56
|
+
|
|
57
|
+
# Destroy session in current directory
|
|
58
|
+
dev-prism destroy
|
|
59
|
+
```
|
|
60
|
+
|
|
37
61
|
## Usage
|
|
38
62
|
|
|
39
63
|
### Create a session
|
|
40
64
|
|
|
41
65
|
```bash
|
|
42
|
-
#
|
|
66
|
+
# Create with worktree (generates timestamp-based branch)
|
|
43
67
|
dev-prism create
|
|
44
68
|
|
|
45
|
-
# Explicit session ID
|
|
46
|
-
dev-prism create 001
|
|
47
|
-
|
|
48
69
|
# Custom branch name
|
|
49
70
|
dev-prism create --branch feature/my-feature
|
|
50
71
|
|
|
@@ -67,33 +88,59 @@ dev-prism create --no-detach
|
|
|
67
88
|
dev-prism list
|
|
68
89
|
```
|
|
69
90
|
|
|
70
|
-
|
|
91
|
+
Shows only running sessions with their ports and container counts.
|
|
92
|
+
|
|
93
|
+
### Session info
|
|
71
94
|
|
|
72
95
|
```bash
|
|
96
|
+
# Show info for current directory
|
|
73
97
|
dev-prism info
|
|
98
|
+
|
|
99
|
+
# Or specify directory
|
|
100
|
+
dev-prism info /path/to/session
|
|
74
101
|
```
|
|
75
102
|
|
|
76
103
|
### Start/Stop services
|
|
77
104
|
|
|
78
105
|
```bash
|
|
79
|
-
|
|
80
|
-
dev-prism
|
|
81
|
-
|
|
106
|
+
# Stop session in current directory
|
|
107
|
+
dev-prism stop
|
|
108
|
+
|
|
109
|
+
# Or specify directory
|
|
110
|
+
dev-prism stop /path/to/session
|
|
111
|
+
|
|
112
|
+
# Stop all running sessions
|
|
113
|
+
dev-prism stop-all
|
|
114
|
+
|
|
115
|
+
# Start stopped session
|
|
116
|
+
dev-prism start
|
|
82
117
|
```
|
|
83
118
|
|
|
84
119
|
### View logs
|
|
85
120
|
|
|
86
121
|
```bash
|
|
87
|
-
|
|
122
|
+
# Stream logs from current directory
|
|
123
|
+
dev-prism logs
|
|
124
|
+
|
|
125
|
+
# Or specify directory
|
|
126
|
+
dev-prism logs /path/to/session
|
|
88
127
|
```
|
|
89
128
|
|
|
90
129
|
### Cleanup
|
|
91
130
|
|
|
92
131
|
```bash
|
|
93
|
-
|
|
94
|
-
dev-prism destroy
|
|
95
|
-
|
|
96
|
-
|
|
132
|
+
# Destroy session in current directory
|
|
133
|
+
dev-prism destroy
|
|
134
|
+
|
|
135
|
+
# Or specify directory
|
|
136
|
+
dev-prism destroy /path/to/session
|
|
137
|
+
|
|
138
|
+
# Destroy all sessions
|
|
139
|
+
dev-prism destroy --all
|
|
140
|
+
|
|
141
|
+
# Remove all stopped session directories
|
|
142
|
+
dev-prism prune
|
|
143
|
+
dev-prism prune -y # Skip confirmation
|
|
97
144
|
```
|
|
98
145
|
|
|
99
146
|
### Claude Code integration
|
|
@@ -103,19 +150,57 @@ dev-prism claude # Install Claude Code skill + CLAUDE.md
|
|
|
103
150
|
dev-prism claude --force # Overwrite existing files
|
|
104
151
|
```
|
|
105
152
|
|
|
106
|
-
##
|
|
153
|
+
## Architecture
|
|
154
|
+
|
|
155
|
+
### Stateless Design
|
|
156
|
+
|
|
157
|
+
dev-prism v0.6+ has **zero persistent state**. Every command queries Docker to understand current reality:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Session discovery
|
|
161
|
+
docker ps --filter "label=dev-prism.managed=true"
|
|
162
|
+
|
|
163
|
+
# Session identity = working directory path
|
|
164
|
+
# Stored in container label: dev-prism.working_dir=/path/to/session
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Port Management
|
|
168
|
+
|
|
169
|
+
Ports are **randomly assigned by Docker** and **discovered after startup**:
|
|
170
|
+
|
|
171
|
+
1. Generate `docker-compose.session.yml` with `"0:5432"` (random host port)
|
|
172
|
+
2. Start containers: `docker compose up -d`
|
|
173
|
+
3. Inspect containers: `docker inspect` to get actual ports
|
|
174
|
+
4. Write `.env.session` with discovered ports
|
|
175
|
+
|
|
176
|
+
Example discovered ports:
|
|
177
|
+
```bash
|
|
178
|
+
POSTGRES_PORT=54321 # Random
|
|
179
|
+
APP_PORT=32768 # Random
|
|
180
|
+
WEB_PORT=32769 # Random
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Why random ports?**
|
|
184
|
+
- Zero configuration
|
|
185
|
+
- Docker handles conflicts automatically
|
|
186
|
+
- Large ephemeral port range (32768-60999)
|
|
187
|
+
- Simpler than centralized allocation
|
|
188
|
+
|
|
189
|
+
### Auto-Healing
|
|
190
|
+
|
|
191
|
+
Commands always reflect Docker's current state:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# If .env.session is deleted, it regenerates from Docker
|
|
195
|
+
dev-prism info # Queries Docker, recreates .env.session
|
|
107
196
|
|
|
108
|
-
|
|
197
|
+
# If containers are manually removed, session disappears from list
|
|
198
|
+
dev-prism list # Only shows what Docker reports
|
|
109
199
|
|
|
110
|
-
|
|
200
|
+
# If containers exist but .env.session is missing, file is recreated
|
|
201
|
+
```
|
|
111
202
|
|
|
112
|
-
|
|
113
|
-
|----------------|-------------|-------------|-------------|
|
|
114
|
-
| APP_PORT | 47100 | 47200 | 47300 |
|
|
115
|
-
| WEB_PORT | 47101 | 47201 | 47301 |
|
|
116
|
-
| POSTGRES_PORT | 47110 | 47210 | 47310 |
|
|
117
|
-
| MAILPIT_SMTP | 47111 | 47211 | 47311 |
|
|
118
|
-
| MAILPIT_WEB | 47112 | 47212 | 47312 |
|
|
203
|
+
No warnings, no errors, no stale state—just current reality.
|
|
119
204
|
|
|
120
205
|
## Configuration
|
|
121
206
|
|
|
@@ -123,18 +208,11 @@ With base port 47000:
|
|
|
123
208
|
|
|
124
209
|
```javascript
|
|
125
210
|
export default {
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
sessionsDir: '../my-project-sessions',
|
|
211
|
+
// Project name for Docker namespace (defaults to directory name)
|
|
212
|
+
projectName: 'myproject',
|
|
129
213
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
ports: {
|
|
133
|
-
APP_PORT: 0, // 47100, 47200, 47300...
|
|
134
|
-
WEB_PORT: 1, // 47101, 47201, 47301...
|
|
135
|
-
POSTGRES_PORT: 10, // 47110, 47210, 47310...
|
|
136
|
-
REDIS_PORT: 11, // 47111, 47211, 47311...
|
|
137
|
-
},
|
|
214
|
+
// Where to create worktrees (relative to project root)
|
|
215
|
+
sessionsDir: '../my-project-sessions',
|
|
138
216
|
|
|
139
217
|
// Docker Compose profiles for app containers (used in docker mode)
|
|
140
218
|
// These match service names with `profiles: ["app-name"]` in docker-compose
|
|
@@ -158,15 +236,14 @@ export default {
|
|
|
158
236
|
};
|
|
159
237
|
```
|
|
160
238
|
|
|
161
|
-
### docker-compose.
|
|
239
|
+
### docker-compose.yml (your base services)
|
|
240
|
+
|
|
241
|
+
Define your services as usual:
|
|
162
242
|
|
|
163
243
|
```yaml
|
|
164
244
|
services:
|
|
165
245
|
postgres:
|
|
166
246
|
image: postgres:16
|
|
167
|
-
container_name: postgres-${SESSION_ID}
|
|
168
|
-
ports:
|
|
169
|
-
- "${POSTGRES_PORT}:5432"
|
|
170
247
|
environment:
|
|
171
248
|
POSTGRES_USER: postgres
|
|
172
249
|
POSTGRES_PASSWORD: postgres
|
|
@@ -176,14 +253,11 @@ services:
|
|
|
176
253
|
timeout: 5s
|
|
177
254
|
retries: 5
|
|
178
255
|
|
|
179
|
-
|
|
180
|
-
profiles: ["
|
|
256
|
+
app:
|
|
257
|
+
profiles: ["app"] # Only runs in docker mode
|
|
181
258
|
build:
|
|
182
259
|
context: .
|
|
183
260
|
dockerfile: apps/my-app/Dockerfile.dev
|
|
184
|
-
container_name: my-app-${SESSION_ID}
|
|
185
|
-
ports:
|
|
186
|
-
- "${APP_PORT}:3000"
|
|
187
261
|
environment:
|
|
188
262
|
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
|
189
263
|
depends_on:
|
|
@@ -191,39 +265,101 @@ services:
|
|
|
191
265
|
condition: service_healthy
|
|
192
266
|
```
|
|
193
267
|
|
|
268
|
+
### Generated docker-compose.session.yml
|
|
269
|
+
|
|
270
|
+
dev-prism generates this automatically with random ports and labels:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
# Auto-generated by dev-prism - DO NOT EDIT
|
|
274
|
+
version: "3.8"
|
|
275
|
+
|
|
276
|
+
x-dev-prism-labels: &dev-prism-labels
|
|
277
|
+
dev-prism.managed: "true"
|
|
278
|
+
dev-prism.working_dir: "/Users/you/worktrees/session-2026-02-06T12-30-45"
|
|
279
|
+
dev-prism.session_id: "/Users/you/worktrees/session-2026-02-06T12-30-45"
|
|
280
|
+
dev-prism.created_at: "2026-02-06T12:30:45.123Z"
|
|
281
|
+
|
|
282
|
+
services:
|
|
283
|
+
postgres:
|
|
284
|
+
extends:
|
|
285
|
+
file: docker-compose.yml
|
|
286
|
+
service: postgres
|
|
287
|
+
ports:
|
|
288
|
+
- "0:5432" # Random host port
|
|
289
|
+
labels:
|
|
290
|
+
<<: *dev-prism-labels
|
|
291
|
+
dev-prism.service: "postgres"
|
|
292
|
+
dev-prism.internal_port: "5432"
|
|
293
|
+
|
|
294
|
+
app:
|
|
295
|
+
extends:
|
|
296
|
+
file: docker-compose.yml
|
|
297
|
+
service: app
|
|
298
|
+
ports:
|
|
299
|
+
- "0:3000" # Random host port
|
|
300
|
+
labels:
|
|
301
|
+
<<: *dev-prism-labels
|
|
302
|
+
dev-prism.service: "app"
|
|
303
|
+
dev-prism.internal_port: "3000"
|
|
304
|
+
```
|
|
305
|
+
|
|
194
306
|
## How It Works
|
|
195
307
|
|
|
196
308
|
1. **Create session**: `dev-prism create`
|
|
197
|
-
-
|
|
198
|
-
- Creates git worktree
|
|
199
|
-
- Generates
|
|
200
|
-
-
|
|
201
|
-
- Runs `docker compose
|
|
309
|
+
- Checks for existing session in directory via Docker labels
|
|
310
|
+
- Creates git worktree (or uses current dir with `--in-place`)
|
|
311
|
+
- Generates `docker-compose.session.yml` with random port bindings (`"0:5432"`)
|
|
312
|
+
- Writes `.env.session` stub with compose project name
|
|
313
|
+
- Runs `docker compose up -d`
|
|
314
|
+
- Inspects running containers to discover actual ports
|
|
315
|
+
- Updates `.env.session` with discovered ports
|
|
202
316
|
- Runs setup commands
|
|
203
317
|
|
|
204
|
-
2. **
|
|
318
|
+
2. **Port discovery**
|
|
319
|
+
- Query containers: `docker inspect <container-id>`
|
|
320
|
+
- Extract `NetworkSettings.Ports` mappings
|
|
321
|
+
- Write discovered ports to `.env.session`
|
|
322
|
+
|
|
323
|
+
3. **Session identity**
|
|
324
|
+
- Session ID = full working directory path
|
|
325
|
+
- Stored in container labels: `dev-prism.working_dir=/path/to/session`
|
|
326
|
+
- One session per directory maximum
|
|
205
327
|
|
|
206
|
-
|
|
207
|
-
|
|
328
|
+
4. **List sessions**: `dev-prism list`
|
|
329
|
+
- Query Docker: `docker ps --filter label=dev-prism.managed=true`
|
|
330
|
+
- Group by `working_dir` label
|
|
331
|
+
- Show only running sessions
|
|
208
332
|
|
|
209
|
-
|
|
333
|
+
5. **Stop session**: `dev-prism stop`
|
|
334
|
+
- Find containers via labels
|
|
335
|
+
- Run `docker compose stop`
|
|
336
|
+
- Delete `.env.session` file
|
|
210
337
|
|
|
211
338
|
## Generated Files
|
|
212
339
|
|
|
213
340
|
```
|
|
214
|
-
session-
|
|
215
|
-
├── .env.session
|
|
216
|
-
├── docker-compose.session.yml #
|
|
217
|
-
└── apps/my-app/.env.session
|
|
341
|
+
session-2026-02-06T12-30-45/
|
|
342
|
+
├── .env.session # Discovered ports (gitignored)
|
|
343
|
+
├── docker-compose.session.yml # Generated compose file (gitignored)
|
|
344
|
+
└── apps/my-app/.env.session # App-specific env (gitignored)
|
|
218
345
|
```
|
|
219
346
|
|
|
220
|
-
Example `.env.session
|
|
347
|
+
Example `.env.session` (after port discovery):
|
|
221
348
|
```bash
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
349
|
+
# Auto-generated by dev-prism
|
|
350
|
+
COMPOSE_PROJECT_NAME=myproject-a1b2c3d4
|
|
351
|
+
SESSION_DIR=/Users/you/worktrees/session-2026-02-06T12-30-45
|
|
352
|
+
|
|
353
|
+
# Discovered ports from running containers
|
|
354
|
+
POSTGRES_PORT=54321
|
|
355
|
+
APP_PORT=32768
|
|
356
|
+
WEB_PORT=32769
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Add to `.gitignore`:
|
|
360
|
+
```
|
|
361
|
+
.env.session
|
|
362
|
+
docker-compose.session.yml
|
|
227
363
|
```
|
|
228
364
|
|
|
229
365
|
## Portability
|
|
@@ -231,6 +367,62 @@ APP_PORT=47100
|
|
|
231
367
|
To use in another project:
|
|
232
368
|
|
|
233
369
|
1. Install: `pnpm add -D dev-prism`
|
|
234
|
-
2. Create `session.config.mjs`
|
|
235
|
-
3.
|
|
370
|
+
2. Create `session.config.mjs` (optional, has defaults)
|
|
371
|
+
3. Define services in `docker-compose.yml`
|
|
236
372
|
4. Run `dev-prism create`
|
|
373
|
+
|
|
374
|
+
dev-prism generates `docker-compose.session.yml` automatically—you never need to write it.
|
|
375
|
+
|
|
376
|
+
## Migration from v0.5.x
|
|
377
|
+
|
|
378
|
+
v0.6.0 is a breaking change with a new stateless architecture:
|
|
379
|
+
|
|
380
|
+
**What changed:**
|
|
381
|
+
- Session IDs: `001, 002, 003` → full directory paths
|
|
382
|
+
- Port allocation: calculated → random (Docker assigns)
|
|
383
|
+
- State storage: SQLite database → stateless (Docker labels)
|
|
384
|
+
- Commands: `dev-prism stop 001` → `dev-prism stop` (uses cwd)
|
|
385
|
+
|
|
386
|
+
**Migration steps:**
|
|
387
|
+
1. Stop all v0.5 sessions: `dev-prism stop-all` (on v0.5.x)
|
|
388
|
+
2. Upgrade to v0.6: `pnpm add -g dev-prism@0.6`
|
|
389
|
+
3. Recreate sessions as needed
|
|
390
|
+
|
|
391
|
+
Old session directories can be deleted manually if no longer needed.
|
|
392
|
+
|
|
393
|
+
## Why Stateless?
|
|
394
|
+
|
|
395
|
+
**Problems with v0.5.x database approach:**
|
|
396
|
+
- State could diverge from Docker reality (manual container removal)
|
|
397
|
+
- Required reconciliation logic
|
|
398
|
+
- Database was single point of failure
|
|
399
|
+
- Stored data that Docker already knows
|
|
400
|
+
|
|
401
|
+
**v0.6+ stateless benefits:**
|
|
402
|
+
- Docker is always the source of truth
|
|
403
|
+
- No state sync issues
|
|
404
|
+
- Survives `docker system prune`
|
|
405
|
+
- Simpler codebase (fewer abstractions)
|
|
406
|
+
- Auto-heals on every command
|
|
407
|
+
- Zero configuration for port conflicts
|
|
408
|
+
|
|
409
|
+
## Troubleshooting
|
|
410
|
+
|
|
411
|
+
**"No session found in this directory"**
|
|
412
|
+
- Session only exists when containers are running
|
|
413
|
+
- Run `dev-prism create` to start a new session
|
|
414
|
+
|
|
415
|
+
**"Session already running in this directory"**
|
|
416
|
+
- Containers are already running here
|
|
417
|
+
- Use `dev-prism stop` first, then `dev-prism create` again
|
|
418
|
+
|
|
419
|
+
**Ports not in .env.session**
|
|
420
|
+
- Run `dev-prism info` to regenerate from Docker
|
|
421
|
+
|
|
422
|
+
**Want predictable ports?**
|
|
423
|
+
- Override in your `docker-compose.yml`: `ports: ["5432:5432"]`
|
|
424
|
+
- Trade-off: potential conflicts across sessions
|
|
425
|
+
|
|
426
|
+
## License
|
|
427
|
+
|
|
428
|
+
MIT
|
package/bin/dev-prism.js
CHANGED
|
@@ -17,19 +17,19 @@ const program = new Command();
|
|
|
17
17
|
program
|
|
18
18
|
.name('dev-prism')
|
|
19
19
|
.description('CLI tool for managing isolated parallel development sessions')
|
|
20
|
-
.version('0.
|
|
20
|
+
.version('0.6.0');
|
|
21
21
|
|
|
22
22
|
program
|
|
23
|
-
.command('create
|
|
23
|
+
.command('create')
|
|
24
24
|
.description('Create a new isolated development session')
|
|
25
25
|
.option('-m, --mode <mode>', 'App mode: docker (default) or native', 'docker')
|
|
26
|
-
.option('-b, --branch <branch>', 'Git branch name (default: session/
|
|
26
|
+
.option('-b, --branch <branch>', 'Git branch name (default: session/TIMESTAMP)')
|
|
27
27
|
.option('-W, --without <apps>', 'Exclude apps (comma-separated: app,web,widget)', (val) => val.split(','))
|
|
28
28
|
.option('--no-detach', 'Stream container logs after starting (default: detach)')
|
|
29
29
|
.option('--in-place', 'Run in current directory instead of creating a worktree')
|
|
30
|
-
.action(async (
|
|
30
|
+
.action(async (options) => {
|
|
31
31
|
const projectRoot = process.cwd();
|
|
32
|
-
await createSession(projectRoot,
|
|
32
|
+
await createSession(projectRoot, undefined, {
|
|
33
33
|
mode: options.mode,
|
|
34
34
|
branch: options.branch,
|
|
35
35
|
detach: options.detach,
|
|
@@ -39,20 +39,19 @@ program
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
program
|
|
42
|
-
.command('destroy [
|
|
43
|
-
.description('Destroy a development session')
|
|
42
|
+
.command('destroy [directory]')
|
|
43
|
+
.description('Destroy a development session (defaults to current directory)')
|
|
44
44
|
.option('-a, --all', 'Destroy all sessions')
|
|
45
|
-
.action(async (
|
|
46
|
-
const
|
|
47
|
-
await destroySession(
|
|
45
|
+
.action(async (directory, options) => {
|
|
46
|
+
const workingDir = directory || process.cwd();
|
|
47
|
+
await destroySession(workingDir, { all: options.all });
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
program
|
|
51
51
|
.command('list')
|
|
52
52
|
.description('List all active development sessions')
|
|
53
53
|
.action(async () => {
|
|
54
|
-
|
|
55
|
-
await listSessions(projectRoot);
|
|
54
|
+
await listSessions();
|
|
56
55
|
});
|
|
57
56
|
|
|
58
57
|
program
|
|
@@ -63,35 +62,35 @@ program
|
|
|
63
62
|
});
|
|
64
63
|
|
|
65
64
|
program
|
|
66
|
-
.command('start
|
|
67
|
-
.description('Start Docker services for a session')
|
|
65
|
+
.command('start [directory]')
|
|
66
|
+
.description('Start Docker services for a session (defaults to current directory)')
|
|
68
67
|
.option('-m, --mode <mode>', 'App mode: docker or native', 'docker')
|
|
69
68
|
.option('-W, --without <apps>', 'Exclude apps (comma-separated: app,web,widget)', (val) => val.split(','))
|
|
70
|
-
.action(async (
|
|
71
|
-
const
|
|
72
|
-
await startSession(
|
|
69
|
+
.action(async (directory, options) => {
|
|
70
|
+
const workingDir = directory || process.cwd();
|
|
71
|
+
await startSession(workingDir, {
|
|
73
72
|
mode: options.mode,
|
|
74
73
|
without: options.without,
|
|
75
74
|
});
|
|
76
75
|
});
|
|
77
76
|
|
|
78
77
|
program
|
|
79
|
-
.command('stop
|
|
80
|
-
.description('Stop Docker services for a session (
|
|
81
|
-
.action(async (
|
|
82
|
-
const
|
|
83
|
-
await stopSession(
|
|
78
|
+
.command('stop [directory]')
|
|
79
|
+
.description('Stop Docker services for a session (defaults to current directory)')
|
|
80
|
+
.action(async (directory) => {
|
|
81
|
+
const workingDir = directory || process.cwd();
|
|
82
|
+
await stopSession(workingDir);
|
|
84
83
|
});
|
|
85
84
|
|
|
86
85
|
program
|
|
87
|
-
.command('logs
|
|
88
|
-
.description('Stream logs from a session\'s Docker services')
|
|
86
|
+
.command('logs [directory]')
|
|
87
|
+
.description('Stream logs from a session\'s Docker services (defaults to current directory)')
|
|
89
88
|
.option('-m, --mode <mode>', 'App mode: docker or native', 'docker')
|
|
90
89
|
.option('-W, --without <apps>', 'Exclude apps (comma-separated: app,web,widget)', (val) => val.split(','))
|
|
91
90
|
.option('-n, --tail <lines>', 'Number of lines to show from the end', '50')
|
|
92
|
-
.action(async (
|
|
93
|
-
const
|
|
94
|
-
await streamLogs(
|
|
91
|
+
.action(async (directory, options) => {
|
|
92
|
+
const workingDir = directory || process.cwd();
|
|
93
|
+
await streamLogs(workingDir, {
|
|
95
94
|
mode: options.mode,
|
|
96
95
|
without: options.without,
|
|
97
96
|
tail: options.tail,
|
|
@@ -102,8 +101,7 @@ program
|
|
|
102
101
|
.command('stop-all')
|
|
103
102
|
.description('Stop all running sessions (preserves data)')
|
|
104
103
|
.action(async () => {
|
|
105
|
-
|
|
106
|
-
await stopAllSessions(projectRoot);
|
|
104
|
+
await stopAllSessions();
|
|
107
105
|
});
|
|
108
106
|
|
|
109
107
|
program
|
|
@@ -111,8 +109,7 @@ program
|
|
|
111
109
|
.description('Remove all stopped sessions (destroys data)')
|
|
112
110
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
113
111
|
.action(async (options) => {
|
|
114
|
-
|
|
115
|
-
await pruneSessions(projectRoot, { yes: options.yes });
|
|
112
|
+
await pruneSessions({ yes: options.yes });
|
|
116
113
|
});
|
|
117
114
|
|
|
118
115
|
program
|
|
@@ -136,21 +133,21 @@ ${chalk.bold('USAGE')}
|
|
|
136
133
|
dev-prism <command> [options]
|
|
137
134
|
|
|
138
135
|
${chalk.bold('COMMANDS')}
|
|
139
|
-
${chalk.cyan('create')}
|
|
140
|
-
${chalk.cyan('destroy')}
|
|
141
|
-
${chalk.cyan('list')} List all sessions
|
|
136
|
+
${chalk.cyan('create')} Create a new session
|
|
137
|
+
${chalk.cyan('destroy')} [dir] Destroy a session (defaults to current directory)
|
|
138
|
+
${chalk.cyan('list')} List all active sessions
|
|
142
139
|
${chalk.cyan('info')} Show session info for current directory
|
|
143
|
-
${chalk.cyan('start')}
|
|
144
|
-
${chalk.cyan('stop')}
|
|
140
|
+
${chalk.cyan('start')} [dir] Start Docker services (defaults to current directory)
|
|
141
|
+
${chalk.cyan('stop')} [dir] Stop Docker services (defaults to current directory)
|
|
145
142
|
${chalk.cyan('stop-all')} Stop all running sessions
|
|
146
|
-
${chalk.cyan('logs')}
|
|
147
|
-
${chalk.cyan('prune')} Remove all stopped
|
|
143
|
+
${chalk.cyan('logs')} [dir] Stream logs (defaults to current directory)
|
|
144
|
+
${chalk.cyan('prune')} Remove all stopped session directories
|
|
148
145
|
|
|
149
146
|
${chalk.bold('EXAMPLES')}
|
|
150
|
-
${chalk.gray('# Create a new session
|
|
147
|
+
${chalk.gray('# Create a new session with worktree')}
|
|
151
148
|
$ dev-prism create
|
|
152
149
|
|
|
153
|
-
${chalk.gray('# Create session with specific branch')}
|
|
150
|
+
${chalk.gray('# Create session with specific branch name')}
|
|
154
151
|
$ dev-prism create --branch feature/my-feature
|
|
155
152
|
|
|
156
153
|
${chalk.gray('# Create session in native mode (apps run on host)')}
|
|
@@ -165,12 +162,18 @@ ${chalk.bold('EXAMPLES')}
|
|
|
165
162
|
${chalk.gray('# Check session status in current directory')}
|
|
166
163
|
$ dev-prism info
|
|
167
164
|
|
|
168
|
-
${chalk.gray('# Stop
|
|
165
|
+
${chalk.gray('# Stop session in current directory')}
|
|
166
|
+
$ dev-prism stop
|
|
167
|
+
|
|
168
|
+
${chalk.gray('# Stop all running sessions')}
|
|
169
169
|
$ dev-prism stop-all
|
|
170
170
|
|
|
171
|
-
${chalk.gray('# Clean up old stopped
|
|
171
|
+
${chalk.gray('# Clean up old stopped session directories')}
|
|
172
172
|
$ dev-prism prune
|
|
173
173
|
|
|
174
|
+
${chalk.gray('# Destroy session in current directory')}
|
|
175
|
+
$ dev-prism destroy
|
|
176
|
+
|
|
174
177
|
${chalk.gray('# Destroy all sessions')}
|
|
175
178
|
$ dev-prism destroy --all
|
|
176
179
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listActiveSessions
|
|
3
|
+
} from "./chunk-DQER5GNG.js";
|
|
4
|
+
|
|
5
|
+
// src/commands/list.ts
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
async function listSessions() {
|
|
8
|
+
const sessions = await listActiveSessions();
|
|
9
|
+
if (sessions.length === 0) {
|
|
10
|
+
console.log(chalk.gray("No active sessions found."));
|
|
11
|
+
console.log(chalk.gray("\nTo create a session:"));
|
|
12
|
+
console.log(chalk.cyan(" dev-prism create"));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
console.log(chalk.blue("Active Sessions:"));
|
|
16
|
+
console.log(chalk.gray("================\n"));
|
|
17
|
+
for (const session of sessions) {
|
|
18
|
+
const statusIcon = session.running ? chalk.green("\u25CF") : chalk.red("\u25CB");
|
|
19
|
+
const statusText = session.running ? chalk.green("running") : chalk.gray("stopped");
|
|
20
|
+
const containerCount = session.containers.length;
|
|
21
|
+
console.log(`${statusIcon} Session ${statusText} (${containerCount} container${containerCount !== 1 ? "s" : ""})`);
|
|
22
|
+
console.log(chalk.gray(` Directory: ${session.workingDir}`));
|
|
23
|
+
if (session.ports.length > 0) {
|
|
24
|
+
console.log(chalk.gray(" Ports:"));
|
|
25
|
+
for (const port of session.ports) {
|
|
26
|
+
console.log(chalk.cyan(` ${port.service}: http://localhost:${port.externalPort}`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
console.log("");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
listSessions
|
|
35
|
+
};
|