@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.
@@ -0,0 +1,191 @@
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 fs_1 = require("fs");
14
+ const caddy_1 = require("../../lib/caddy");
15
+ const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
16
+ const dev_identity_1 = require("../../lib/dev-identity");
17
+ const dev_project_1 = require("../../lib/dev-project");
18
+ const dev_state_1 = require("../../lib/dev-state");
19
+ /**
20
+ * One-shot E2E convenience wrapper.
21
+ *
22
+ * Ensures the project is up under `lt dev`, then runs the test command
23
+ * with the ENV bridge loaded, and optionally tears the session down at
24
+ * the end. Useful for TDD loops, CI reproduction, and "just test it"
25
+ * workflows where the developer doesn't want to remember three commands.
26
+ *
27
+ * Behaviour:
28
+ * 1. If no `lt dev up` session is alive: run `lt dev up` first.
29
+ * 2. Wait for the App URL to respond (best-effort, ~30s timeout).
30
+ * 3. Inherit the env from `<root>/.lt-dev/.env` (so VS Code IDE-style
31
+ * runners get the same setup) and spawn the test command in the
32
+ * App project.
33
+ * 4. With `--teardown` (default false): run `lt dev down` after.
34
+ *
35
+ * Usage:
36
+ * lt dev test # ensure up + run pnpm test:e2e in app
37
+ * lt dev test --api # run API tests instead (pnpm test in api)
38
+ * lt dev test --teardown # plus stop session after
39
+ * lt dev test --debug # PWDEBUG=1 + headed mode
40
+ * lt dev test -- <args> # forward args to the test runner
41
+ * lt dev test -- --ui crm-login.spec.ts
42
+ */
43
+ const TestCommand = {
44
+ alias: ['t'],
45
+ description: 'Ensure up + run E2E tests with lt dev env',
46
+ hidden: false,
47
+ name: 'test',
48
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
49
+ var _a;
50
+ const { filesystem, parameters, print: { colors, error, info, success }, } = toolbox;
51
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
52
+ if (!layout.apiDir && !layout.appDir) {
53
+ error('No API or App project detected at this path.');
54
+ if (!parameters.options.fromGluegunMenu)
55
+ process.exit(1);
56
+ return 'dev test: not a project';
57
+ }
58
+ const apiMode = Boolean(parameters.options.api);
59
+ const teardown = Boolean(parameters.options.teardown);
60
+ const debug = Boolean(parameters.options.debug);
61
+ // Forwarded args after `--` (gluegun puts them in parameters.array).
62
+ const forwarded = parameters.array || [];
63
+ const targetDir = apiMode ? layout.apiDir : layout.appDir;
64
+ if (!targetDir) {
65
+ error(`Cannot run ${apiMode ? 'API' : 'App'} tests — no ${apiMode ? 'apiDir' : 'appDir'} in layout.`);
66
+ if (!parameters.options.fromGluegunMenu)
67
+ process.exit(1);
68
+ return 'dev test: target missing';
69
+ }
70
+ const identity = (0, dev_identity_1.buildIdentity)(layout.root);
71
+ // Pre-flight: Caddy + ensure up.
72
+ if (!apiMode) {
73
+ if (!(yield (0, caddy_1.caddyAvailable)())) {
74
+ error('caddy is not installed. Run `lt dev install` first.');
75
+ if (!parameters.options.fromGluegunMenu)
76
+ process.exit(1);
77
+ return 'dev test: caddy missing';
78
+ }
79
+ if (!(yield (0, caddy_1.caddyDaemonRunning)())) {
80
+ error('caddy daemon not running. `brew services start caddy` first.');
81
+ if (!parameters.options.fromGluegunMenu)
82
+ process.exit(1);
83
+ return 'dev test: caddy daemon down';
84
+ }
85
+ const reg = (0, dev_state_1.loadRegistry)();
86
+ const entry = reg.projects[identity.slug];
87
+ const session = (0, dev_state_1.loadSession)(layout.root);
88
+ const sessionAlive = session !== null &&
89
+ ((session.pids.api && (0, dev_state_1.isPidAlive)(session.pids.api)) || (session.pids.app && (0, dev_state_1.isPidAlive)(session.pids.app)));
90
+ if (!entry || !sessionAlive) {
91
+ info(colors.dim('Project not running — invoking `lt dev up` first ...'));
92
+ const upResult = yield runChild('lt', ['dev', 'up'], { cwd: layout.root, env: process.env, inherit: true });
93
+ if (upResult !== 0) {
94
+ error(`lt dev up failed (exit ${upResult}).`);
95
+ if (!parameters.options.fromGluegunMenu)
96
+ process.exit(upResult !== null && upResult !== void 0 ? upResult : 1);
97
+ return 'dev test: up failed';
98
+ }
99
+ // Wait for App URL to respond (best-effort).
100
+ const appHost = (_a = identity.subdomains.app) === null || _a === void 0 ? void 0 : _a.hostname;
101
+ if (appHost) {
102
+ info(colors.dim(`Waiting for https://${appHost} to respond ...`));
103
+ yield waitForUrl(`https://${appHost}`, 30000);
104
+ }
105
+ }
106
+ }
107
+ // Build env: process.env + bridge file (bridge wins for keys it defines).
108
+ const env = Object.assign(Object.assign({}, process.env), readBridgeEnv(layout.root));
109
+ if (debug) {
110
+ env.PWDEBUG = '1';
111
+ env.HEADED = '1';
112
+ }
113
+ // Pick the runner.
114
+ const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
115
+ const args = apiMode ? ['run', 'test:e2e', ...forwarded] : ['run', 'test:e2e', ...forwarded];
116
+ info('');
117
+ info(colors.bold(`Running ${apiMode ? 'API' : 'App'} E2E tests for "${identity.slug}"`));
118
+ info(colors.dim(` ${pnpmBin} ${args.join(' ')}`));
119
+ info(colors.dim(` cwd: ${targetDir}`));
120
+ info('');
121
+ const exitCode = yield runChild(pnpmBin, args, { cwd: targetDir, env, inherit: true });
122
+ if (teardown) {
123
+ info('');
124
+ info(colors.dim('Tearing down lt dev session ...'));
125
+ yield runChild('lt', ['dev', 'down'], { cwd: layout.root, env: process.env, inherit: true });
126
+ }
127
+ if (exitCode === 0)
128
+ success('Tests passed.');
129
+ else
130
+ error(`Tests failed (exit ${exitCode}).`);
131
+ if (!parameters.options.fromGluegunMenu)
132
+ process.exit(exitCode !== null && exitCode !== void 0 ? exitCode : 1);
133
+ return `dev test: exit=${exitCode}`;
134
+ }),
135
+ };
136
+ /**
137
+ * Read the .lt-dev/.env bridge file as a key/value map.
138
+ * Returns an empty object if the file is missing.
139
+ */
140
+ function readBridgeEnv(root) {
141
+ const file = (0, dev_env_bridge_1.envBridgePath)(root);
142
+ if (!(0, fs_1.existsSync)(file))
143
+ return {};
144
+ const out = {};
145
+ for (const line of (0, fs_1.readFileSync)(file, 'utf8').split(/\r?\n/)) {
146
+ const m = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/);
147
+ if (m)
148
+ out[m[1]] = m[2];
149
+ }
150
+ return out;
151
+ }
152
+ /** Spawn a child synchronously (waits for exit), inheriting stdio when requested. */
153
+ function runChild(cmd, args, opts) {
154
+ return new Promise((resolve) => {
155
+ const child = (0, child_process_1.spawn)(cmd, args, {
156
+ cwd: opts.cwd,
157
+ env: opts.env,
158
+ stdio: opts.inherit ? 'inherit' : 'pipe',
159
+ });
160
+ child.on('error', () => resolve(1));
161
+ child.on('close', (code) => resolve(code));
162
+ });
163
+ }
164
+ /** Poll a URL until it responds or timeout elapses. */
165
+ function waitForUrl(url, timeoutMs) {
166
+ const start = Date.now();
167
+ return new Promise((resolve) => {
168
+ const tick = () => {
169
+ var _a;
170
+ const child = (0, child_process_1.spawn)('curl', ['-sk', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', '2', url], {
171
+ stdio: ['ignore', 'pipe', 'ignore'],
172
+ });
173
+ let status = '';
174
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (status += String(b)));
175
+ child.on('close', () => {
176
+ if (/^[1-5]\d\d$/.test(status.trim()))
177
+ return resolve(true);
178
+ if (Date.now() - start > timeoutMs)
179
+ return resolve(false);
180
+ setTimeout(tick, 500);
181
+ });
182
+ child.on('error', () => {
183
+ if (Date.now() - start > timeoutMs)
184
+ return resolve(false);
185
+ setTimeout(tick, 500);
186
+ });
187
+ };
188
+ tick();
189
+ });
190
+ }
191
+ module.exports = TestCommand;
@@ -0,0 +1,228 @@
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 caddy_1 = require("../../lib/caddy");
15
+ const dev_env_1 = require("../../lib/dev-env");
16
+ const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
17
+ const dev_identity_1 = require("../../lib/dev-identity");
18
+ const dev_patches_1 = require("../../lib/dev-patches");
19
+ const dev_process_1 = require("../../lib/dev-process");
20
+ const dev_project_1 = require("../../lib/dev-project");
21
+ const dev_state_1 = require("../../lib/dev-state");
22
+ /**
23
+ * Start API + App behind Caddy with project-specific URLs.
24
+ *
25
+ * Pre-flight:
26
+ * - Caddy must be installed and running (otherwise points to install)
27
+ * - No existing `lt dev up` session for THIS project
28
+ * - Internal upstream ports must be free
29
+ *
30
+ * Process:
31
+ * 1. Resolve layout + identity
32
+ * 2. Allocate (or reuse) internal upstream ports
33
+ * 3. Upsert Caddy block + reload
34
+ * 4. Spawn API + App detached, log into `<root>/.lt-dev/{api,app}.log`
35
+ * 5. Persist registry entry + session state
36
+ *
37
+ * Env-vars injected (see lib/dev-env.ts):
38
+ * PORT, BASE_URL, APP_URL, NUXT_API_URL, NUXT_PUBLIC_API_URL,
39
+ * NUXT_PUBLIC_SITE_URL, NUXT_PUBLIC_STORAGE_PREFIX,
40
+ * NSC__MONGOOSE__URI, NSC__BASE_URL, NSC__APP_URL, DATABASE_URL,
41
+ * NUXT_PUBLIC_API_PROXY=false (Caddy makes vite-proxy obsolete).
42
+ */
43
+ const UpCommand = {
44
+ alias: ['u'],
45
+ description: 'Start API + App behind Caddy',
46
+ hidden: false,
47
+ name: 'up',
48
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
49
+ var _a, _b, _c, _d, _e, _f;
50
+ const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
51
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
52
+ if (!layout.apiDir && !layout.appDir) {
53
+ error('No API or App project detected at this path. Run `lt dev migrate` first.');
54
+ if (!parameters.options.fromGluegunMenu)
55
+ process.exit(1);
56
+ return 'dev up: not a project';
57
+ }
58
+ // Pre-flight: Caddy
59
+ if (!(yield (0, caddy_1.caddyAvailable)())) {
60
+ error('caddy is not installed. Run `lt dev install` first.');
61
+ if (!parameters.options.fromGluegunMenu)
62
+ process.exit(1);
63
+ return 'dev up: caddy missing';
64
+ }
65
+ if (!(yield (0, caddy_1.caddyDaemonRunning)())) {
66
+ error('caddy daemon is not running. Start it: `brew services start caddy`.');
67
+ if (!parameters.options.fromGluegunMenu)
68
+ process.exit(1);
69
+ return 'dev up: caddy daemon down';
70
+ }
71
+ const identity = (0, dev_identity_1.buildIdentity)(layout.root);
72
+ const dbName = (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug);
73
+ // Sanft auto-migrate sichere Operationen (ohne Code-Modifikation):
74
+ // CLAUDE.md-URL-Block einfügen + .gitignore ergänzen.
75
+ // Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev migrate`.
76
+ {
77
+ const claudeCandidates = [
78
+ (0, path_1.join)(layout.root, 'CLAUDE.md'),
79
+ ...(layout.apiDir ? [(0, path_1.join)(layout.apiDir, 'CLAUDE.md')] : []),
80
+ ...(layout.appDir ? [(0, path_1.join)(layout.appDir, 'CLAUDE.md')] : []),
81
+ ];
82
+ const patched = claudeCandidates.map((f) => (0, dev_patches_1.patchClaudeMd)(f, { dbName, identity })).filter((r) => r.patched);
83
+ if (patched.length > 0) {
84
+ info(colors.dim(`updated CLAUDE.md URL block in ${patched.length} file(s)`));
85
+ }
86
+ if ((0, dev_patches_1.addToGitignore)(layout.root, '.lt-dev/')) {
87
+ info(colors.dim('added `.lt-dev/` to .gitignore'));
88
+ }
89
+ }
90
+ // Warnung bei Legacy-Code (hardcoded ports) — kein Auto-Patch.
91
+ {
92
+ const legacyFiles = [];
93
+ if (layout.apiDir) {
94
+ const f = (0, dev_project_1.apiNeedsPortPatch)(layout.apiDir);
95
+ if (f)
96
+ legacyFiles.push(f);
97
+ }
98
+ if (layout.appDir)
99
+ legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
100
+ if (legacyFiles.length > 0) {
101
+ warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev migrate`:');
102
+ legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
103
+ info(colors.dim(' (Continuing — env-aware files will work; legacy files may bind on 3000/3001 and miss Caddy.)'));
104
+ }
105
+ }
106
+ // Already running?
107
+ const existingSession = (0, dev_state_1.loadSession)(layout.root);
108
+ if (existingSession) {
109
+ const apiUp = existingSession.pids.api ? (0, dev_state_1.isPidAlive)(existingSession.pids.api) : false;
110
+ const appUp = existingSession.pids.app ? (0, dev_state_1.isPidAlive)(existingSession.pids.app) : false;
111
+ if (apiUp || appUp) {
112
+ warning(`Already running (api pid ${(_a = existingSession.pids.api) !== null && _a !== void 0 ? _a : '-'}, app pid ${(_b = existingSession.pids.app) !== null && _b !== void 0 ? _b : '-'}).`);
113
+ info('Run `lt dev down` first.');
114
+ if (!parameters.options.fromGluegunMenu)
115
+ process.exit(1);
116
+ return 'dev up: already running';
117
+ }
118
+ }
119
+ // Allocate internal ports (reuse existing if registered).
120
+ const reg = (0, dev_state_1.loadRegistry)();
121
+ const entry = reg.projects[identity.slug];
122
+ const taken = (0, dev_state_1.takenInternalPorts)(reg, identity.slug);
123
+ const apiPort = (_c = entry === null || entry === void 0 ? void 0 : entry.internalPorts.api) !== null && _c !== void 0 ? _c : (layout.apiDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
124
+ if (apiPort)
125
+ taken.add(apiPort);
126
+ const appPort = (_d = entry === null || entry === void 0 ? void 0 : entry.internalPorts.app) !== null && _d !== void 0 ? _d : (layout.appDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
127
+ // Pre-flight: internal ports free?
128
+ const portsToCheck = [apiPort, appPort].filter((p) => typeof p === 'number');
129
+ const snap = yield (0, dev_process_1.listenSnapshot)(portsToCheck);
130
+ for (const p of portsToCheck) {
131
+ const r = snap.get(p);
132
+ if (r) {
133
+ error(`Internal port ${p} already in use by ${r.command} (pid ${r.pid}).`);
134
+ if (!parameters.options.fromGluegunMenu)
135
+ process.exit(1);
136
+ return 'dev up: port in use';
137
+ }
138
+ }
139
+ // Caddy block + reload.
140
+ const routes = [];
141
+ if (identity.subdomains.api && apiPort)
142
+ routes.push({ hostname: identity.subdomains.api.hostname, upstreamPort: apiPort });
143
+ if (identity.subdomains.app && appPort)
144
+ routes.push({ hostname: identity.subdomains.app.hostname, upstreamPort: appPort });
145
+ if (routes.length === 0) {
146
+ error('No subdomains to expose (project has neither api nor app).');
147
+ if (!parameters.options.fromGluegunMenu)
148
+ process.exit(1);
149
+ return 'dev up: nothing to expose';
150
+ }
151
+ (0, caddy_1.upsertProjectBlock)(identity.slug, routes);
152
+ const reload = yield (0, caddy_1.reloadCaddy)();
153
+ if (!reload.ok) {
154
+ error(`caddy reload failed:\n${reload.stderr}`);
155
+ if (!parameters.options.fromGluegunMenu)
156
+ process.exit(1);
157
+ return 'dev up: caddy reload failed';
158
+ }
159
+ info('');
160
+ info(colors.bold(`Starting "${identity.slug}"`));
161
+ if (identity.subdomains.app)
162
+ info(` app: https://${identity.subdomains.app.hostname} → 127.0.0.1:${appPort}`);
163
+ if (identity.subdomains.api)
164
+ info(` api: https://${identity.subdomains.api.hostname} → 127.0.0.1:${apiPort}`);
165
+ info(` db: mongodb://127.0.0.1/${dbName}`);
166
+ info('');
167
+ // Build env per process.
168
+ const devEnv = (0, dev_env_1.buildDevEnv)({
169
+ apiInternalPort: apiPort !== null && apiPort !== void 0 ? apiPort : 0,
170
+ appInternalPort: appPort !== null && appPort !== void 0 ? appPort : 0,
171
+ baseEnv: process.env,
172
+ dbName,
173
+ identity,
174
+ });
175
+ const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
176
+ const pids = {};
177
+ if (layout.apiDir && (0, fs_1.existsSync)((0, path_1.join)(layout.apiDir, 'package.json')) && apiPort) {
178
+ const apiPid = (0, dev_process_1.spawnDetached)(pnpmBin, ['start'], {
179
+ cwd: layout.apiDir,
180
+ env: devEnv.api.env,
181
+ logFile: (0, path_1.join)(layout.root, '.lt-dev', 'api.log'),
182
+ });
183
+ if (apiPid)
184
+ pids.api = apiPid;
185
+ }
186
+ if (layout.appDir && (0, fs_1.existsSync)((0, path_1.join)(layout.appDir, 'package.json')) && appPort) {
187
+ const appPid = (0, dev_process_1.spawnDetached)(pnpmBin, ['dev'], {
188
+ cwd: layout.appDir,
189
+ env: devEnv.app.env,
190
+ logFile: (0, path_1.join)(layout.root, '.lt-dev', 'app.log'),
191
+ });
192
+ if (appPid)
193
+ pids.app = appPid;
194
+ }
195
+ // Persist.
196
+ const subdomainMap = {};
197
+ for (const [k, v] of Object.entries(identity.subdomains))
198
+ subdomainMap[k] = v.hostname;
199
+ reg.projects[identity.slug] = {
200
+ dbName,
201
+ internalPorts: { api: apiPort, app: appPort },
202
+ lastUsedAt: new Date().toISOString(),
203
+ path: layout.root,
204
+ subdomains: subdomainMap,
205
+ };
206
+ (0, dev_state_1.saveRegistry)(reg);
207
+ (0, dev_state_1.saveSession)(layout.root, { pids, startedAt: new Date().toISOString() });
208
+ // Write the ENV bridge so external tools (Playwright, IDE test runners,
209
+ // custom shell scripts) can pick up the URLs without inheriting our shell.
210
+ const bridgePath = (0, dev_env_bridge_1.writeEnvBridge)(layout.root, devEnv, dbName);
211
+ info(colors.dim(`ENV bridge: ${bridgePath}`));
212
+ success(`Started: api pid=${(_e = pids.api) !== null && _e !== void 0 ? _e : '-'}, app pid=${(_f = pids.app) !== null && _f !== void 0 ? _f : '-'}`);
213
+ info(colors.dim('Logs: <root>/.lt-dev/api.log, <root>/.lt-dev/app.log'));
214
+ info(colors.dim('Stop with: lt dev down'));
215
+ // Best-effort: kill orphaned children if neither spawned (unlikely, but tidy).
216
+ if (Object.keys(pids).length === 0) {
217
+ warning('Nothing was spawned. Check package.json scripts (`start` for api, `dev` for app).');
218
+ if (pids.api)
219
+ (0, dev_process_1.killProcessGroup)(pids.api);
220
+ if (pids.app)
221
+ (0, dev_process_1.killProcessGroup)(pids.app);
222
+ }
223
+ if (!parameters.options.fromGluegunMenu)
224
+ process.exit();
225
+ return `dev up: api=${pids.api}, app=${pids.app}`;
226
+ }),
227
+ };
228
+ module.exports = UpCommand;
@@ -12,6 +12,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ const gluegun_1 = require("gluegun");
16
+ const caddy_1 = require("../../lib/caddy");
17
+ const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
18
+ const dev_project_1 = require("../../lib/dev-project");
15
19
  const hoist_workspace_pnpm_config_1 = require("../../lib/hoist-workspace-pnpm-config");
16
20
  const workspace_integration_1 = require("../../lib/workspace-integration");
17
21
  const add_api_1 = __importDefault(require("./add-api"));
@@ -27,7 +31,7 @@ const NewCommand = {
27
31
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
28
32
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
29
33
  // Retrieve the tools we need
30
- const { config, filesystem, frontendHelper, git, helper, parameters, patching, print: { error, info, spin, success }, prompt: { ask, confirm }, server, strings: { kebabCase }, system, template, } = toolbox;
34
+ const { config, filesystem, frontendHelper, git, helper, parameters, patching, print: { colors, error, info, spin, success }, prompt: { ask, confirm }, server, strings: { kebabCase }, system, template, } = toolbox;
31
35
  // Start timer
32
36
  const timer = system.startTimer();
33
37
  // Info
@@ -637,6 +641,22 @@ const NewCommand = {
637
641
  return;
638
642
  }
639
643
  }
644
+ // Best-effort `lt dev migrate` so the workspace is ready for `lt dev up`
645
+ // out-of-the-box: registers the slug in `~/.lenneTech/projects.json`,
646
+ // injects the URL block into CLAUDE.md, adds `.lt-dev/` to .gitignore.
647
+ // Failures here are non-fatal — `init` itself remains successful.
648
+ let devMigrateOk = false;
649
+ try {
650
+ const layout = (0, dev_project_1.resolveLayout)(projectDir, gluegun_1.filesystem);
651
+ const migrate = (0, dev_migrate_helper_1.runMigrate)({ layout });
652
+ devMigrateOk = true;
653
+ if (!migrate.alreadyMigrated) {
654
+ info(colors.dim(` registered "${migrate.identity.slug}" with \`lt dev\``));
655
+ }
656
+ }
657
+ catch (_1) {
658
+ /* best-effort — never block init */
659
+ }
640
660
  // We're done, so show what to do next
641
661
  info('');
642
662
  success(`Generated fullstack workspace with ${frontend} in ${projectDir} with ${name} app in ${helper.msToMinutesAndSeconds(timer())}m.`);
@@ -652,7 +672,21 @@ const NewCommand = {
652
672
  else {
653
673
  info(` Run ${name}`);
654
674
  info(` $ cd ${projectDir}`);
655
- info(` $ ${toolbox.pm.run('start')}`);
675
+ if (devMigrateOk) {
676
+ // Prefer lt dev up — sets up Caddy + HTTPS URLs + cross-wiring guards.
677
+ const caddyOk = yield (0, caddy_1.caddyAvailable)();
678
+ if (caddyOk) {
679
+ info(' $ lt dev up # start API + App behind Caddy with project-specific URLs');
680
+ }
681
+ else {
682
+ info(' $ lt dev install # one-time per machine: verify Caddy + CA');
683
+ info(' $ lt dev up # then: start API + App behind Caddy');
684
+ }
685
+ info(colors.dim(` (fallback: ${toolbox.pm.run('start')} runs the classic localhost:3000/3001 mode)`));
686
+ }
687
+ else {
688
+ info(` $ ${toolbox.pm.run('start')}`);
689
+ }
656
690
  }
657
691
  info('');
658
692
  if (!toolbox.parameters.options.fromGluegunMenu) {
@@ -10,9 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const path_1 = require("path");
13
+ const dev_identity_1 = require("../lib/dev-identity");
14
+ const dev_state_1 = require("../lib/dev-state");
13
15
  const framework_detection_1 = require("../lib/framework-detection");
14
16
  const frontend_framework_detection_1 = require("../lib/frontend-framework-detection");
15
- const port_registry_1 = require("../lib/port-registry");
16
17
  const workspace_integration_1 = require("../lib/workspace-integration");
17
18
  /**
18
19
  * Show project status and context
@@ -235,32 +236,31 @@ const StatusCommand = {
235
236
  info(colors.dim(' Hint: `lt fullstack add-app` to integrate a Nuxt or Angular app.'));
236
237
  }
237
238
  }
238
- // Local dev orchestration registry — surface slot/ports if registered.
239
- // Helps users discover that `lt local` is set up for this project, and
240
- // gives a quick inline answer to "what ports does this project use?".
239
+ // Local dev orchestration registry — surface URLs if registered.
240
+ // Helps users discover that `lt dev` is set up for this project, and
241
+ // gives a quick inline answer to "what URLs does this project use?".
241
242
  {
242
243
  const registryRoot = ((_c = projectInfo.workspaceSubProject) === null || _c === void 0 ? void 0 : _c.root) ||
243
244
  (projectInfo.workspaceLayout.hasWorkspace ? projectInfo.workspaceLayout.workspaceDir : cwd);
244
245
  if (registryRoot) {
245
- const slug = (0, port_registry_1.projectSlug)(registryRoot);
246
- const entry = (0, port_registry_1.loadRegistry)().projects[slug];
246
+ const slug = (0, dev_identity_1.projectSlug)(registryRoot);
247
+ const entry = (0, dev_state_1.loadRegistry)().projects[slug];
247
248
  if (entry) {
248
249
  info('');
249
- info(colors.bold('Local dev orchestration (lt local):'));
250
- const ports = (0, port_registry_1.portsForSlot)(entry.slot);
251
- info(` Slot: ${entry.slot}`);
252
- info(` API: http://localhost:${ports.api}`);
253
- info(` App: http://localhost:${ports.app}`);
250
+ info(colors.bold('Local dev orchestration (lt dev):'));
251
+ for (const [sub, host] of Object.entries(entry.subdomains)) {
252
+ info(` ${sub.padEnd(6)} https://${host}`);
253
+ }
254
254
  if (entry.dbName)
255
- info(` DB: mongodb://127.0.0.1/${entry.dbName}`);
256
- const state = (0, port_registry_1.loadLocalState)(registryRoot);
257
- const apiAlive = (state === null || state === void 0 ? void 0 : state.pids.api) ? (0, port_registry_1.isPidAlive)(state.pids.api) : false;
258
- const appAlive = (state === null || state === void 0 ? void 0 : state.pids.app) ? (0, port_registry_1.isPidAlive)(state.pids.app) : false;
255
+ info(` db mongodb://127.0.0.1/${entry.dbName}`);
256
+ const session = (0, dev_state_1.loadSession)(registryRoot);
257
+ const apiAlive = (session === null || session === void 0 ? void 0 : session.pids.api) ? (0, dev_state_1.isPidAlive)(session.pids.api) : false;
258
+ const appAlive = (session === null || session === void 0 ? void 0 : session.pids.app) ? (0, dev_state_1.isPidAlive)(session.pids.app) : false;
259
259
  if (apiAlive || appAlive) {
260
260
  info(` Running: api ${apiAlive ? colors.green('●') : colors.dim('○')} app ${appAlive ? colors.green('●') : colors.dim('○')}`);
261
261
  }
262
262
  else {
263
- info(colors.dim(' Hint: `lt local up` to start API + App with these ports.'));
263
+ info(colors.dim(' Hint: `lt dev up` to start API + App behind Caddy.'));
264
264
  }
265
265
  }
266
266
  }