@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 CHANGED
@@ -57,21 +57,27 @@ $ lt status
57
57
  $ lt completion install
58
58
  ```
59
59
 
60
- ## Local dev orchestration (parallel projects)
60
+ ## Local dev orchestration (parallel projects, HTTPS URLs)
61
61
 
62
- Run multiple lt projects on the same machine without manually shuffling ports.
63
- Each project gets a deterministic port slot (`3000+slot*10` / `3001+slot*10`) and
64
- the matching env vars are injected into spawned dev servers automatically.
62
+ Run multiple lt projects on the same machine without port collisions or
63
+ cross-wiring. Each project gets stable HTTPS URLs (`<slug>.localhost`,
64
+ `api.<slug>.localhost`) served by Caddy, and the matching env vars
65
+ (`BASE_URL`, `APP_URL`, `NUXT_PUBLIC_*`, `NSC__MONGOOSE__URI`, …) are
66
+ injected into the spawned dev servers automatically.
65
67
 
66
68
  ```bash
67
- # Once per project:
68
- $ lt local init # register slot + patch legacy hardcoded ports
69
+ # Once per machine:
70
+ $ lt dev install # verify Caddy, prep Caddyfile + local CA
71
+
72
+ # Once per project (idempotent):
73
+ $ lt dev migrate # patch legacy hardcoded ports + register
69
74
 
70
75
  # Daily:
71
- $ lt local up # start API + App
72
- $ lt local status # show running PIDs + bound ports
73
- $ lt local down # SIGTERM the process group
74
- $ lt ports # cross-project port overview
76
+ $ lt dev up # start API + App behind Caddy
77
+ $ lt dev status # current project status
78
+ $ lt dev status --all # all registered projects
79
+ $ lt dev down # SIGTERM the process group + remove Caddy block
80
+ $ lt dev doctor # diagnose Caddy / CA / DNS / port issues
75
81
  ```
76
82
 
77
83
  Full reference: [docs/commands.md → Local Development Commands](docs/commands.md#local-development-commands).
@@ -10,21 +10,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  /**
13
- * Local development orchestration commands (`lt local <subcommand>`).
13
+ * Local development orchestration commands (`lt dev <subcommand>`).
14
14
  *
15
15
  * Subcommands:
16
- * - `init` — register a port slot for this project
17
- * - `up` start API + App with project-specific ports
18
- * - `down` — stop processes started by `up`
19
- * - `status` show what is running
16
+ * - `install` — one-time per-machine setup: caddy + LaunchAgent/systemd unit
17
+ * - `uninstall` remove the lt-dev service (symmetric to install)
18
+ * - `migrate` — register an existing project (idempotent ENV patches)
19
+ * - `up` start API + App behind Caddy with project-specific URLs
20
+ * - `down` — stop the detached processes + remove the Caddy block
21
+ * - `status` — show what is running
22
+ * - `doctor` — diagnose Caddy/CA/DNS/port issues
23
+ * - `tunnel` — Cloudflare quick tunnel to expose a running project publicly
20
24
  */
21
25
  module.exports = {
22
- alias: ['l'],
23
- description: 'Local dev orchestration',
26
+ alias: ['d'],
27
+ description: 'Local dev orchestration (Caddy + per-project URLs)',
24
28
  hidden: false,
25
- name: 'local',
29
+ name: 'dev',
26
30
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
27
- yield toolbox.helper.showMenu('local');
28
- return 'local';
31
+ yield toolbox.helper.showMenu('dev');
32
+ return 'dev';
29
33
  }),
30
34
  };
@@ -0,0 +1,144 @@
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 child_process_1 = require("child_process");
13
+ const caddy_1 = require("../../lib/caddy");
14
+ const dev_process_1 = require("../../lib/dev-process");
15
+ const dev_service_1 = require("../../lib/dev-service");
16
+ const dev_state_1 = require("../../lib/dev-state");
17
+ /**
18
+ * Diagnose Caddy / CA / DNS / port issues for `lt dev`.
19
+ *
20
+ * Categorical output (OK / WARN / FAIL) so developers can quickly see
21
+ * what is missing on a fresh machine. Exit code 0 = all green,
22
+ * 1 = at least one FAIL.
23
+ *
24
+ * Checks our OWN LaunchAgent / systemd-user unit — not
25
+ * `brew services caddy`. The latter cannot host our Caddyfile.
26
+ */
27
+ const DoctorCommand = {
28
+ alias: ['doc'],
29
+ description: 'Diagnose Caddy/CA/DNS/port issues',
30
+ hidden: false,
31
+ name: 'doctor',
32
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
33
+ const { parameters, print: { colors, info }, } = toolbox;
34
+ info('');
35
+ info(colors.bold('lt dev doctor'));
36
+ info(colors.dim('─'.repeat(60)));
37
+ let fails = 0;
38
+ // 1. Caddy installed
39
+ const hasCaddy = yield (0, caddy_1.caddyAvailable)();
40
+ if (hasCaddy)
41
+ line('OK', colors.green, 'caddy on PATH');
42
+ else {
43
+ line('FAIL', colors.red, 'caddy not installed — run `brew install caddy` then `lt dev install`');
44
+ fails++;
45
+ }
46
+ // 2. Service installed (LaunchAgent / systemd-user)
47
+ const plat = (0, dev_service_1.platformSupported)();
48
+ if (plat === 'unsupported') {
49
+ line('WARN', colors.yellow, `service management not supported on ${process.platform} — run caddy manually`);
50
+ }
51
+ else {
52
+ const svc = yield (0, dev_service_1.getServiceStatus)();
53
+ const servicePaths = (0, dev_service_1.getServicePaths)();
54
+ if (svc.installed && svc.loaded) {
55
+ line('OK', colors.green, `lt-dev service loaded (${servicePaths.unitFile})`);
56
+ }
57
+ else if (svc.installed && !svc.loaded) {
58
+ line('FAIL', colors.red, `service file exists but is not loaded — run \`lt dev install\``);
59
+ fails++;
60
+ }
61
+ else {
62
+ line('FAIL', colors.red, `lt-dev service not installed — run \`lt dev install\``);
63
+ fails++;
64
+ }
65
+ }
66
+ // 3. Caddy daemon admin endpoint
67
+ if (hasCaddy) {
68
+ const daemon = yield (0, caddy_1.caddyDaemonRunning)();
69
+ if (daemon)
70
+ line('OK', colors.green, 'caddy admin (:2019) reachable');
71
+ else {
72
+ line('FAIL', colors.red, 'caddy admin (:2019) unreachable — run `lt dev install`');
73
+ fails++;
74
+ }
75
+ }
76
+ // 4. Caddyfile validates
77
+ if (hasCaddy) {
78
+ const v = yield (0, caddy_1.validateCaddyfile)();
79
+ if (v.ok)
80
+ line('OK', colors.green, `Caddyfile valid (${caddy_1.paths.caddyfile})`);
81
+ else
82
+ line('WARN', colors.yellow, `Caddyfile validation: ${v.stderr.split('\n')[0]}`);
83
+ }
84
+ // 4. Port 80 / 443 free or held by Caddy
85
+ for (const port of [80, 443]) {
86
+ const r = yield (0, dev_process_1.checkPortInUse)(port);
87
+ if (r === null)
88
+ line('WARN', colors.yellow, `lsof unavailable — cannot probe port ${port}`);
89
+ else if (!r.inUse)
90
+ line('OK', colors.green, `port ${port} free`);
91
+ else if (r.command === 'caddy')
92
+ line('OK', colors.green, `port ${port} held by caddy (pid ${r.pid})`);
93
+ else {
94
+ line('FAIL', colors.red, `port ${port} held by ${r.command} (pid ${r.pid}) — Caddy cannot bind`);
95
+ fails++;
96
+ }
97
+ }
98
+ // 5. *.localhost resolves to 127.0.0.1
99
+ const dnsOk = yield dnsResolvesLocalhost('lt-dev-doctor.localhost');
100
+ if (dnsOk)
101
+ line('OK', colors.green, '*.localhost resolves to 127.0.0.1 (RFC 6761)');
102
+ else
103
+ line('WARN', colors.yellow, '*.localhost may not resolve — check /etc/hosts or system resolver');
104
+ // 6. Registry
105
+ const reg = (0, dev_state_1.loadRegistry)();
106
+ const count = Object.keys(reg.projects).length;
107
+ line('OK', colors.green, `registry: ${count} project(s) at ${dev_state_1.paths.registry}`);
108
+ info('');
109
+ if (fails > 0)
110
+ info(colors.red(`✗ ${fails} fail(s) — see above`));
111
+ else
112
+ info(colors.green('✓ all checks passed'));
113
+ if (!parameters.options.fromGluegunMenu)
114
+ process.exit(fails > 0 ? 1 : 0);
115
+ return fails > 0 ? `dev doctor: ${fails} fails` : 'dev doctor: ok';
116
+ function line(tag, color, msg) {
117
+ info(` ${color(`[${tag.padEnd(4)}]`)} ${msg}`);
118
+ }
119
+ }),
120
+ };
121
+ /**
122
+ * Probe DNS — RFC 6761 mandates *.localhost MUST resolve to loopback.
123
+ *
124
+ * On macOS the resolver returns `::1` first (IPv6 loopback); on Linux
125
+ * `127.0.0.1` (IPv4) is more common. Both are valid loopback addresses
126
+ * and Caddy listens on both, so we accept either.
127
+ */
128
+ function dnsResolvesLocalhost(host) {
129
+ return new Promise((resolve) => {
130
+ const child = (0, child_process_1.spawn)('node', [
131
+ '-e',
132
+ `require('dns').lookup(${JSON.stringify(host)}, { all: true }, (e, addrs) => {
133
+ if (e) process.exit(1);
134
+ const loopback = (addrs || []).some(a => a.address === '127.0.0.1' || a.address === '::1');
135
+ process.exit(loopback ? 0 : 1);
136
+ });`,
137
+ ], {
138
+ stdio: ['ignore', 'ignore', 'ignore'],
139
+ });
140
+ child.on('error', () => resolve(false));
141
+ child.on('close', (code) => resolve(code === 0));
142
+ });
143
+ }
144
+ module.exports = DoctorCommand;
@@ -0,0 +1,76 @@
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 caddy_1 = require("../../lib/caddy");
13
+ const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
14
+ const dev_identity_1 = require("../../lib/dev-identity");
15
+ const dev_process_1 = require("../../lib/dev-process");
16
+ const dev_project_1 = require("../../lib/dev-project");
17
+ const dev_state_1 = require("../../lib/dev-state");
18
+ /**
19
+ * Stop the processes started by `lt dev up` and remove the project's
20
+ * Caddy block.
21
+ *
22
+ * - SIGTERM is sent to the detached process GROUP (negative PID) so
23
+ * children (Vite, Nest watcher) receive the signal too.
24
+ * - The Caddy block is removed and `caddy reload` is invoked, so the
25
+ * subdomain stops resolving immediately.
26
+ */
27
+ const DownCommand = {
28
+ alias: ['d'],
29
+ description: 'Stop API + App and remove Caddy block',
30
+ hidden: false,
31
+ name: 'down',
32
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
33
+ const { filesystem, parameters, print: { colors, info, success, warning }, } = toolbox;
34
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
35
+ const identity = (0, dev_identity_1.buildIdentity)(layout.root);
36
+ const session = (0, dev_state_1.loadSession)(layout.root);
37
+ const stopped = [];
38
+ if (session) {
39
+ for (const [name, pid] of Object.entries(session.pids)) {
40
+ if (!pid)
41
+ continue;
42
+ if (!(0, dev_state_1.isPidAlive)(pid)) {
43
+ stopped.push(`${name} (pid ${pid}, already dead)`);
44
+ continue;
45
+ }
46
+ if ((0, dev_process_1.killProcessGroup)(pid))
47
+ stopped.push(`${name} (pid ${pid})`);
48
+ else
49
+ warning(`Failed to stop ${name} (pid ${pid})`);
50
+ }
51
+ (0, dev_state_1.clearSession)(layout.root);
52
+ }
53
+ else {
54
+ info(colors.dim('No running processes registered for this project.'));
55
+ }
56
+ // Remove Caddy block (best-effort — even if no session was active).
57
+ const removed = (0, caddy_1.removeProjectBlock)(identity.slug);
58
+ if (removed) {
59
+ const r = yield (0, caddy_1.reloadCaddy)();
60
+ if (r.ok)
61
+ success(`Removed Caddy block for "${identity.slug}".`);
62
+ else
63
+ warning(`Removed Caddy block but reload failed: ${r.stderr.split('\n')[0]}`);
64
+ }
65
+ // Clear ENV bridge so subsequent test runs without `lt dev up`
66
+ // do not pick up stale URLs.
67
+ if ((0, dev_env_bridge_1.clearEnvBridge)(layout.root))
68
+ info(colors.dim('Removed .lt-dev/.env bridge.'));
69
+ if (stopped.length > 0)
70
+ success(`Stopped: ${stopped.join(', ')}`);
71
+ if (!parameters.options.fromGluegunMenu)
72
+ process.exit();
73
+ return `dev down: ${stopped.length} stopped`;
74
+ }),
75
+ };
76
+ module.exports = DownCommand;
@@ -0,0 +1,172 @@
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 caddy_1 = require("../../lib/caddy");
13
+ const dev_service_1 = require("../../lib/dev-service");
14
+ /**
15
+ * One-time per-machine setup for `lt dev`.
16
+ *
17
+ * Owns the full Caddy lifecycle through a dedicated LaunchAgent
18
+ * (macOS) / systemd-user unit (Linux). This intentionally bypasses
19
+ * `brew services caddy`, whose plist hardcodes
20
+ * `--config /opt/homebrew/etc/Caddyfile` and would crash-loop against
21
+ * our `~/.lenneTech/Caddyfile` location — which is the bug that
22
+ * blocked the first real install.
23
+ *
24
+ * Steps (each idempotent — safe to re-run):
25
+ * 1. Verify `caddy` is on PATH (suggest install instructions otherwise)
26
+ * 2. Ensure `~/.lenneTech/Caddyfile` exists (stub written if missing)
27
+ * 3. Detect conflicting `brew services caddy` and tell the user to
28
+ * stop it — it would fight us for ports 80/443
29
+ * 4. Install + bootstrap our LaunchAgent/systemd unit
30
+ * 5. Wait for the Caddy admin endpoint to respond
31
+ * 6. Validate the Caddyfile
32
+ * 7. Surface the `sudo -E HOME="$HOME" caddy trust` instruction for
33
+ * installing the local CA (HOME must survive sudo so caddy can
34
+ * find its CA files under the *user* profile, not /var/root)
35
+ */
36
+ const InstallCommand = {
37
+ alias: ['i'],
38
+ description: 'Setup Caddy service for lt dev',
39
+ hidden: false,
40
+ name: 'install',
41
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
42
+ const { parameters, print: { colors, error, info, success, warning }, } = toolbox;
43
+ info('');
44
+ info(colors.bold('lt dev install — one-time per-machine setup'));
45
+ info(colors.dim('─'.repeat(60)));
46
+ const plat = (0, dev_service_1.platformSupported)();
47
+ if (plat === 'unsupported') {
48
+ error(`Service management is not supported on ${process.platform}. Only macOS and Linux are covered.`);
49
+ info(` Workaround: run \`${colors.cyan(`caddy run --config ${caddy_1.paths.caddyfile}`)}\` manually.`);
50
+ if (!parameters.options.fromGluegunMenu)
51
+ process.exit(1);
52
+ return 'dev install: unsupported platform';
53
+ }
54
+ let blocked = false;
55
+ // 1. caddy on PATH
56
+ const hasCaddy = yield (0, caddy_1.caddyAvailable)();
57
+ if (hasCaddy) {
58
+ success('caddy is on PATH');
59
+ }
60
+ else {
61
+ warning('caddy is not installed.');
62
+ info(` → macOS: ${colors.cyan('brew install caddy')}`);
63
+ info(` → Linux: ${colors.cyan('https://caddyserver.com/docs/install')}`);
64
+ info(' (Do NOT start it via `brew services` — `lt dev install` runs its own service.)');
65
+ blocked = true;
66
+ }
67
+ // 2. Caddyfile stub
68
+ (0, caddy_1.writeCaddyfile)('# lt dev — managed Caddyfile\n# Add per-project blocks via `lt dev up`.\n');
69
+ success(`Caddyfile present at ${caddy_1.paths.caddyfile}`);
70
+ if (!hasCaddy) {
71
+ info('');
72
+ error('Cannot continue setup until Caddy is installed. Re-run `lt dev install` afterwards.');
73
+ if (!parameters.options.fromGluegunMenu)
74
+ process.exit(blocked ? 1 : 0);
75
+ return 'dev install: caddy missing';
76
+ }
77
+ // 3. brew services conflict warning
78
+ const brewConflict = yield detectBrewCaddyConflict();
79
+ if (brewConflict) {
80
+ warning('A `brew services caddy` instance is registered.');
81
+ info(` Stop it (it crash-loops against our Caddyfile): ${colors.cyan('brew services stop caddy')}`);
82
+ info(' `lt dev install` runs its own service — the brew one is no longer needed.');
83
+ }
84
+ // 4. Install our LaunchAgent / systemd unit
85
+ const paths = (0, dev_service_1.getServicePaths)();
86
+ info('');
87
+ info(`Installing ${plat === 'darwin' ? 'LaunchAgent' : 'systemd-user unit'} at:`);
88
+ info(colors.dim(` ${paths.unitFile}`));
89
+ const installResult = yield (0, dev_service_1.installService)();
90
+ if (!installResult.ok) {
91
+ error(installResult.message);
92
+ blocked = true;
93
+ }
94
+ else {
95
+ if (installResult.created)
96
+ success(installResult.message);
97
+ else
98
+ info(colors.dim(installResult.message));
99
+ }
100
+ // 5. Wait for admin endpoint
101
+ if (installResult.ok) {
102
+ info(colors.dim('Waiting for Caddy admin endpoint (:2019) ...'));
103
+ const ready = yield (0, dev_service_1.waitForServiceReady)(8000);
104
+ const status = yield (0, dev_service_1.getServiceStatus)();
105
+ if (ready && status.daemonReachable) {
106
+ success(`Caddy daemon ready${status.pid ? ` (pid ${status.pid})` : ''}.`);
107
+ }
108
+ else if (status.loaded && !status.daemonReachable) {
109
+ warning('Service is loaded but admin endpoint did not respond within 8s.');
110
+ info(colors.dim(` Logs: ${paths.logFile} / ${paths.errFile}`));
111
+ blocked = true;
112
+ }
113
+ else {
114
+ warning('Caddy daemon did not start. See logs:');
115
+ info(colors.dim(` ${paths.logFile}`));
116
+ info(colors.dim(` ${paths.errFile}`));
117
+ blocked = true;
118
+ }
119
+ }
120
+ // 6. Validate Caddyfile
121
+ if (installResult.ok) {
122
+ const validation = yield (0, caddy_1.validateCaddyfile)();
123
+ if (validation.ok)
124
+ success('Caddyfile validates');
125
+ else
126
+ warning(`Caddyfile validation: ${validation.stderr.split('\n').slice(0, 2).join(' / ')}`);
127
+ }
128
+ // 7. CA trust
129
+ info('');
130
+ info(colors.bold('Local CA trust'));
131
+ info(' Caddy creates its local CA on first run. To trust it system-wide,');
132
+ info(' run this once (HOME must be preserved so sudo keeps the user-scoped');
133
+ info(' CA, otherwise caddy looks in /var/root and fails):');
134
+ info(` ${colors.cyan('sudo -E HOME="$HOME" caddy trust')}`);
135
+ info(` Browsers will then accept ${colors.cyan('https://*.localhost')} without warnings.`);
136
+ info('');
137
+ if (blocked) {
138
+ warning('Setup incomplete. Address the items above and re-run `lt dev install`.');
139
+ }
140
+ else {
141
+ success('Setup complete. Use `lt dev migrate` in a project, then `lt dev up`.');
142
+ }
143
+ if (!parameters.options.fromGluegunMenu)
144
+ process.exit(blocked ? 1 : 0);
145
+ return blocked ? 'dev install: incomplete' : 'dev install: ok';
146
+ }),
147
+ };
148
+ /**
149
+ * Quick `brew services list` scan for a registered caddy service.
150
+ * Returns true on macOS if any entry contains "caddy" — error/started
151
+ * alike, both are conflicts. Always returns false on non-darwin or
152
+ * when `brew` is unavailable (no false positives).
153
+ */
154
+ function detectBrewCaddyConflict() {
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ if (process.platform !== 'darwin')
157
+ return false;
158
+ return new Promise((resolve) => {
159
+ var _a;
160
+ const { spawn } = require('child_process');
161
+ const child = spawn('brew', ['services', 'list'], { stdio: ['ignore', 'pipe', 'ignore'] });
162
+ let out = '';
163
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (out += String(b)));
164
+ child.on('error', () => resolve(false));
165
+ child.on('close', () => {
166
+ const conflict = /\bcaddy\b/.test(out) && !/^caddy\s+none\b/m.test(out);
167
+ resolve(conflict);
168
+ });
169
+ });
170
+ });
171
+ }
172
+ module.exports = InstallCommand;
@@ -0,0 +1,96 @@
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 caddy_1 = require("../../lib/caddy");
13
+ const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
14
+ const dev_project_1 = require("../../lib/dev-project");
15
+ /**
16
+ * Register an existing project with `lt dev` and apply idempotent
17
+ * env-aware patches.
18
+ *
19
+ * Idempotent — re-running with no changes is a no-op. Safe to invoke
20
+ * automatically after `lt fullstack init` or by developers manually.
21
+ *
22
+ * Steps (delegated to `lib/dev-migrate-helper.ts#runMigrate`):
23
+ * 1. Resolve workspace layout (api/app dirs, root)
24
+ * 2. Build identity (slug + subdomains)
25
+ * 3. Patch hardcoded ports → env-aware fallbacks (config.env.ts,
26
+ * nuxt.config.ts, playwright.config.ts)
27
+ * 4. Inject CLAUDE.md URL block (root + each subproject)
28
+ * 5. Persist project to ~/.lenneTech/projects.json
29
+ * 6. Add `.lt-dev/` to .gitignore
30
+ *
31
+ * Closes with a hint to run `lt dev install` if Caddy is missing.
32
+ */
33
+ const MigrateCommand = {
34
+ alias: ['m'],
35
+ description: 'Migrate an existing project to lt dev (idempotent)',
36
+ hidden: false,
37
+ name: 'migrate',
38
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
39
+ const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
40
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
41
+ if (!layout.apiDir && !layout.appDir) {
42
+ error('No API (src/config.env.ts) or App (nuxt.config.ts) project detected at this path.');
43
+ if (!parameters.options.fromGluegunMenu)
44
+ process.exit(1);
45
+ return 'dev migrate: not a project';
46
+ }
47
+ const result = (0, dev_migrate_helper_1.runMigrate)({ layout });
48
+ info('');
49
+ info(colors.bold(`Migrating "${result.identity.slug}"`));
50
+ info(colors.dim('─'.repeat(60)));
51
+ if (result.identity.subdomains.app)
52
+ info(` App URL: https://${result.identity.subdomains.app.hostname}`);
53
+ if (result.identity.subdomains.api)
54
+ info(` API URL: https://${result.identity.subdomains.api.hostname}`);
55
+ info(` DB: mongodb://127.0.0.1/${result.dbName}`);
56
+ info('');
57
+ // Code patches
58
+ if (result.codePatches.length > 0) {
59
+ for (const r of result.codePatches) {
60
+ if (r.patched)
61
+ success(`patched ${r.replacements}× in ${r.file}`);
62
+ else
63
+ info(colors.dim(`already patched: ${r.file}`));
64
+ }
65
+ }
66
+ else {
67
+ info(colors.dim(' patches: not needed (already env-aware)'));
68
+ }
69
+ // CLAUDE.md
70
+ result.claudePatches.filter((r) => r.patched).forEach((r) => success(`updated CLAUDE.md URL block: ${r.file}`));
71
+ // Registry
72
+ if (result.registryUpdated) {
73
+ success(`registered in ${process.env.LT_DEV_REGISTRY_PATH || '~/.lenneTech/projects.json'}`);
74
+ }
75
+ // .gitignore
76
+ if (result.addedGitignoreEntry)
77
+ success('added `.lt-dev/` to .gitignore');
78
+ if (result.alreadyMigrated) {
79
+ info(colors.dim(' Project was already fully migrated — nothing changed.'));
80
+ }
81
+ info('');
82
+ success('Migration complete.');
83
+ // Hint: Caddy installed?
84
+ const caddyOk = yield (0, caddy_1.caddyAvailable)();
85
+ if (!caddyOk) {
86
+ warning('Caddy is not installed yet. Run `lt dev install` first, then `lt dev up`.');
87
+ }
88
+ else {
89
+ info('Start the project with `lt dev up`.');
90
+ }
91
+ if (!parameters.options.fromGluegunMenu)
92
+ process.exit();
93
+ return `dev migrate: ${result.identity.slug}`;
94
+ }),
95
+ };
96
+ module.exports = MigrateCommand;