@lenne.tech/cli 1.21.0 → 1.22.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/docs/commands.md CHANGED
@@ -286,188 +286,201 @@ For mode-aware update workflows after conversion, use:
286
286
 
287
287
  ## Local Development Commands
288
288
 
289
- Orchestrate parallel lt projects on the same machine without port collisions. Each project gets a deterministic port slot derived from its slug; API/App ports are always slot-paired (`3000+slot*10` / `3001+slot*10`). Slot allocation is reproducible across machines (FNV-1a hash) and persisted in `~/.lenneTech/ports.json`.
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 local`
295
+ ### `lt dev`
292
296
 
293
297
  Open the local-orchestration submenu.
294
298
 
295
299
  **Usage:**
296
300
  ```bash
297
- lt local
301
+ lt dev
298
302
  ```
299
303
 
300
- **Alias:** `lt l`
304
+ **Alias:** `lt d`
301
305
 
302
306
  ---
303
307
 
304
- ### `lt local init`
308
+ ### `lt dev install`
305
309
 
306
- Register the current project in the central port registry, optionally patching legacy hardcoded ports to be env-aware.
310
+ One-time per-machine setup. Idempotent re-run anytime to diagnose what's missing.
307
311
 
308
312
  **Usage:**
309
313
  ```bash
310
- lt local init [options]
314
+ lt dev install
311
315
  ```
312
316
 
313
- **Alias:** `lt l i`
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. Detects the workspace layout (monorepo with `projects/api/` + `projects/app/`, or standalone API/App project).
325
- 2. Looks up or allocates a slot via FNV-1a hash of the project slug. If the slot is taken, falls through linearly to the next free slot.
326
- 3. Detects legacy hardcoded ports in `config.env.ts` (`port: 3000`), `nuxt.config.ts` (`port: 3001`, vite proxy `target: 'http://localhost:3000'`), and `playwright.config.ts` (`baseURL`/`host`/`url: 'http://localhost:3001'`). If `--patch` (or interactive confirm), rewrites them to env-overridable form (`Number(process.env.PORT) || 3000`, `process.env.NUXT_API_URL || …`, etc.) — defaults preserved, idempotent.
327
- 4. Persists the entry to `~/.lenneTech/ports.json`.
328
- 5. Adds `.lt-local/` to the project's `.gitignore`.
329
- 6. Injects (or refreshes) a "Local Development (lt local)" port block into `CLAUDE.md` files at the workspace root and inside each subproject — bracketed by HTML comment markers so it can be replaced cleanly when ports change.
320
+ 1. Verifies `caddy` is on PATH (suggests `brew install caddy` if missing).
321
+ 2. Creates `~/.lenneTech/Caddyfile` stub if absent.
322
+ 3. Verifies the Caddy daemon is running (suggests `brew services start caddy`).
323
+ 4. Validates the Caddyfile.
324
+ 5. Reminds you to run `sudo caddy trust` once so browsers accept `https://*.localhost`.
330
325
 
331
- **Examples:**
332
- ```bash
333
- # Inside a workspace or standalone project
334
- lt local init
326
+ ---
335
327
 
336
- # Force slot 5 for predictable cross-team ports
337
- lt local init --slot 5 --noConfirm --patch
328
+ ### `lt dev migrate`
338
329
 
339
- # Register without touching any source files
340
- lt local init --no-patch --noConfirm
330
+ 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`.
331
+
332
+ **Usage:**
333
+ ```bash
334
+ lt dev migrate
341
335
  ```
342
336
 
337
+ **Alias:** `lt d m`
338
+
339
+ **What it does:**
340
+ 1. Detects the workspace layout (monorepo `projects/api`+`projects/app`, or standalone).
341
+ 2. Builds the project identity (slug from `package.json` "name", subdomains).
342
+ 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.
343
+ 4. Persists the entry to `~/.lenneTech/projects.json` (override path via `LT_DEV_REGISTRY_PATH`).
344
+ 5. Adds `.lt-dev/` to the project's `.gitignore`.
345
+ 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.
346
+
343
347
  ---
344
348
 
345
- ### `lt local up`
349
+ ### `lt dev up`
346
350
 
347
- Start the API + App with project-specific ports. Spawns `pnpm start` (api) and `pnpm dev` (app) detached; persists PIDs to `<root>/.lt-local/state.json`.
351
+ Start API + App behind Caddy. Allocates internal ports (4000+), spawns processes detached, persists PIDs to `<root>/.lt-dev/state.json`.
348
352
 
349
353
  **Usage:**
350
354
  ```bash
351
- lt local up
355
+ lt dev up
352
356
  ```
353
357
 
354
- **Alias:** `lt l u`
358
+ **Alias:** `lt d u`
355
359
 
356
- **Environment variables injected into both children:**
360
+ **Environment variables injected:**
357
361
  | Variable | Consumer | Example value |
358
362
  |----------|----------|---------------|
359
- | `PORT` | Nest (api) / Nuxt dev server (app) | slot-derived |
360
- | `BASE_URL` | nest-server config.env.ts (canonical API base) | `http://localhost:3030` |
361
- | `APP_URL` | nest-server config.env.ts (frontend origin for redirects/CORS) | `http://localhost:3031` |
362
- | `NUXT_API_URL` | Nuxt vite-proxy target for `/api`, `/iam`, … | `http://localhost:3030` |
363
- | `NUXT_PUBLIC_API_URL` | Nuxt `useRuntimeConfig().public.apiUrl` | `http://localhost:3030` |
364
- | `NUXT_PUBLIC_SITE_URL` | Nuxt `useRuntimeConfig().public.siteUrl` + Playwright | `http://localhost:3031` |
365
- | `NUXT_PUBLIC_STORAGE_PREFIX` | namespaces sessionStorage/localStorage so parallel projects don't share auth tokens | `crm-local` |
366
- | `NSC__MONGOOSE__URI` | nest-server-config Mongoose URI (only when `dbName` is known) | `mongodb://127.0.0.1/crm-local` |
367
-
368
- **Override the binary** used for both spawns by setting `LT_PNPM_BIN` (e.g. `LT_PNPM_BIN=/usr/local/bin/pnpm lt local up`).
363
+ | `PORT` | Nest (api) / Nuxt (app) | auto-allocated 4000+ |
364
+ | `BASE_URL` / `NSC__BASE_URL` | nest-server canonical API URL | `https://api.crm.localhost` |
365
+ | `APP_URL` / `NSC__APP_URL` | nest-server frontend origin (CORS, BetterAuth) | `https://crm.localhost` |
366
+ | `NUXT_API_URL` | Nuxt vite-proxy target for `/api`, `/iam`, … | `https://api.crm.localhost` |
367
+ | `NUXT_PUBLIC_API_URL` | Nuxt `useRuntimeConfig().public.apiUrl` | `https://api.crm.localhost` |
368
+ | `NUXT_PUBLIC_SITE_URL` | Nuxt `useRuntimeConfig().public.siteUrl` + Playwright | `https://crm.localhost` |
369
+ | `NUXT_PUBLIC_STORAGE_PREFIX` | namespaces sessionStorage/localStorage | `crm` |
370
+ | `NUXT_PUBLIC_API_PROXY` | always `false` Caddy + cookie-domain make it obsolete | `false` |
371
+ | `NSC__MONGOOSE__URI` | nest-server Mongoose URI | `mongodb://127.0.0.1/crm-local` |
372
+ | `DATABASE_URL` | Postgres convenience URL (for nest-base-style projects) | `postgresql://crm-local:crm-local@localhost:5432/crm-local` |
373
+
374
+ **Override the binary** for both spawns via `LT_PNPM_BIN` (e.g. `LT_PNPM_BIN=/usr/local/bin/pnpm lt dev up`).
369
375
 
370
376
  **Pre-flight guards (exit code 1 each):**
371
- - Project not registered (`lt local init` first)
372
- - Already running (run `lt local down` first)
373
- - Port already in use by another process
377
+ - Caddy not installed (`lt dev install` first)
378
+ - Caddy daemon not running (`brew services start caddy`)
379
+ - Already running for this project (`lt dev down` first)
380
+ - Internal port already in use
374
381
 
375
- **Logs:** `<root>/.lt-local/api.log`, `<root>/.lt-local/app.log` (append-mode).
382
+ **Logs:** `<root>/.lt-dev/api.log`, `<root>/.lt-dev/app.log` (append-mode).
376
383
 
377
384
  ---
378
385
 
379
- ### `lt local down`
386
+ ### `lt dev down`
380
387
 
381
- Stop processes started by `lt local up`. Sends `SIGTERM` to the detached process group (negative PID) so descendants — Vite, the Nest watcher, etc. — receive the signal too. Falls back to single-PID kill if the process group send fails (`EPERM`).
388
+ Stop processes started by `lt dev up` and remove the project's Caddy block.
382
389
 
383
390
  **Usage:**
384
391
  ```bash
385
- lt local down
392
+ lt dev down
386
393
  ```
387
394
 
388
- **Alias:** `lt l d`
395
+ **Alias:** `lt d d`
389
396
 
390
- PID values from `state.json` are validated (positive integer in `[100, 2^31)`) before any signal is sent, so a tampered state file cannot cause `lt local down` to signal arbitrary process groups.
397
+ 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
398
 
392
399
  ---
393
400
 
394
- ### `lt local status`
401
+ ### `lt dev status`
395
402
 
396
- Show what is registered + running for the current project: slot, ports, db URI, PIDs (alive/dead), and live `lsof` state of the assigned ports.
403
+ Show what is registered + running.
397
404
 
398
405
  **Usage:**
399
406
  ```bash
400
- lt local status
407
+ lt dev status # current project
408
+ lt dev status --all # every project in the registry
401
409
  ```
402
410
 
403
- **Alias:** `lt l s`
404
-
405
- ---
411
+ **Alias:** `lt d s`
406
412
 
407
- ## Ports Commands
413
+ 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
414
 
409
- Inspect the port registry and currently-bound dev ports across all your lt projects. Useful for diagnosing collisions and rebuilding the registry from disk.
415
+ ---
410
416
 
411
- ### `lt ports`
417
+ ### `lt dev doctor`
412
418
 
413
- List all reserved registry entries side-by-side with the live `lsof` state. Issues a single `lsof` call internally for the entire slot range (3000–3899) instead of per port — runs in ~150ms regardless of how many projects are registered.
419
+ Diagnose Caddy / CA / DNS / port issues. Exit code 0 = all green, 1 = at least one FAIL.
414
420
 
415
421
  **Usage:**
416
422
  ```bash
417
- lt ports
423
+ lt dev doctor
418
424
  ```
419
425
 
420
- **Alias:** `lt p`
426
+ **Alias:** `lt d doc`
421
427
 
422
- **Output sections:**
423
- 1. **Reserved ports (registry)** — every project entry with a `●` (bound) or `○` (free) indicator per port.
424
- 2. **Currently bound dev ports (3000–3899)** every port in the slot range that currently has a LISTEN socket, with command + PID + owning registry entry (if any).
428
+ **Checks:**
429
+ 1. `caddy` on PATH
430
+ 2. Caddy daemon running (admin endpoint `:2019` reachable)
431
+ 3. Caddyfile validates
432
+ 4. Ports 80 + 443 free or held by Caddy
433
+ 5. `*.localhost` resolves to `127.0.0.1` (RFC 6761)
434
+ 6. Registry status
425
435
 
426
436
  ---
427
437
 
428
- ### `lt ports check <port>`
438
+ ### `lt dev test`
429
439
 
430
- Exit-coded port probe useful in shell scripts.
440
+ 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.
431
441
 
432
442
  **Usage:**
433
443
  ```bash
434
- lt ports check <port>
444
+ lt dev test # App E2E (projects/app)
445
+ lt dev test --api # API E2E (projects/api) — no Caddy required
446
+ lt dev test --teardown # plus `lt dev down` after
447
+ lt dev test --debug # PWDEBUG=1 + HEADED=1
448
+ lt dev test -- --ui spec.ts # everything after `--` is forwarded to playwright
435
449
  ```
436
450
 
437
- **Exit codes:**
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 |
451
+ **Alias:** `lt d t`
443
452
 
444
- **Example:**
445
- ```bash
446
- if lt ports check 3000; then
447
- echo "API port free"
448
- else
449
- echo "API port already bound"
450
- fi
451
- ```
453
+ **Behaviour:**
454
+ 1. Pre-flight: Caddy installed + daemon running (App mode only).
455
+ 2. If no `lt dev up` session is alive: invokes `lt dev up` first.
456
+ 3. Waits up to 30 s for the App URL to respond.
457
+ 4. Reads `<root>/.lt-dev/.env` and merges into the spawn env (existing process.env wins for keys it defines).
458
+ 5. Spawns `pnpm run test:e2e [forwarded args]` in `projects/api` (with `--api`) or `projects/app` (default).
459
+ 6. With `--teardown`, runs `lt dev down` after.
460
+
461
+ **When to use this vs. `pnpm run test:e2e` directly:**
462
+ - Use **`lt dev test`** for TDD loops, ad-hoc reproduction, or when you want a single-command "ensure-up + run + teardown" flow.
463
+ - 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.
452
464
 
453
465
  ---
454
466
 
455
- ### `lt ports scan [dir]`
467
+ ### ENV bridge for external test runners
456
468
 
457
- Rebuild the registry from the filesystem. Walks the given directory (default: cwd) up to depth 3, looking for `lt.config.json` + `package.json` pairs or workspace markers (`pnpm-workspace.yaml`, `projects/`). Re-allocates a slot for new projects; preserves slots for existing entries (only refreshing the path if it moved). Writes only when the registry actually changed (no mtime churn for cloud-sync tools).
469
+ `lt dev up` writes a `<root>/.lt-dev/.env` file with the following keys:
458
470
 
459
- **Usage:**
460
- ```bash
461
- lt ports scan [dir]
462
- ```
471
+ | Key | Source |
472
+ |-----|--------|
473
+ | `BASE_URL`, `APP_URL`, `NSC__BASE_URL`, `NSC__APP_URL` | Identity → `https://api.<slug>.localhost` / `https://<slug>.localhost` |
474
+ | `NUXT_API_URL`, `NUXT_PUBLIC_API_URL`, `NUXT_PUBLIC_SITE_URL` | Same URLs for Nuxt |
475
+ | `NUXT_PUBLIC_STORAGE_PREFIX` | Project slug |
476
+ | `NUXT_PUBLIC_API_PROXY` | Always `false` under `lt dev` |
477
+ | `NSC__MONGOOSE__URI`, `DATABASE_URL` | Project-namespaced DB URI (when `dbName` known) |
478
+ | `LT_DEV_ACTIVE`, `LT_DEV_DB_NAME` | Marker keys for consumers |
479
+ | `NODE_EXTRA_CA_CERTS` | Path to Caddy's root CA cert (auto-detected) |
463
480
 
464
- **Examples:**
465
- ```bash
466
- lt ports scan # scan from cwd
467
- lt ports scan ~/code/lenneTech # scan a specific tree
468
- ```
481
+ `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
482
 
470
- Symlinks are skipped to avoid traversal loops; dotdirs and `node_modules` are not descended into.
483
+ `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
484
 
472
485
  ---
473
486
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/cli",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "lenne.Tech CLI: lt",
5
5
  "keywords": [
6
6
  "lenne.Tech",
@@ -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;
@@ -1,162 +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 fs_1 = require("fs");
13
- const path_1 = require("path");
14
- const local_patches_1 = require("../../lib/local-patches");
15
- const local_project_1 = require("../../lib/local-project");
16
- const port_registry_1 = require("../../lib/port-registry");
17
- /**
18
- * Register a port slot for the current project + optionally patch
19
- * legacy hardcoded ports.
20
- */
21
- const InitCommand = {
22
- alias: ['i'],
23
- description: 'Register port slot',
24
- hidden: false,
25
- name: 'init',
26
- run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
27
- const { filesystem, parameters, print: { colors, error, info, success, warning }, prompt, } = toolbox;
28
- const layout = (0, local_project_1.resolveLayout)(filesystem.cwd(), filesystem);
29
- if (!layout.apiDir && !layout.appDir) {
30
- error('No API (src/config.env.ts) or App (nuxt.config.ts) project detected at this path.');
31
- if (!parameters.options.fromGluegunMenu)
32
- process.exit(1);
33
- return 'local init: not a project';
34
- }
35
- const slug = (0, port_registry_1.projectSlug)(layout.root);
36
- const registry = (0, port_registry_1.loadRegistry)();
37
- // Determine slot: existing entry > CLI flag > deterministic from slug.
38
- let slot;
39
- const cliSlot = parameters.options.slot !== undefined ? Number(parameters.options.slot) : null;
40
- if (registry.projects[slug]) {
41
- slot = registry.projects[slug].slot;
42
- info(`Project "${slug}" already registered with slot ${slot}.`);
43
- }
44
- else if (cliSlot !== null && Number.isFinite(cliSlot)) {
45
- slot = cliSlot;
46
- }
47
- else {
48
- slot = (0, port_registry_1.allocateSlot)(slug, registry);
49
- }
50
- const ports = (0, port_registry_1.portsForSlot)(slot);
51
- info('');
52
- info(colors.bold('Project layout'));
53
- info(colors.dim('─'.repeat(50)));
54
- info(` name: ${slug}`);
55
- info(` root: ${layout.root}`);
56
- if (layout.apiDir)
57
- info(` api: ${layout.apiDir}`);
58
- if (layout.appDir)
59
- info(` app: ${layout.appDir}`);
60
- info(` slot: ${slot}`);
61
- info(` ports: api=${ports.api} app=${ports.app}`);
62
- // Detect legacy hardcoded ports
63
- const apiPatchFile = layout.apiDir ? (0, local_project_1.apiNeedsPortPatch)(layout.apiDir) : null;
64
- const appPatchFiles = layout.appDir ? (0, local_project_1.appNeedsPortPatch)(layout.appDir) : [];
65
- const filesToPatch = [apiPatchFile, ...appPatchFiles].filter((f) => Boolean(f));
66
- const noConfirm = Boolean(parameters.options.noConfirm);
67
- const noPatch = Boolean(parameters.options.noPatch);
68
- const forcePatch = Boolean(parameters.options.patch);
69
- if (filesToPatch.length > 0 && !noPatch) {
70
- info('');
71
- warning('Files with legacy hardcoded ports detected:');
72
- filesToPatch.forEach((f) => info(` - ${f}`));
73
- info(colors.dim('Patch makes them env-overridable: `process.env.PORT || 3000` etc. — defaults preserved.'));
74
- let doPatch = forcePatch;
75
- if (!doPatch && !noConfirm) {
76
- const ans = yield prompt.confirm('Apply env-aware patches now?', true);
77
- doPatch = Boolean(ans);
78
- }
79
- else if (!doPatch && noConfirm) {
80
- info(colors.dim('Skipping patches (--noConfirm without --patch). Pass --patch to auto-apply.'));
81
- }
82
- if (doPatch) {
83
- for (const file of filesToPatch) {
84
- const result = (0, local_patches_1.autoPatch)(file);
85
- if (result.patched) {
86
- success(`patched ${result.replacements}× in ${file}`);
87
- }
88
- else {
89
- info(colors.dim(`skipped (already patched): ${file}`));
90
- }
91
- }
92
- }
93
- }
94
- else if (filesToPatch.length === 0) {
95
- info(colors.dim(' patches: not needed (already env-aware)'));
96
- }
97
- // Persist to registry only when something actually changed — avoids
98
- // mtime churn on ~/.lenneTech/ports.json for cloud-sync tools (Dropbox,
99
- // iCloud Drive, Syncthing) and editor "file changed externally" prompts.
100
- const dbName = deriveDbName(layout, slug);
101
- const existing = registry.projects[slug];
102
- const next = { dbName, path: layout.root, ports, slot };
103
- const changed = !existing ||
104
- existing.path !== next.path ||
105
- existing.slot !== next.slot ||
106
- existing.dbName !== next.dbName ||
107
- existing.ports.api !== next.ports.api ||
108
- existing.ports.app !== next.ports.app;
109
- if (changed) {
110
- registry.projects[slug] = next;
111
- (0, port_registry_1.saveRegistry)(registry);
112
- }
113
- // Add .lt-local/ to .gitignore (idempotent)
114
- addToGitignore(layout.root, '.lt-local/');
115
- // Patch CLAUDE.md files (workspace + each subproject) with the active
116
- // port block so future Claude Code sessions read the correct ports
117
- // even when the lt-dev plugin's hook is not active.
118
- const claudeMdCandidates = [
119
- (0, path_1.join)(layout.root, 'CLAUDE.md'),
120
- ...(layout.apiDir ? [(0, path_1.join)(layout.apiDir, 'CLAUDE.md')] : []),
121
- ...(layout.appDir ? [(0, path_1.join)(layout.appDir, 'CLAUDE.md')] : []),
122
- ];
123
- const claudePatches = claudeMdCandidates
124
- .map((file) => (0, local_patches_1.patchClaudeMd)(file, { apiPort: ports.api, appPort: ports.app, dbName, slug }))
125
- .filter((r) => r.patched);
126
- if (claudePatches.length > 0) {
127
- claudePatches.forEach((r) => success(`updated CLAUDE.md port block: ${r.file}`));
128
- }
129
- info('');
130
- success(`Registered. Run \`lt local up\` to start.`);
131
- if (!parameters.options.fromGluegunMenu)
132
- process.exit();
133
- return `local init ${slug} slot=${slot}`;
134
- }),
135
- };
136
- /** Append entry to .gitignore if not already present. */
137
- function addToGitignore(root, entry) {
138
- const path = (0, path_1.join)(root, '.gitignore');
139
- let content = '';
140
- if ((0, fs_1.existsSync)(path))
141
- content = (0, fs_1.readFileSync)(path, 'utf8');
142
- const lines = content.split(/\r?\n/);
143
- if (lines.some((l) => l.trim() === entry || l.trim() === entry.replace(/\/$/, '')))
144
- return;
145
- const ensured = `${(content.endsWith('\n') || content.length === 0 ? content : `${content}\n`) + entry}\n`;
146
- (0, fs_1.writeFileSync)(path, ensured, 'utf8');
147
- }
148
- /** Derive a sensible default DB name from project + workspace shape. */
149
- function deriveDbName(layout, slug) {
150
- // Reuse existing dbName from the API config if it is the default `${slug}-local`
151
- if (layout.apiDir) {
152
- const cfg = (0, path_1.join)(layout.apiDir, 'src', 'config.env.ts');
153
- if ((0, fs_1.existsSync)(cfg)) {
154
- const content = (0, fs_1.readFileSync)(cfg, 'utf8');
155
- const match = content.match(/dbName:\s*['"`]([^'"`]+)['"`]/);
156
- if (match)
157
- return match[1];
158
- }
159
- }
160
- return `${slug}-local`;
161
- }
162
- module.exports = InitCommand;
@@ -1,69 +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
- * Show what is running for the current project.
16
- */
17
- const StatusCommand = {
18
- alias: ['s'],
19
- description: 'Show local status',
20
- hidden: false,
21
- name: 'status',
22
- run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
23
- var _a, _b;
24
- const { filesystem, parameters, print: { colors, info, warning }, } = toolbox;
25
- const layout = (0, local_project_1.resolveLayout)(filesystem.cwd(), filesystem);
26
- const slug = (0, port_registry_1.projectSlug)(layout.root);
27
- const registry = (0, port_registry_1.loadRegistry)();
28
- const entry = registry.projects[slug];
29
- info('');
30
- info(colors.bold(`Local status: ${slug}`));
31
- info(colors.dim('─'.repeat(50)));
32
- if (!entry) {
33
- warning('Not registered. Run `lt local init` first.');
34
- if (!parameters.options.fromGluegunMenu)
35
- process.exit();
36
- return 'local status: not registered';
37
- }
38
- const ports = (0, port_registry_1.portsForSlot)(entry.slot);
39
- info(` slot: ${entry.slot}`);
40
- info(` api: http://localhost:${ports.api}`);
41
- info(` app: http://localhost:${ports.app}`);
42
- if (entry.dbName)
43
- info(` db: mongodb://127.0.0.1/${entry.dbName}`);
44
- const state = (0, port_registry_1.loadLocalState)(layout.root);
45
- info('');
46
- if (!state || (!state.pids.api && !state.pids.app)) {
47
- info(colors.dim(' no `lt local up` session active'));
48
- }
49
- else {
50
- const apiAlive = state.pids.api ? (0, port_registry_1.isPidAlive)(state.pids.api) : false;
51
- const appAlive = state.pids.app ? (0, port_registry_1.isPidAlive)(state.pids.app) : false;
52
- info(` api: ${apiAlive ? colors.green('running') : colors.red('dead')} (pid ${(_a = state.pids.api) !== null && _a !== void 0 ? _a : '-'})`);
53
- info(` app: ${appAlive ? colors.green('running') : colors.red('dead')} (pid ${(_b = state.pids.app) !== null && _b !== void 0 ? _b : '-'})`);
54
- info(colors.dim(` started: ${state.startedAt}`));
55
- }
56
- info('');
57
- info(colors.bold('Live port state'));
58
- const liveSnapshot = yield (0, port_registry_1.listenSnapshot)([ports.api, ports.app]);
59
- for (const port of [ports.api, ports.app]) {
60
- const r = liveSnapshot.get(port);
61
- info(` ${port}: ${r ? colors.green(`bound to ${r.command} (pid ${r.pid})`) : colors.dim('free')}`);
62
- }
63
- info('');
64
- if (!parameters.options.fromGluegunMenu)
65
- process.exit();
66
- return `local status ${slug}`;
67
- }),
68
- };
69
- module.exports = StatusCommand;