@cruxgarden/cli 0.0.9 → 0.0.11
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 +106 -9
- package/bin/crux.js +17 -9
- package/docker/docker-compose.nursery.yml +26 -14
- package/lib/commands.js +141 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
<img src=".github/banner.jpg" alt="Crux Garden - Where Ideas Grow" width="100%">
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
|
+
## What is Crux Garden?
|
|
6
|
+
|
|
7
|
+
Crux Garden is a model of how ideas manifest and develop over time. The heart of the model is the **Crux**, an atomic representation of an idea. In our implementation, a Crux can be text, media, code, or any digital content worth preserving. But Cruxes, just like ideas, don't exist in isolation. Ideas have origins. They lead to new ideas. They evolve. And often, they randomly connect. So it is with Cruxes. In Crux Garden, there are four types of relationships Cruxes can have with each other. These are called **Dimensions**:
|
|
8
|
+
|
|
9
|
+
- **GATES** — Cruxes which influenced or inspired a Crux; its origins and sources.
|
|
10
|
+
- **GARDENS** — Cruxes which emerged or grew from a Crux, its creations and consequences.
|
|
11
|
+
- **GROWTH** — How a Crux developed over time, its transformation and refinement.
|
|
12
|
+
- **GRAFTS** — Cruxes which connect to a Crux laterally, its associations and resonances.
|
|
13
|
+
|
|
14
|
+
These four Dimensions capture the fundamental ways that Cruxes, or ideas, relate to one another.
|
|
15
|
+
|
|
16
|
+
The power of a system that models ideas at such a primitive scale is that literally any idea or framework of ideas can be realized inside Crux Garden: interactive fiction with evolving storylines, personal knowledge bases connecting insights across domains, product development roadmaps linking requirements to releases, research systems tracking citations and discoveries, or anything else where ideas have origins, consequences, transformation, and connection.
|
|
17
|
+
|
|
18
|
+
Along with Cruxes and Dimensions, there are several other types including Tags, Themes, and Paths. See the database schema in `db/schema.sql` or the API documentation at `/docs` for details.
|
|
19
|
+
|
|
20
|
+
For further reading on the goals and ambitions of Crux Garden, explore the history of the [Digital Garden](https://maggieappleton.com/garden-history) movement and the 1945 essay [As We May Think](https://en.wikipedia.org/wiki/As_We_May_Think) by Vannevar Bush.
|
|
21
|
+
|
|
22
|
+
Will Stepp, October 2025
|
|
23
|
+
|
|
24
|
+
## CLI
|
|
25
|
+
|
|
5
26
|
The Crux Garden CLI tool helps manage the Crux Garden Nursery environment with Docker.
|
|
6
27
|
|
|
7
28
|
The **Nursery** is a production-like demo environment with sample data, perfect for trials, demos, and showcasing features.
|
|
@@ -57,7 +78,7 @@ Start the Nursery environment:
|
|
|
57
78
|
crux nursery start
|
|
58
79
|
```
|
|
59
80
|
|
|
60
|
-
The
|
|
81
|
+
The App will be available at `http://localhost:8080` and the API at `http://localhost:3000` with demo data loaded.
|
|
61
82
|
|
|
62
83
|
View logs:
|
|
63
84
|
|
|
@@ -77,15 +98,26 @@ All commands are scoped under `crux nursery`:
|
|
|
77
98
|
|
|
78
99
|
### `crux nursery start`
|
|
79
100
|
|
|
80
|
-
Start the Nursery environment (
|
|
101
|
+
Start the Nursery environment (App, API, PostgreSQL, Redis with demo data).
|
|
81
102
|
|
|
82
103
|
```bash
|
|
83
104
|
crux nursery start
|
|
105
|
+
|
|
106
|
+
# With inline environment variables
|
|
107
|
+
crux nursery start API_PORT=3001 APP_PORT=8081
|
|
108
|
+
|
|
109
|
+
# With options and environment variables
|
|
110
|
+
crux nursery start --api-only JWT_SECRET=my-secret
|
|
84
111
|
```
|
|
85
112
|
|
|
86
113
|
**Options:**
|
|
87
114
|
|
|
88
115
|
- `--db-only` - Start only database services (PostgreSQL and Redis)
|
|
116
|
+
- `--api-only` - Start only API services (API, PostgreSQL, Redis) without the App
|
|
117
|
+
|
|
118
|
+
**Environment Variables:**
|
|
119
|
+
|
|
120
|
+
You can pass environment variables directly on the command line in `KEY=VALUE` format after the command and options. These override values from your `.env` file.
|
|
89
121
|
|
|
90
122
|
### `crux nursery stop`
|
|
91
123
|
|
|
@@ -103,6 +135,11 @@ Restart the Nursery environment.
|
|
|
103
135
|
crux nursery restart
|
|
104
136
|
```
|
|
105
137
|
|
|
138
|
+
**Options:**
|
|
139
|
+
|
|
140
|
+
- `--db-only` - Restart only database services (PostgreSQL and Redis)
|
|
141
|
+
- `--api-only` - Restart only API services (API, PostgreSQL, Redis) without the App
|
|
142
|
+
|
|
106
143
|
### `crux nursery status`
|
|
107
144
|
|
|
108
145
|
Show the status of all Nursery services.
|
|
@@ -198,6 +235,14 @@ Connect to Nursery Redis with `redis-cli`.
|
|
|
198
235
|
crux nursery redis connect
|
|
199
236
|
```
|
|
200
237
|
|
|
238
|
+
### `crux nursery api start`
|
|
239
|
+
|
|
240
|
+
Start only Nursery API services (API, PostgreSQL, Redis) without the App.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
crux nursery api start
|
|
244
|
+
```
|
|
245
|
+
|
|
201
246
|
### `crux nursery api connect`
|
|
202
247
|
|
|
203
248
|
Open a shell in the Nursery API container.
|
|
@@ -217,31 +262,81 @@ The Nursery is a production-like demo environment that:
|
|
|
217
262
|
|
|
218
263
|
**Services:**
|
|
219
264
|
|
|
265
|
+
- **App**: `http://localhost:8080` - Crux Garden Web Application
|
|
220
266
|
- **API**: `http://localhost:3000` - Crux Garden API (published image with demo data)
|
|
221
267
|
- **PostgreSQL**: `localhost:5432` - Database
|
|
222
268
|
- **Redis**: `localhost:6379` - Cache
|
|
223
269
|
|
|
224
270
|
## Environment Variables
|
|
225
271
|
|
|
226
|
-
The Nursery environment has defaults for all environment variables, so
|
|
272
|
+
The Nursery environment has defaults for all environment variables, so configuration is optional. You can override variables in two ways:
|
|
273
|
+
|
|
274
|
+
### 1. Using a `.env` file
|
|
275
|
+
|
|
276
|
+
Create a `.env` file in your working directory:
|
|
227
277
|
|
|
228
278
|
```bash
|
|
229
|
-
#
|
|
279
|
+
# Port Mappings
|
|
280
|
+
API_PORT=3000 # External API port
|
|
281
|
+
APP_PORT=8080 # External App port
|
|
282
|
+
POSTGRES_PORT=5432 # External PostgreSQL port
|
|
283
|
+
REDIS_PORT=6379 # External Redis port
|
|
284
|
+
|
|
285
|
+
# PostgreSQL Configuration
|
|
286
|
+
POSTGRES_DB=cruxgarden
|
|
287
|
+
POSTGRES_USER=cruxgarden
|
|
288
|
+
POSTGRES_PASSWORD=cruxgarden_nursery_password
|
|
289
|
+
|
|
290
|
+
# Database & Cache URLs (override to use external services)
|
|
291
|
+
DATABASE_URL=postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden
|
|
292
|
+
REDIS_URL=redis://redis:6379
|
|
293
|
+
|
|
294
|
+
# Security (has dev default, but you should use a different one)
|
|
230
295
|
JWT_SECRET=your-super-secret-jwt-key-min-32-chars
|
|
231
296
|
|
|
232
|
-
# AWS (defaults to "dummy" values)
|
|
297
|
+
# AWS Configuration (defaults to "dummy" values)
|
|
233
298
|
AWS_ACCESS_KEY_ID=your-key
|
|
234
299
|
AWS_SECRET_ACCESS_KEY=your-secret
|
|
235
300
|
AWS_REGION=us-east-1
|
|
236
301
|
AWS_SES_FROM_EMAIL=demo@example.com
|
|
237
302
|
AWS_S3_ATTACHMENTS_BUCKET=crux-garden-attachments
|
|
238
303
|
|
|
239
|
-
# Optional
|
|
304
|
+
# Optional Configuration
|
|
305
|
+
NODE_ENV=production
|
|
240
306
|
CORS_ORIGIN=*
|
|
241
307
|
LOG_LEVEL=info
|
|
242
|
-
|
|
308
|
+
HOSTNAME=0.0.0.0
|
|
309
|
+
DB_POOL_MIN=2
|
|
310
|
+
DB_POOL_MAX=10
|
|
311
|
+
DB_POOL_IDLE_TIMEOUT=30000
|
|
312
|
+
DB_POOL_ACQUIRE_TIMEOUT=60000
|
|
313
|
+
RATE_LIMIT_TTL=60000
|
|
314
|
+
RATE_LIMIT_MAX=100
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 2. Using inline environment variables
|
|
318
|
+
|
|
319
|
+
Pass environment variables directly on the command line:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Override ports
|
|
323
|
+
crux nursery start API_PORT=3001 APP_PORT=8081
|
|
324
|
+
|
|
325
|
+
# Override database connection
|
|
326
|
+
crux nursery start DATABASE_URL=postgresql://user:pass@external-host:5432/db
|
|
327
|
+
|
|
328
|
+
# Multiple variables
|
|
329
|
+
crux nursery start API_PORT=3001 JWT_SECRET=my-secret AWS_REGION=us-west-2
|
|
330
|
+
|
|
331
|
+
# Works with all start commands
|
|
332
|
+
crux nursery api start API_PORT=4000
|
|
333
|
+
crux nursery db start POSTGRES_PORT=5433
|
|
334
|
+
crux nursery restart API_PORT=3001
|
|
335
|
+
crux nursery reset JWT_SECRET=new-secret
|
|
243
336
|
```
|
|
244
337
|
|
|
338
|
+
**Note:** Inline environment variables override values from your `.env` file. You can override `DATABASE_URL` and `REDIS_URL` to connect the API to external database/cache services instead of the bundled ones.
|
|
339
|
+
|
|
245
340
|
## Common Workflows
|
|
246
341
|
|
|
247
342
|
### Demo/Trial Setup
|
|
@@ -249,10 +344,11 @@ PORT=3000
|
|
|
249
344
|
Use the Nursery environment for demos, trials, or showcasing features:
|
|
250
345
|
|
|
251
346
|
```bash
|
|
252
|
-
# First time setup - pulls
|
|
347
|
+
# First time setup - pulls images and starts with demo data
|
|
253
348
|
crux nursery start
|
|
254
349
|
|
|
255
|
-
# View the
|
|
350
|
+
# View the app at http://localhost:8080
|
|
351
|
+
# Or access the API directly at http://localhost:3000
|
|
256
352
|
|
|
257
353
|
# Stop (keeps data for next demo)
|
|
258
354
|
crux nursery stop
|
|
@@ -304,6 +400,7 @@ If you get an error about ports being in use, stop any existing services:
|
|
|
304
400
|
|
|
305
401
|
```bash
|
|
306
402
|
# Check what's using the ports
|
|
403
|
+
lsof -i :8080 # Nursery App
|
|
307
404
|
lsof -i :3000 # Nursery API
|
|
308
405
|
lsof -i :5432 # Nursery PostgreSQL
|
|
309
406
|
lsof -i :6379 # Nursery Redis
|
package/bin/crux.js
CHANGED
|
@@ -37,13 +37,14 @@ const nursery = program
|
|
|
37
37
|
.description("Manage the Nursery environment (demo/trial environment)");
|
|
38
38
|
|
|
39
39
|
nursery
|
|
40
|
-
.command("start")
|
|
41
|
-
.description("Start the Nursery environment (api, postgres, redis)")
|
|
40
|
+
.command("start [env...]")
|
|
41
|
+
.description("Start the Nursery environment (app, api, postgres, redis)")
|
|
42
42
|
.option("--db-only", "Start only database services (postgres, redis)")
|
|
43
|
+
.option("--api-only", "Start only API services (api, postgres, redis) without app")
|
|
43
44
|
.option("--no-banner", "Hide the startup banner")
|
|
44
|
-
.action((options) => {
|
|
45
|
+
.action((envVars, options) => {
|
|
45
46
|
if (!options.noBanner) showBanner();
|
|
46
|
-
startNursery(options);
|
|
47
|
+
startNursery(options, envVars);
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
nursery
|
|
@@ -52,9 +53,11 @@ nursery
|
|
|
52
53
|
.action(stopNursery);
|
|
53
54
|
|
|
54
55
|
nursery
|
|
55
|
-
.command("restart")
|
|
56
|
+
.command("restart [env...]")
|
|
56
57
|
.description("Restart the Nursery environment")
|
|
57
|
-
.
|
|
58
|
+
.option("--db-only", "Restart only database services (postgres, redis)")
|
|
59
|
+
.option("--api-only", "Restart only API services (api, postgres, redis) without app")
|
|
60
|
+
.action((envVars, options) => restartNursery(options, envVars));
|
|
58
61
|
|
|
59
62
|
nursery
|
|
60
63
|
.command("status")
|
|
@@ -85,7 +88,7 @@ nursery
|
|
|
85
88
|
.action(updateNursery);
|
|
86
89
|
|
|
87
90
|
nursery
|
|
88
|
-
.command("reset")
|
|
91
|
+
.command("reset [env...]")
|
|
89
92
|
.description("Complete fresh reset (stop, clean, pull latest image, restart)")
|
|
90
93
|
.action(resetNursery);
|
|
91
94
|
|
|
@@ -95,9 +98,9 @@ const nurseryDb = nursery
|
|
|
95
98
|
.description("Manage Nursery database services");
|
|
96
99
|
|
|
97
100
|
nurseryDb
|
|
98
|
-
.command("start")
|
|
101
|
+
.command("start [env...]")
|
|
99
102
|
.description("Start only Nursery database services (postgres, redis)")
|
|
100
|
-
.action(() => startNursery({ dbOnly: true }));
|
|
103
|
+
.action((envVars) => startNursery({ dbOnly: true }, envVars));
|
|
101
104
|
|
|
102
105
|
nurseryDb
|
|
103
106
|
.command("stop")
|
|
@@ -122,6 +125,11 @@ nurseryRedis
|
|
|
122
125
|
// Nursery API commands
|
|
123
126
|
const nurseryApi = nursery.command("api").description("Manage Nursery API");
|
|
124
127
|
|
|
128
|
+
nurseryApi
|
|
129
|
+
.command("start [env...]")
|
|
130
|
+
.description("Start only Nursery API services (api, postgres, redis) without app")
|
|
131
|
+
.action((envVars) => startNursery({ apiOnly: true }, envVars));
|
|
132
|
+
|
|
125
133
|
nurseryApi
|
|
126
134
|
.command("connect")
|
|
127
135
|
.description("Open a shell in the Nursery API container")
|
|
@@ -12,15 +12,15 @@ services:
|
|
|
12
12
|
image: postgres:16-alpine
|
|
13
13
|
container_name: cruxgarden-nursery-postgres
|
|
14
14
|
environment:
|
|
15
|
-
POSTGRES_DB: cruxgarden
|
|
16
|
-
POSTGRES_USER: cruxgarden
|
|
17
|
-
POSTGRES_PASSWORD: cruxgarden_nursery_password
|
|
15
|
+
POSTGRES_DB: ${POSTGRES_DB:-cruxgarden}
|
|
16
|
+
POSTGRES_USER: ${POSTGRES_USER:-cruxgarden}
|
|
17
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cruxgarden_nursery_password}
|
|
18
18
|
ports:
|
|
19
|
-
- "5432:5432"
|
|
19
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
20
20
|
volumes:
|
|
21
21
|
- postgres_data_nursery:/var/lib/postgresql/data
|
|
22
22
|
healthcheck:
|
|
23
|
-
test: ["CMD-SHELL", "pg_isready -U cruxgarden"]
|
|
23
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cruxgarden}"]
|
|
24
24
|
interval: 5s
|
|
25
25
|
timeout: 5s
|
|
26
26
|
retries: 5
|
|
@@ -31,7 +31,7 @@ services:
|
|
|
31
31
|
container_name: cruxgarden-nursery-redis
|
|
32
32
|
command: redis-server --appendonly yes
|
|
33
33
|
ports:
|
|
34
|
-
- "6379:6379"
|
|
34
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
35
35
|
volumes:
|
|
36
36
|
- redis_data_nursery:/data
|
|
37
37
|
healthcheck:
|
|
@@ -48,8 +48,8 @@ services:
|
|
|
48
48
|
postgres:
|
|
49
49
|
condition: service_healthy
|
|
50
50
|
environment:
|
|
51
|
-
NODE_ENV: production
|
|
52
|
-
DATABASE_URL: postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden
|
|
51
|
+
NODE_ENV: ${NODE_ENV:-production}
|
|
52
|
+
DATABASE_URL: ${DATABASE_URL:-postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden}
|
|
53
53
|
command: ["npm", "run", "migrate:nursery"]
|
|
54
54
|
restart: "no"
|
|
55
55
|
|
|
@@ -64,19 +64,20 @@ services:
|
|
|
64
64
|
redis:
|
|
65
65
|
condition: service_healthy
|
|
66
66
|
ports:
|
|
67
|
-
- "${
|
|
67
|
+
- "${API_PORT:-3000}:3000"
|
|
68
68
|
environment:
|
|
69
69
|
# Application
|
|
70
|
-
NODE_ENV: production
|
|
70
|
+
NODE_ENV: ${NODE_ENV:-production}
|
|
71
71
|
PORT: 3000
|
|
72
|
-
HOSTNAME: 0.0.0.0
|
|
72
|
+
HOSTNAME: ${HOSTNAME:-0.0.0.0}
|
|
73
73
|
|
|
74
|
-
# Bundled Services
|
|
75
|
-
DATABASE_URL: postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden
|
|
76
|
-
REDIS_URL: redis://redis:6379
|
|
74
|
+
# Bundled Services (can override to use external services)
|
|
75
|
+
DATABASE_URL: ${DATABASE_URL:-postgresql://cruxgarden:cruxgarden_nursery_password@postgres:5432/cruxgarden}
|
|
76
|
+
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
|
77
77
|
|
|
78
78
|
# Security
|
|
79
79
|
JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-min-32-chars-for-development-only}
|
|
80
|
+
NURSERY_MODE: ${NURSERY_MODE:-true}
|
|
80
81
|
|
|
81
82
|
# AWS SES (optional for nursery)
|
|
82
83
|
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-dummy}
|
|
@@ -107,6 +108,17 @@ services:
|
|
|
107
108
|
retries: 3
|
|
108
109
|
start_period: 40s
|
|
109
110
|
|
|
111
|
+
# Crux Garden App - Web Application
|
|
112
|
+
app:
|
|
113
|
+
image: ghcr.io/cruxgarden/app:latest
|
|
114
|
+
container_name: cruxgarden-nursery-app
|
|
115
|
+
restart: unless-stopped
|
|
116
|
+
depends_on:
|
|
117
|
+
api:
|
|
118
|
+
condition: service_healthy
|
|
119
|
+
ports:
|
|
120
|
+
- "${APP_PORT:-8080}:80"
|
|
121
|
+
|
|
110
122
|
volumes:
|
|
111
123
|
postgres_data_nursery:
|
|
112
124
|
redis_data_nursery:
|
package/lib/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execSync, spawn } from "child_process";
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
|
-
import { readFileSync } from "fs";
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
5
|
import readline from "readline";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import ora from "ora";
|
|
@@ -166,35 +166,113 @@ function ensureDockerRunning() {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function getEnvFileFlag() {
|
|
170
|
+
const envFilePath = join(process.cwd(), ".env");
|
|
171
|
+
if (existsSync(envFilePath)) {
|
|
172
|
+
return `--env-file "${envFilePath}"`;
|
|
173
|
+
}
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseAndSetEnvVars(envArgs = []) {
|
|
178
|
+
if (!envArgs || envArgs.length === 0) return;
|
|
179
|
+
|
|
180
|
+
for (const arg of envArgs) {
|
|
181
|
+
const match = arg.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
182
|
+
if (match) {
|
|
183
|
+
const [, key, value] = match;
|
|
184
|
+
process.env[key] = value;
|
|
185
|
+
console.log(chalk.gray(` Setting ${key}=${value}`));
|
|
186
|
+
} else {
|
|
187
|
+
console.log(chalk.yellow(` Warning: Ignoring invalid env var format: ${arg}`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
169
192
|
// ============================================================================
|
|
170
193
|
// Nursery Environment Commands
|
|
171
194
|
// ============================================================================
|
|
172
195
|
|
|
173
|
-
export async function startNursery(options) {
|
|
196
|
+
export async function startNursery(options, envVars) {
|
|
174
197
|
ensureDockerRunning();
|
|
198
|
+
|
|
199
|
+
// Ensure NURSERY_MODE is always set to true for nursery environment
|
|
200
|
+
process.env.NURSERY_MODE = 'true';
|
|
201
|
+
|
|
202
|
+
// Parse and set environment variables from command line
|
|
203
|
+
if (envVars && envVars.length > 0) {
|
|
204
|
+
console.log(chalk.cyan("\nApplying environment variables:"));
|
|
205
|
+
parseAndSetEnvVars(envVars);
|
|
206
|
+
console.log();
|
|
207
|
+
}
|
|
208
|
+
|
|
175
209
|
const spinner = ora("Starting Crux Garden Nursery environment...").start();
|
|
210
|
+
const envFileFlag = getEnvFileFlag();
|
|
211
|
+
|
|
212
|
+
// Get actual port values from environment
|
|
213
|
+
const appPort = process.env.APP_PORT || "8080";
|
|
214
|
+
const apiPort = process.env.API_PORT || "3000";
|
|
215
|
+
const postgresPort = process.env.POSTGRES_PORT || "5432";
|
|
216
|
+
const redisPort = process.env.REDIS_PORT || "6379";
|
|
176
217
|
|
|
177
218
|
try {
|
|
178
219
|
if (options.dbOnly) {
|
|
179
220
|
spinner.text = "Starting nursery database services (postgres, redis)...";
|
|
180
221
|
await runCommandAsync(
|
|
181
|
-
|
|
222
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans postgres redis`,
|
|
182
223
|
{ silent: true },
|
|
183
224
|
);
|
|
184
225
|
spinner.succeed("Nursery database services started!");
|
|
185
226
|
console.log(
|
|
186
227
|
chalk.hex(SUCCESS_GREEN)("\n✓ PostgreSQL running on:"),
|
|
187
|
-
|
|
228
|
+
`localhost:${postgresPort}`,
|
|
229
|
+
);
|
|
230
|
+
console.log(
|
|
231
|
+
chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
|
|
232
|
+
`localhost:${redisPort}`,
|
|
233
|
+
);
|
|
234
|
+
} else if (options.apiOnly) {
|
|
235
|
+
spinner.text =
|
|
236
|
+
"Starting nursery API services (api, postgres, redis)...";
|
|
237
|
+
await runCommandAsync(
|
|
238
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans postgres redis migrations api`,
|
|
239
|
+
{
|
|
240
|
+
silent: true,
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Clean up the migrations container if it exists (cross-platform)
|
|
245
|
+
await runCommandAsync(
|
|
246
|
+
"docker rm cruxgarden-nursery-migrations",
|
|
247
|
+
{
|
|
248
|
+
silent: true,
|
|
249
|
+
ignoreError: true,
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
spinner.succeed("Crux Garden Nursery API services started!");
|
|
254
|
+
console.log(
|
|
255
|
+
chalk.hex(SUCCESS_GREEN)("\n✓ API running on:"),
|
|
256
|
+
`http://localhost:${apiPort}`,
|
|
257
|
+
);
|
|
258
|
+
console.log(
|
|
259
|
+
chalk.hex(SUCCESS_GREEN)("✓ PostgreSQL running on:"),
|
|
260
|
+
`localhost:${postgresPort}`,
|
|
188
261
|
);
|
|
189
262
|
console.log(
|
|
190
263
|
chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
|
|
191
|
-
|
|
264
|
+
`localhost:${redisPort}`,
|
|
265
|
+
);
|
|
266
|
+
console.log(
|
|
267
|
+
chalk.yellow(
|
|
268
|
+
"\nℹ Nursery includes demo data for trials and showcases",
|
|
269
|
+
),
|
|
192
270
|
);
|
|
193
271
|
} else {
|
|
194
272
|
spinner.text =
|
|
195
|
-
"Starting nursery services (api, postgres, redis)...";
|
|
273
|
+
"Starting nursery services (app, api, postgres, redis)...";
|
|
196
274
|
await runCommandAsync(
|
|
197
|
-
|
|
275
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans`,
|
|
198
276
|
{
|
|
199
277
|
silent: true,
|
|
200
278
|
},
|
|
@@ -211,16 +289,20 @@ export async function startNursery(options) {
|
|
|
211
289
|
|
|
212
290
|
spinner.succeed("Crux Garden Nursery environment started!");
|
|
213
291
|
console.log(
|
|
214
|
-
chalk.hex(SUCCESS_GREEN)("\n✓
|
|
215
|
-
|
|
292
|
+
chalk.hex(SUCCESS_GREEN)("\n✓ App running on:"),
|
|
293
|
+
`http://localhost:${appPort}`,
|
|
294
|
+
);
|
|
295
|
+
console.log(
|
|
296
|
+
chalk.hex(SUCCESS_GREEN)("✓ API running on:"),
|
|
297
|
+
`http://localhost:${apiPort}`,
|
|
216
298
|
);
|
|
217
299
|
console.log(
|
|
218
300
|
chalk.hex(SUCCESS_GREEN)("✓ PostgreSQL running on:"),
|
|
219
|
-
|
|
301
|
+
`localhost:${postgresPort}`,
|
|
220
302
|
);
|
|
221
303
|
console.log(
|
|
222
304
|
chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
|
|
223
|
-
|
|
305
|
+
`localhost:${redisPort}`,
|
|
224
306
|
);
|
|
225
307
|
console.log(
|
|
226
308
|
chalk.yellow(
|
|
@@ -259,7 +341,8 @@ export async function startNursery(options) {
|
|
|
259
341
|
export async function stopNursery() {
|
|
260
342
|
ensureDockerRunning();
|
|
261
343
|
const spinner = ora("Stopping Crux Garden Nursery environment...").start();
|
|
262
|
-
|
|
344
|
+
const envFileFlag = getEnvFileFlag();
|
|
345
|
+
await runCommandAsync(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml down`, {
|
|
263
346
|
silent: true,
|
|
264
347
|
});
|
|
265
348
|
spinner.succeed("Crux Garden Nursery environment stopped!");
|
|
@@ -273,13 +356,16 @@ export async function stopNursery() {
|
|
|
273
356
|
|
|
274
357
|
export async function logsNursery(options) {
|
|
275
358
|
ensureDockerRunning();
|
|
359
|
+
const envFileFlag = getEnvFileFlag();
|
|
360
|
+
const envFileArgs = envFileFlag ? envFileFlag.split(" ") : [];
|
|
361
|
+
|
|
276
362
|
if (options.follow) {
|
|
277
363
|
console.log(
|
|
278
364
|
chalk.gray("Following nursery logs (press Ctrl+C to exit)...\n"),
|
|
279
365
|
);
|
|
280
366
|
const child = spawn(
|
|
281
367
|
"docker-compose",
|
|
282
|
-
["-f", "docker-compose.nursery.yml", "logs", "-f"],
|
|
368
|
+
[...envFileArgs, "-f", "docker-compose.nursery.yml", "logs", "-f"],
|
|
283
369
|
{
|
|
284
370
|
cwd: dockerDir,
|
|
285
371
|
stdio: "inherit",
|
|
@@ -291,7 +377,7 @@ export async function logsNursery(options) {
|
|
|
291
377
|
process.exit(0);
|
|
292
378
|
});
|
|
293
379
|
} else {
|
|
294
|
-
runCommand(
|
|
380
|
+
runCommand(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml logs --tail=100`);
|
|
295
381
|
console.log();
|
|
296
382
|
}
|
|
297
383
|
}
|
|
@@ -316,9 +402,10 @@ export async function cleanNursery() {
|
|
|
316
402
|
const spinner = ora(
|
|
317
403
|
"Cleaning up nursery (removing containers and volumes)...",
|
|
318
404
|
).start();
|
|
405
|
+
const envFileFlag = getEnvFileFlag();
|
|
319
406
|
|
|
320
407
|
await runCommandAsync(
|
|
321
|
-
|
|
408
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml down -v`,
|
|
322
409
|
{
|
|
323
410
|
silent: true,
|
|
324
411
|
ignoreError: true,
|
|
@@ -359,9 +446,10 @@ export async function purgeNursery() {
|
|
|
359
446
|
const spinner = ora(
|
|
360
447
|
"Purging nursery (removing containers, volumes, and images)...",
|
|
361
448
|
).start();
|
|
449
|
+
const envFileFlag = getEnvFileFlag();
|
|
362
450
|
|
|
363
451
|
await runCommandAsync(
|
|
364
|
-
|
|
452
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml down -v --rmi all`,
|
|
365
453
|
{
|
|
366
454
|
silent: true,
|
|
367
455
|
ignoreError: true,
|
|
@@ -383,6 +471,7 @@ export async function updateNursery() {
|
|
|
383
471
|
const spinner = ora(
|
|
384
472
|
"Checking for latest Crux Garden API image from ghcr.io...",
|
|
385
473
|
).start();
|
|
474
|
+
const envFileFlag = getEnvFileFlag();
|
|
386
475
|
|
|
387
476
|
try {
|
|
388
477
|
// Get the current local image digest before pulling
|
|
@@ -394,7 +483,7 @@ export async function updateNursery() {
|
|
|
394
483
|
|
|
395
484
|
// Pull latest images
|
|
396
485
|
spinner.text = "Pulling latest images...";
|
|
397
|
-
await runCommandAsync(
|
|
486
|
+
await runCommandAsync(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml pull`, {
|
|
398
487
|
silent: true,
|
|
399
488
|
});
|
|
400
489
|
|
|
@@ -433,7 +522,7 @@ export async function updateNursery() {
|
|
|
433
522
|
console.log();
|
|
434
523
|
}
|
|
435
524
|
|
|
436
|
-
export async function resetNursery() {
|
|
525
|
+
export async function resetNursery(envVars) {
|
|
437
526
|
ensureDockerRunning();
|
|
438
527
|
console.log(
|
|
439
528
|
chalk.yellow(
|
|
@@ -450,13 +539,30 @@ export async function resetNursery() {
|
|
|
450
539
|
return;
|
|
451
540
|
}
|
|
452
541
|
|
|
542
|
+
// Ensure NURSERY_MODE is always set to true for nursery environment
|
|
543
|
+
process.env.NURSERY_MODE = 'true';
|
|
544
|
+
|
|
545
|
+
// Parse and set environment variables from command line
|
|
546
|
+
if (envVars && envVars.length > 0) {
|
|
547
|
+
console.log(chalk.cyan("\nApplying environment variables:"));
|
|
548
|
+
parseAndSetEnvVars(envVars);
|
|
549
|
+
console.log();
|
|
550
|
+
}
|
|
551
|
+
|
|
453
552
|
const spinner = ora("Resetting nursery environment...").start();
|
|
553
|
+
const envFileFlag = getEnvFileFlag();
|
|
554
|
+
|
|
555
|
+
// Get actual port values from environment
|
|
556
|
+
const appPort = process.env.APP_PORT || "8080";
|
|
557
|
+
const apiPort = process.env.API_PORT || "3000";
|
|
558
|
+
const postgresPort = process.env.POSTGRES_PORT || "5432";
|
|
559
|
+
const redisPort = process.env.REDIS_PORT || "6379";
|
|
454
560
|
|
|
455
561
|
try {
|
|
456
562
|
// Stop and remove volumes
|
|
457
563
|
spinner.text = "Stopping and removing containers/volumes...";
|
|
458
564
|
await runCommandAsync(
|
|
459
|
-
|
|
565
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml down -v`,
|
|
460
566
|
{
|
|
461
567
|
silent: true,
|
|
462
568
|
ignoreError: true,
|
|
@@ -465,14 +571,14 @@ export async function resetNursery() {
|
|
|
465
571
|
|
|
466
572
|
// Pull latest image
|
|
467
573
|
spinner.text = "Pulling latest image...";
|
|
468
|
-
await runCommandAsync(
|
|
574
|
+
await runCommandAsync(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml pull`, {
|
|
469
575
|
silent: true,
|
|
470
576
|
});
|
|
471
577
|
|
|
472
578
|
// Start fresh
|
|
473
579
|
spinner.text = "Starting fresh nursery environment...";
|
|
474
580
|
await runCommandAsync(
|
|
475
|
-
|
|
581
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml up -d --remove-orphans`,
|
|
476
582
|
{
|
|
477
583
|
silent: true,
|
|
478
584
|
},
|
|
@@ -489,16 +595,20 @@ export async function resetNursery() {
|
|
|
489
595
|
|
|
490
596
|
spinner.succeed("Nursery environment reset complete!");
|
|
491
597
|
console.log(
|
|
492
|
-
chalk.hex(SUCCESS_GREEN)("\n✓
|
|
493
|
-
|
|
598
|
+
chalk.hex(SUCCESS_GREEN)("\n✓ App running on:"),
|
|
599
|
+
`http://localhost:${appPort}`,
|
|
600
|
+
);
|
|
601
|
+
console.log(
|
|
602
|
+
chalk.hex(SUCCESS_GREEN)("✓ API running on:"),
|
|
603
|
+
`http://localhost:${apiPort}`,
|
|
494
604
|
);
|
|
495
605
|
console.log(
|
|
496
606
|
chalk.hex(SUCCESS_GREEN)("✓ PostgreSQL running on:"),
|
|
497
|
-
|
|
607
|
+
`localhost:${postgresPort}`,
|
|
498
608
|
);
|
|
499
609
|
console.log(
|
|
500
610
|
chalk.hex(SUCCESS_GREEN)("✓ Redis running on:"),
|
|
501
|
-
|
|
611
|
+
`localhost:${redisPort}`,
|
|
502
612
|
);
|
|
503
613
|
console.log(
|
|
504
614
|
chalk.yellow("\nℹ Fresh nursery with latest demo data loaded"),
|
|
@@ -510,15 +620,16 @@ export async function resetNursery() {
|
|
|
510
620
|
}
|
|
511
621
|
}
|
|
512
622
|
|
|
513
|
-
export async function restartNursery() {
|
|
623
|
+
export async function restartNursery(options = {}, envVars) {
|
|
514
624
|
await stopNursery();
|
|
515
|
-
await startNursery(
|
|
625
|
+
await startNursery(options, envVars);
|
|
516
626
|
}
|
|
517
627
|
|
|
518
628
|
export async function statusNursery() {
|
|
519
629
|
ensureDockerRunning();
|
|
630
|
+
const envFileFlag = getEnvFileFlag();
|
|
520
631
|
console.log(chalk.bold("\nCrux Garden Nursery Environment Status:\n"));
|
|
521
|
-
runCommand(
|
|
632
|
+
runCommand(`docker-compose ${envFileFlag} -f docker-compose.nursery.yml ps`);
|
|
522
633
|
console.log();
|
|
523
634
|
}
|
|
524
635
|
|
|
@@ -545,8 +656,9 @@ export async function connectNurseryApi() {
|
|
|
545
656
|
export async function stopNurseryDb() {
|
|
546
657
|
ensureDockerRunning();
|
|
547
658
|
const spinner = ora("Stopping nursery database services...").start();
|
|
659
|
+
const envFileFlag = getEnvFileFlag();
|
|
548
660
|
await runCommandAsync(
|
|
549
|
-
|
|
661
|
+
`docker-compose ${envFileFlag} -f docker-compose.nursery.yml stop postgres redis`,
|
|
550
662
|
{ silent: true },
|
|
551
663
|
);
|
|
552
664
|
spinner.succeed("Nursery database services stopped!");
|