@lenne.tech/cli 1.21.0 → 1.23.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 +16 -10
- package/build/commands/{local/local.js → dev/dev.js} +14 -10
- package/build/commands/dev/doctor.js +144 -0
- package/build/commands/dev/down.js +76 -0
- package/build/commands/dev/install.js +172 -0
- package/build/commands/dev/migrate.js +96 -0
- package/build/commands/dev/status.js +153 -0
- package/build/commands/dev/test.js +191 -0
- package/build/commands/dev/tunnel.js +142 -0
- package/build/commands/dev/uninstall.js +92 -0
- package/build/commands/dev/up.js +228 -0
- package/build/commands/fullstack/init.js +36 -2
- package/build/commands/status.js +16 -16
- package/build/lib/caddy.js +183 -0
- package/build/lib/cloudflared.js +129 -0
- package/build/lib/dev-env-bridge.js +112 -0
- package/build/lib/dev-env.js +65 -0
- package/build/lib/dev-identity.js +100 -0
- package/build/lib/dev-migrate-helper.js +81 -0
- package/build/lib/dev-patches.js +190 -0
- package/build/lib/dev-process.js +152 -0
- package/build/lib/{local-project.js → dev-project.js} +23 -38
- package/build/lib/dev-service.js +414 -0
- package/build/lib/dev-state.js +152 -0
- package/docs/commands.md +167 -94
- package/package.json +1 -1
- package/build/commands/local/down.js +0 -71
- package/build/commands/local/init.js +0 -162
- package/build/commands/local/status.js +0 -69
- package/build/commands/local/up.js +0 -148
- package/build/commands/ports/ports.js +0 -118
- package/build/commands/ports/scan.js +0 -131
- package/build/lib/local-patches.js +0 -175
- package/build/lib/port-registry.js +0 -304
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.paths = void 0;
|
|
4
|
+
exports.allocateInternalPort = allocateInternalPort;
|
|
5
|
+
exports.clearSession = clearSession;
|
|
6
|
+
exports.isPidAlive = isPidAlive;
|
|
7
|
+
exports.isValidPid = isValidPid;
|
|
8
|
+
exports.loadRegistry = loadRegistry;
|
|
9
|
+
exports.loadSession = loadSession;
|
|
10
|
+
exports.saveRegistry = saveRegistry;
|
|
11
|
+
exports.saveSession = saveSession;
|
|
12
|
+
exports.takenInternalPorts = takenInternalPorts;
|
|
13
|
+
/**
|
|
14
|
+
* State persistence for `lt dev`.
|
|
15
|
+
*
|
|
16
|
+
* Two stores:
|
|
17
|
+
* - Central registry at `~/.lenneTech/projects.json` — index of all
|
|
18
|
+
* known projects, used by `lt dev status --all`, the Claude Code
|
|
19
|
+
* plugin hook, and conflict detection.
|
|
20
|
+
* - Per-project state at `<root>/.lt-dev/state.json` — PIDs of the
|
|
21
|
+
* currently running `lt dev up` session.
|
|
22
|
+
*
|
|
23
|
+
* Both files are JSON, atomically written, and schema-versioned.
|
|
24
|
+
*/
|
|
25
|
+
const fs_1 = require("fs");
|
|
26
|
+
const os_1 = require("os");
|
|
27
|
+
const path_1 = require("path");
|
|
28
|
+
const REGISTRY_PATH = process.env.LT_DEV_REGISTRY_PATH || (0, path_1.join)((0, os_1.homedir)(), '.lenneTech', 'projects.json');
|
|
29
|
+
const SESSION_DIR = '.lt-dev';
|
|
30
|
+
const SESSION_FILE = 'state.json';
|
|
31
|
+
/**
|
|
32
|
+
* Allocate a free internal port for a Caddy upstream.
|
|
33
|
+
*
|
|
34
|
+
* Strategy: try sequential ports starting from `start`, skipping any
|
|
35
|
+
* that are already in use. The range 4000-4999 is conventional for
|
|
36
|
+
* lt dev internal ports — well above the deprecated 3000/3001 range
|
|
37
|
+
* and safely below most reserved/system ranges.
|
|
38
|
+
*/
|
|
39
|
+
function allocateInternalPort(start, taken) {
|
|
40
|
+
for (let p = start; p < start + 1000; p++) {
|
|
41
|
+
if (!taken.has(p))
|
|
42
|
+
return p;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`No free internal port in range [${start}, ${start + 1000})`);
|
|
45
|
+
}
|
|
46
|
+
/** Remove session state file (called by `lt dev down`). */
|
|
47
|
+
function clearSession(root) {
|
|
48
|
+
const file = (0, path_1.join)(root, SESSION_DIR, SESSION_FILE);
|
|
49
|
+
if ((0, fs_1.existsSync)(file)) {
|
|
50
|
+
try {
|
|
51
|
+
(0, fs_1.rmSync)(file);
|
|
52
|
+
}
|
|
53
|
+
catch (_a) {
|
|
54
|
+
/* best-effort */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Check whether a process with the given PID is currently alive. */
|
|
59
|
+
function isPidAlive(pid) {
|
|
60
|
+
if (!isValidPid(pid))
|
|
61
|
+
return false;
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 0);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch (_a) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Validate a PID — positive integer, within plausible range. */
|
|
71
|
+
function isValidPid(pid) {
|
|
72
|
+
return typeof pid === 'number' && Number.isInteger(pid) && pid > 0 && pid < 4194304;
|
|
73
|
+
}
|
|
74
|
+
/** Load the central registry; returns an empty one if missing or unreadable. */
|
|
75
|
+
function loadRegistry() {
|
|
76
|
+
if (!(0, fs_1.existsSync)(REGISTRY_PATH))
|
|
77
|
+
return { projects: {}, version: 1 };
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(REGISTRY_PATH, 'utf8'));
|
|
80
|
+
if (parsed && typeof parsed === 'object' && parsed.version === 1 && typeof parsed.projects === 'object') {
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
/* fall through */
|
|
86
|
+
}
|
|
87
|
+
return { projects: {}, version: 1 };
|
|
88
|
+
}
|
|
89
|
+
/** Load session state for a project root. */
|
|
90
|
+
function loadSession(root) {
|
|
91
|
+
const file = (0, path_1.join)(root, SESSION_DIR, SESSION_FILE);
|
|
92
|
+
if (!(0, fs_1.existsSync)(file))
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(file, 'utf8'));
|
|
96
|
+
if (parsed &&
|
|
97
|
+
typeof parsed === 'object' &&
|
|
98
|
+
typeof parsed.pids === 'object' &&
|
|
99
|
+
typeof parsed.startedAt === 'string') {
|
|
100
|
+
// Validate PIDs
|
|
101
|
+
const pids = {};
|
|
102
|
+
if (isValidPid(parsed.pids.api))
|
|
103
|
+
pids.api = parsed.pids.api;
|
|
104
|
+
if (isValidPid(parsed.pids.app))
|
|
105
|
+
pids.app = parsed.pids.app;
|
|
106
|
+
return { pids, startedAt: parsed.startedAt };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (_a) {
|
|
110
|
+
/* fall through */
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** Atomically persist the registry. */
|
|
115
|
+
function saveRegistry(reg) {
|
|
116
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(REGISTRY_PATH), { recursive: true });
|
|
117
|
+
const tmp = `${REGISTRY_PATH}.tmp`;
|
|
118
|
+
(0, fs_1.writeFileSync)(tmp, JSON.stringify(reg, null, 2), 'utf8');
|
|
119
|
+
// rename is atomic on POSIX
|
|
120
|
+
(0, fs_1.writeFileSync)(REGISTRY_PATH, (0, fs_1.readFileSync)(tmp, 'utf8'), 'utf8');
|
|
121
|
+
try {
|
|
122
|
+
(0, fs_1.rmSync)(tmp);
|
|
123
|
+
}
|
|
124
|
+
catch (_a) {
|
|
125
|
+
/* best-effort */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Persist session state for a project root. */
|
|
129
|
+
function saveSession(root, state) {
|
|
130
|
+
const dir = (0, path_1.join)(root, SESSION_DIR);
|
|
131
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
132
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(dir, SESSION_FILE), JSON.stringify(state, null, 2), 'utf8');
|
|
133
|
+
}
|
|
134
|
+
/** Collect all internal ports already claimed across the registry. */
|
|
135
|
+
function takenInternalPorts(reg, excludeSlug) {
|
|
136
|
+
const ports = new Set();
|
|
137
|
+
for (const [slug, entry] of Object.entries(reg.projects)) {
|
|
138
|
+
if (slug === excludeSlug)
|
|
139
|
+
continue;
|
|
140
|
+
if (entry.internalPorts.api)
|
|
141
|
+
ports.add(entry.internalPorts.api);
|
|
142
|
+
if (entry.internalPorts.app)
|
|
143
|
+
ports.add(entry.internalPorts.app);
|
|
144
|
+
}
|
|
145
|
+
return ports;
|
|
146
|
+
}
|
|
147
|
+
/** Path constants exported for tests + status displays. */
|
|
148
|
+
exports.paths = {
|
|
149
|
+
registry: REGISTRY_PATH,
|
|
150
|
+
sessionDir: SESSION_DIR,
|
|
151
|
+
sessionFile: SESSION_FILE,
|
|
152
|
+
};
|
package/docs/commands.md
CHANGED
|
@@ -286,188 +286,261 @@ For mode-aware update workflows after conversion, use:
|
|
|
286
286
|
|
|
287
287
|
## Local Development Commands
|
|
288
288
|
|
|
289
|
-
|
|
289
|
+
Run multiple lt projects in parallel without port collisions or cross-wiring.
|
|
290
|
+
**URL-first**: every project gets stable HTTPS URLs (`<slug>.localhost`,
|
|
291
|
+
`api.<slug>.localhost`) served by Caddy. Internal ports are opaque and
|
|
292
|
+
auto-allocated. Cross-project state (database, storage, cookies) is
|
|
293
|
+
namespaced by slug so projects cannot accidentally interfere.
|
|
290
294
|
|
|
291
|
-
### `lt
|
|
295
|
+
### `lt dev`
|
|
292
296
|
|
|
293
297
|
Open the local-orchestration submenu.
|
|
294
298
|
|
|
295
299
|
**Usage:**
|
|
296
300
|
```bash
|
|
297
|
-
lt
|
|
301
|
+
lt dev
|
|
298
302
|
```
|
|
299
303
|
|
|
300
|
-
**Alias:** `lt
|
|
304
|
+
**Alias:** `lt d`
|
|
301
305
|
|
|
302
306
|
---
|
|
303
307
|
|
|
304
|
-
### `lt
|
|
308
|
+
### `lt dev install`
|
|
305
309
|
|
|
306
|
-
|
|
310
|
+
One-time per-machine setup. Idempotent — re-run anytime to diagnose what's missing. Owns the full Caddy lifecycle via a dedicated LaunchAgent (macOS) / systemd-user unit (Linux) — **does not** use `brew services caddy`, whose hardcoded `/opt/homebrew/etc/Caddyfile` path would crash-loop against our `~/.lenneTech/Caddyfile`.
|
|
307
311
|
|
|
308
312
|
**Usage:**
|
|
309
313
|
```bash
|
|
310
|
-
lt
|
|
314
|
+
lt dev install
|
|
311
315
|
```
|
|
312
316
|
|
|
313
|
-
**Alias:** `lt
|
|
314
|
-
|
|
315
|
-
**Options:**
|
|
316
|
-
| Option | Description |
|
|
317
|
-
|--------|-------------|
|
|
318
|
-
| `--slot <n>` | Force a specific slot index (0..89) instead of the deterministic slug hash |
|
|
319
|
-
| `--patch` | Apply env-aware port patches non-interactively |
|
|
320
|
-
| `--no-patch` | Skip the patch detection / prompt entirely |
|
|
321
|
-
| `--noConfirm` | Skip confirmation prompts (without `--patch`, patches are skipped) |
|
|
317
|
+
**Alias:** `lt d i`
|
|
322
318
|
|
|
323
319
|
**What it does:**
|
|
324
|
-
1.
|
|
325
|
-
2.
|
|
326
|
-
3. Detects
|
|
327
|
-
4.
|
|
328
|
-
|
|
329
|
-
|
|
320
|
+
1. Verifies `caddy` is on PATH (suggests `brew install caddy` if missing).
|
|
321
|
+
2. Creates `~/.lenneTech/Caddyfile` stub if absent.
|
|
322
|
+
3. Detects a conflicting `brew services caddy` registration and asks you to stop it.
|
|
323
|
+
4. Writes + bootstraps a dedicated service:
|
|
324
|
+
- **macOS:** `~/Library/LaunchAgents/tech.lenne.lt-dev-caddy.plist` via `launchctl bootstrap gui/<uid>`.
|
|
325
|
+
- **Linux:** `~/.config/systemd/user/lt-dev-caddy.service` via `systemctl --user enable --now`.
|
|
326
|
+
5. Waits up to 8s for Caddy's admin endpoint (`http://127.0.0.1:2019/config/`) to respond.
|
|
327
|
+
6. Validates the Caddyfile.
|
|
328
|
+
7. Reminds you to run the CA trust command **with HOME preserved**:
|
|
329
|
+
```bash
|
|
330
|
+
sudo -E HOME="$HOME" caddy trust
|
|
331
|
+
```
|
|
332
|
+
Without `-E HOME="$HOME"`, sudo switches HOME to `/var/root` and caddy cannot find its user-scoped CA — this was the bug that blocked the very first install attempt.
|
|
330
333
|
|
|
331
|
-
**
|
|
334
|
+
**Logs:** `~/.lenneTech/caddy.log`, `~/.lenneTech/caddy.err.log`.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### `lt dev uninstall`
|
|
339
|
+
|
|
340
|
+
Symmetric counterpart to `lt dev install`. Removes the LaunchAgent / systemd-user unit and stops the Caddy daemon. Does **not** remove the caddy binary itself.
|
|
341
|
+
|
|
342
|
+
**Usage:**
|
|
332
343
|
```bash
|
|
333
|
-
#
|
|
334
|
-
lt
|
|
344
|
+
lt dev uninstall # interactive: asks whether to purge Caddyfile + logs
|
|
345
|
+
lt dev uninstall --purge # also remove ~/.lenneTech/Caddyfile + caddy logs
|
|
346
|
+
lt dev uninstall --noConfirm # skip the purge prompt (keep files)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Alias:** `lt d un`
|
|
350
|
+
|
|
351
|
+
**What it does NOT touch:**
|
|
352
|
+
- the `caddy` binary (use `brew uninstall caddy` if you want to remove the tool too)
|
|
353
|
+
- per-project state under `<project>/.lt-dev/` (use `lt dev down`)
|
|
354
|
+
- the trusted CA in the system keychain (use `sudo -E HOME="$HOME" caddy untrust` if desired)
|
|
355
|
+
|
|
356
|
+
---
|
|
335
357
|
|
|
336
|
-
|
|
337
|
-
lt local init --slot 5 --noConfirm --patch
|
|
358
|
+
### `lt dev migrate`
|
|
338
359
|
|
|
339
|
-
|
|
340
|
-
|
|
360
|
+
Register an existing project with `lt dev` and apply idempotent env-aware patches. Safe to run multiple times; safe to run after `lt fullstack init`.
|
|
361
|
+
|
|
362
|
+
**Usage:**
|
|
363
|
+
```bash
|
|
364
|
+
lt dev migrate
|
|
341
365
|
```
|
|
342
366
|
|
|
367
|
+
**Alias:** `lt d m`
|
|
368
|
+
|
|
369
|
+
**What it does:**
|
|
370
|
+
1. Detects the workspace layout (monorepo `projects/api`+`projects/app`, or standalone).
|
|
371
|
+
2. Builds the project identity (slug from `package.json` "name", subdomains).
|
|
372
|
+
3. Patches legacy hardcoded ports in `config.env.ts` (`port: 3000`), `nuxt.config.ts` (`port: 3001`, vite proxy target), `playwright.config.ts` (`baseURL`/`host`/`url`) to env-overridable form. Defaults preserved, idempotent.
|
|
373
|
+
4. Persists the entry to `~/.lenneTech/projects.json` (override path via `LT_DEV_REGISTRY_PATH`).
|
|
374
|
+
5. Adds `.lt-dev/` to the project's `.gitignore`.
|
|
375
|
+
6. Injects (or refreshes) a "Local Development (lt dev)" URL block into `CLAUDE.md` files at the workspace root and inside each subproject — bracketed by HTML comment markers.
|
|
376
|
+
|
|
343
377
|
---
|
|
344
378
|
|
|
345
|
-
### `lt
|
|
379
|
+
### `lt dev up`
|
|
346
380
|
|
|
347
|
-
Start
|
|
381
|
+
Start API + App behind Caddy. Allocates internal ports (4000+), spawns processes detached, persists PIDs to `<root>/.lt-dev/state.json`.
|
|
348
382
|
|
|
349
383
|
**Usage:**
|
|
350
384
|
```bash
|
|
351
|
-
lt
|
|
385
|
+
lt dev up
|
|
352
386
|
```
|
|
353
387
|
|
|
354
|
-
**Alias:** `lt
|
|
388
|
+
**Alias:** `lt d u`
|
|
355
389
|
|
|
356
|
-
**Environment variables injected
|
|
390
|
+
**Environment variables injected:**
|
|
357
391
|
| Variable | Consumer | Example value |
|
|
358
392
|
|----------|----------|---------------|
|
|
359
|
-
| `PORT` | Nest (api) / Nuxt
|
|
360
|
-
| `BASE_URL` | nest-server
|
|
361
|
-
| `APP_URL` | nest-server
|
|
362
|
-
| `NUXT_API_URL` | Nuxt vite-proxy target for `/api`, `/iam`, … | `
|
|
363
|
-
| `NUXT_PUBLIC_API_URL` | Nuxt `useRuntimeConfig().public.apiUrl` | `
|
|
364
|
-
| `NUXT_PUBLIC_SITE_URL` | Nuxt `useRuntimeConfig().public.siteUrl` + Playwright | `
|
|
365
|
-
| `NUXT_PUBLIC_STORAGE_PREFIX` | namespaces sessionStorage/localStorage
|
|
366
|
-
| `
|
|
367
|
-
|
|
368
|
-
|
|
393
|
+
| `PORT` | Nest (api) / Nuxt (app) | auto-allocated 4000+ |
|
|
394
|
+
| `BASE_URL` / `NSC__BASE_URL` | nest-server canonical API URL | `https://api.crm.localhost` |
|
|
395
|
+
| `APP_URL` / `NSC__APP_URL` | nest-server frontend origin (CORS, BetterAuth) | `https://crm.localhost` |
|
|
396
|
+
| `NUXT_API_URL` | Nuxt vite-proxy target for `/api`, `/iam`, … | `https://api.crm.localhost` |
|
|
397
|
+
| `NUXT_PUBLIC_API_URL` | Nuxt `useRuntimeConfig().public.apiUrl` | `https://api.crm.localhost` |
|
|
398
|
+
| `NUXT_PUBLIC_SITE_URL` | Nuxt `useRuntimeConfig().public.siteUrl` + Playwright | `https://crm.localhost` |
|
|
399
|
+
| `NUXT_PUBLIC_STORAGE_PREFIX` | namespaces sessionStorage/localStorage | `crm` |
|
|
400
|
+
| `NUXT_PUBLIC_API_PROXY` | always `false` — Caddy + cookie-domain make it obsolete | `false` |
|
|
401
|
+
| `NSC__MONGOOSE__URI` | nest-server Mongoose URI | `mongodb://127.0.0.1/crm-local` |
|
|
402
|
+
| `DATABASE_URL` | Postgres convenience URL (for nest-base-style projects) | `postgresql://crm-local:crm-local@localhost:5432/crm-local` |
|
|
403
|
+
|
|
404
|
+
**Override the binary** for both spawns via `LT_PNPM_BIN` (e.g. `LT_PNPM_BIN=/usr/local/bin/pnpm lt dev up`).
|
|
369
405
|
|
|
370
406
|
**Pre-flight guards (exit code 1 each):**
|
|
371
|
-
-
|
|
372
|
-
-
|
|
373
|
-
-
|
|
407
|
+
- Caddy not installed (`lt dev install` first)
|
|
408
|
+
- Caddy daemon not running (run `lt dev install` — it bootstraps the lt-dev service)
|
|
409
|
+
- Already running for this project (`lt dev down` first)
|
|
410
|
+
- Internal port already in use
|
|
374
411
|
|
|
375
|
-
**Logs:** `<root>/.lt-
|
|
412
|
+
**Logs:** `<root>/.lt-dev/api.log`, `<root>/.lt-dev/app.log` (append-mode).
|
|
376
413
|
|
|
377
414
|
---
|
|
378
415
|
|
|
379
|
-
### `lt
|
|
416
|
+
### `lt dev down`
|
|
380
417
|
|
|
381
|
-
Stop processes started by `lt
|
|
418
|
+
Stop processes started by `lt dev up` and remove the project's Caddy block.
|
|
382
419
|
|
|
383
420
|
**Usage:**
|
|
384
421
|
```bash
|
|
385
|
-
lt
|
|
422
|
+
lt dev down
|
|
386
423
|
```
|
|
387
424
|
|
|
388
|
-
**Alias:** `lt
|
|
425
|
+
**Alias:** `lt d d`
|
|
389
426
|
|
|
390
|
-
|
|
427
|
+
Sends `SIGTERM` to the detached process group (negative PID) so descendants — Vite, the Nest watcher, etc. — receive the signal too. PID values from `state.json` are validated before signaling. Best-effort: removes the project's Caddy block and reloads even if no session was active.
|
|
391
428
|
|
|
392
429
|
---
|
|
393
430
|
|
|
394
|
-
### `lt
|
|
431
|
+
### `lt dev status`
|
|
395
432
|
|
|
396
|
-
Show what is registered + running
|
|
433
|
+
Show what is registered + running.
|
|
397
434
|
|
|
398
435
|
**Usage:**
|
|
399
436
|
```bash
|
|
400
|
-
lt
|
|
437
|
+
lt dev status # current project
|
|
438
|
+
lt dev status --all # every project in the registry
|
|
401
439
|
```
|
|
402
440
|
|
|
403
|
-
**Alias:** `lt
|
|
404
|
-
|
|
405
|
-
---
|
|
441
|
+
**Alias:** `lt d s`
|
|
406
442
|
|
|
407
|
-
|
|
443
|
+
The current-project view shows subdomains → upstream ports, db URI, session PIDs (alive/dead), and live `lsof` state. The `--all` view lists every project, with a `●`/`○` indicator for running state.
|
|
408
444
|
|
|
409
|
-
|
|
445
|
+
---
|
|
410
446
|
|
|
411
|
-
### `lt
|
|
447
|
+
### `lt dev tunnel`
|
|
412
448
|
|
|
413
|
-
|
|
449
|
+
Expose a running `lt dev up` project to the public internet via a Cloudflare Quick Tunnel. Foreground command — runs until Ctrl-C.
|
|
414
450
|
|
|
415
451
|
**Usage:**
|
|
416
452
|
```bash
|
|
417
|
-
lt
|
|
453
|
+
lt dev tunnel # tunnel the App
|
|
454
|
+
lt dev tunnel --api # tunnel the API instead
|
|
418
455
|
```
|
|
419
456
|
|
|
420
|
-
**Alias:** `lt
|
|
457
|
+
**Alias:** `lt d tun`
|
|
421
458
|
|
|
422
|
-
**
|
|
423
|
-
1.
|
|
424
|
-
2.
|
|
459
|
+
**What it does:**
|
|
460
|
+
1. Checks `cloudflared` is on PATH (suggests `brew install cloudflared` otherwise).
|
|
461
|
+
2. Confirms the Caddy daemon is up (`lt dev install` must have run).
|
|
462
|
+
3. Spawns `cloudflared tunnel --url https://<slug>.localhost --http-host-header <slug>.localhost --no-tls-verify`. The host-header rewrite is required — without it Caddy would not match the project block for the random `*.trycloudflare.com` hostname.
|
|
463
|
+
4. Waits for cloudflared to publish the public URL (usually 5-10s) and prints it prominently.
|
|
464
|
+
|
|
465
|
+
**Caveats (also printed at runtime):**
|
|
466
|
+
- Auth cookies on the localhost domain are NOT valid on the `*.trycloudflare.com` URL — users log in again on the tunnel URL.
|
|
467
|
+
- Better-Auth's `trustedOrigins` won't include the random tunnel URL — login flows that validate the origin reject the request unless the URL is added explicitly to the API config.
|
|
468
|
+
- Default tunnels expose ONLY the App. For full external usage (e.g. external client calling the API), start a second `lt dev tunnel --api` in another shell — the API will be reachable on its own `*.trycloudflare.com` URL.
|
|
469
|
+
|
|
470
|
+
**Not yet supported (intentional scope limit):**
|
|
471
|
+
- Named tunnels with a persistent URL (`cloudflared tunnel create`)
|
|
472
|
+
- Multi-host tunneling in one process
|
|
473
|
+
- Background/detached mode
|
|
425
474
|
|
|
426
475
|
---
|
|
427
476
|
|
|
428
|
-
### `lt
|
|
477
|
+
### `lt dev doctor`
|
|
429
478
|
|
|
430
|
-
|
|
479
|
+
Diagnose Caddy / CA / DNS / port issues. Exit code 0 = all green, 1 = at least one FAIL.
|
|
431
480
|
|
|
432
481
|
**Usage:**
|
|
433
482
|
```bash
|
|
434
|
-
lt
|
|
483
|
+
lt dev doctor
|
|
435
484
|
```
|
|
436
485
|
|
|
437
|
-
**
|
|
438
|
-
| Code | Meaning |
|
|
439
|
-
|------|---------|
|
|
440
|
-
| `0` | Port is free |
|
|
441
|
-
| `1` | Port is in use |
|
|
442
|
-
| `2` | `lsof` not available, or `<port>` argument missing/invalid |
|
|
486
|
+
**Alias:** `lt d doc`
|
|
443
487
|
|
|
444
|
-
**
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
```
|
|
488
|
+
**Checks:**
|
|
489
|
+
1. `caddy` on PATH
|
|
490
|
+
2. Caddy daemon running (admin endpoint `:2019` reachable)
|
|
491
|
+
3. Caddyfile validates
|
|
492
|
+
4. Ports 80 + 443 free or held by Caddy
|
|
493
|
+
5. `*.localhost` resolves to `127.0.0.1` (RFC 6761)
|
|
494
|
+
6. Registry status
|
|
452
495
|
|
|
453
496
|
---
|
|
454
497
|
|
|
455
|
-
### `lt
|
|
498
|
+
### `lt dev test`
|
|
456
499
|
|
|
457
|
-
|
|
500
|
+
One-shot E2E wrapper: ensure `up`, wait for the App URL, run `pnpm run test:e2e` with the `.lt-dev/.env` bridge loaded. Optional teardown after.
|
|
458
501
|
|
|
459
502
|
**Usage:**
|
|
460
503
|
```bash
|
|
461
|
-
lt
|
|
504
|
+
lt dev test # App E2E (projects/app)
|
|
505
|
+
lt dev test --api # API E2E (projects/api) — no Caddy required
|
|
506
|
+
lt dev test --teardown # plus `lt dev down` after
|
|
507
|
+
lt dev test --debug # PWDEBUG=1 + HEADED=1
|
|
508
|
+
lt dev test -- --ui spec.ts # everything after `--` is forwarded to playwright
|
|
462
509
|
```
|
|
463
510
|
|
|
464
|
-
**
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
511
|
+
**Alias:** `lt d t`
|
|
512
|
+
|
|
513
|
+
**Behaviour:**
|
|
514
|
+
1. Pre-flight: Caddy installed + daemon running (App mode only).
|
|
515
|
+
2. If no `lt dev up` session is alive: invokes `lt dev up` first.
|
|
516
|
+
3. Waits up to 30 s for the App URL to respond.
|
|
517
|
+
4. Reads `<root>/.lt-dev/.env` and merges into the spawn env (existing process.env wins for keys it defines).
|
|
518
|
+
5. Spawns `pnpm run test:e2e [forwarded args]` in `projects/api` (with `--api`) or `projects/app` (default).
|
|
519
|
+
6. With `--teardown`, runs `lt dev down` after.
|
|
520
|
+
|
|
521
|
+
**When to use this vs. `pnpm run test:e2e` directly:**
|
|
522
|
+
- Use **`lt dev test`** for TDD loops, ad-hoc reproduction, or when you want a single-command "ensure-up + run + teardown" flow.
|
|
523
|
+
- Use **direct `pnpm run test:e2e`** (or VS Code Playwright Extension, IDE test runners) for everyday work — the auto-injected `playwright.config.ts` bridge loads the `.lt-dev/.env` automatically, so the env is correct without the wrapper.
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
### ENV bridge for external test runners
|
|
528
|
+
|
|
529
|
+
`lt dev up` writes a `<root>/.lt-dev/.env` file with the following keys:
|
|
530
|
+
|
|
531
|
+
| Key | Source |
|
|
532
|
+
|-----|--------|
|
|
533
|
+
| `BASE_URL`, `APP_URL`, `NSC__BASE_URL`, `NSC__APP_URL` | Identity → `https://api.<slug>.localhost` / `https://<slug>.localhost` |
|
|
534
|
+
| `NUXT_API_URL`, `NUXT_PUBLIC_API_URL`, `NUXT_PUBLIC_SITE_URL` | Same URLs for Nuxt |
|
|
535
|
+
| `NUXT_PUBLIC_STORAGE_PREFIX` | Project slug |
|
|
536
|
+
| `NUXT_PUBLIC_API_PROXY` | Always `false` under `lt dev` |
|
|
537
|
+
| `NSC__MONGOOSE__URI`, `DATABASE_URL` | Project-namespaced DB URI (when `dbName` known) |
|
|
538
|
+
| `LT_DEV_ACTIVE`, `LT_DEV_DB_NAME` | Marker keys for consumers |
|
|
539
|
+
| `NODE_EXTRA_CA_CERTS` | Path to Caddy's root CA cert (auto-detected) |
|
|
540
|
+
|
|
541
|
+
`lt dev migrate` injects a tiny `// >>> lt-dev:bridge >>>` block at the top of `playwright.config.ts` that loads this file at config-load time — making Playwright (CLI, IDE, VS Code extension) automatically use the `lt dev` URLs and trust the local CA, without inheriting the parent shell.
|
|
469
542
|
|
|
470
|
-
|
|
543
|
+
`lt dev down` removes the bridge file so subsequent runs without `lt dev up` fall back cleanly to the classic `localhost:3000`/`localhost:3001` defaults.
|
|
471
544
|
|
|
472
545
|
---
|
|
473
546
|
|
package/package.json
CHANGED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
const local_project_1 = require("../../lib/local-project");
|
|
13
|
-
const port_registry_1 = require("../../lib/port-registry");
|
|
14
|
-
/**
|
|
15
|
-
* Stop processes started by `lt local up`. Sends SIGTERM to the
|
|
16
|
-
* detached process group (negative PID) so descendants — Vite,
|
|
17
|
-
* the Nest watcher etc. — receive the signal too.
|
|
18
|
-
*/
|
|
19
|
-
const DownCommand = {
|
|
20
|
-
alias: ['d'],
|
|
21
|
-
description: 'Stop API + App',
|
|
22
|
-
hidden: false,
|
|
23
|
-
name: 'down',
|
|
24
|
-
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
-
const { filesystem, parameters, print: { colors, info, success, warning }, } = toolbox;
|
|
26
|
-
const layout = (0, local_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
27
|
-
const state = (0, port_registry_1.loadLocalState)(layout.root);
|
|
28
|
-
if (!state || (!state.pids.api && !state.pids.app)) {
|
|
29
|
-
info(colors.dim('No running processes registered for this project.'));
|
|
30
|
-
if (!parameters.options.fromGluegunMenu)
|
|
31
|
-
process.exit();
|
|
32
|
-
return 'local down: nothing to stop';
|
|
33
|
-
}
|
|
34
|
-
const stopped = [];
|
|
35
|
-
for (const [name, pid] of Object.entries(state.pids)) {
|
|
36
|
-
if (!pid)
|
|
37
|
-
continue;
|
|
38
|
-
// Defense-in-depth: refuse anything that loadLocalState's schema gate
|
|
39
|
-
// wouldn't have accepted. Prevents a tampered state.json from causing
|
|
40
|
-
// process.kill(-pid, …) to signal arbitrary process groups.
|
|
41
|
-
if (!(0, port_registry_1.isValidPid)(pid)) {
|
|
42
|
-
warning(`Refusing to signal suspicious pid ${pid} for ${name} (state.json tampered?)`);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (!(0, port_registry_1.isPidAlive)(pid)) {
|
|
46
|
-
stopped.push(`${name} (pid ${pid}, already dead)`);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
// Negative PID kills the process group of a detached process.
|
|
51
|
-
process.kill(-pid, 'SIGTERM');
|
|
52
|
-
stopped.push(`${name} (pid ${pid})`);
|
|
53
|
-
}
|
|
54
|
-
catch (_a) {
|
|
55
|
-
try {
|
|
56
|
-
process.kill(pid, 'SIGTERM');
|
|
57
|
-
stopped.push(`${name} (pid ${pid}, single)`);
|
|
58
|
-
}
|
|
59
|
-
catch (_b) {
|
|
60
|
-
warning(`Failed to stop ${name} (pid ${pid})`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
(0, port_registry_1.clearLocalState)(layout.root);
|
|
65
|
-
success(`Stopped: ${stopped.join(', ')}`);
|
|
66
|
-
if (!parameters.options.fromGluegunMenu)
|
|
67
|
-
process.exit();
|
|
68
|
-
return `local down: ${stopped.length} stopped`;
|
|
69
|
-
}),
|
|
70
|
-
};
|
|
71
|
-
module.exports = DownCommand;
|