@lenne.tech/cli 1.20.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,6 +10,8 @@ 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
17
  const workspace_integration_1 = require("../lib/workspace-integration");
@@ -22,7 +24,7 @@ const StatusCommand = {
22
24
  hidden: false,
23
25
  name: 'status',
24
26
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
25
- var _a, _b;
27
+ var _a, _b, _c;
26
28
  const { filesystem, print: { colors, info, success, warning }, system, } = toolbox;
27
29
  const cwd = filesystem.cwd();
28
30
  info('');
@@ -107,7 +109,7 @@ const StatusCommand = {
107
109
  projectInfo.projectType = 'node';
108
110
  }
109
111
  }
110
- catch (_c) {
112
+ catch (_d) {
111
113
  // Ignore parse errors
112
114
  }
113
115
  }
@@ -136,7 +138,7 @@ const StatusCommand = {
136
138
  });
137
139
  }
138
140
  }
139
- catch (_d) {
141
+ catch (_e) {
140
142
  // ignore
141
143
  }
142
144
  }
@@ -152,7 +154,7 @@ const StatusCommand = {
152
154
  });
153
155
  }
154
156
  }
155
- catch (_e) {
157
+ catch (_f) {
156
158
  // ignore
157
159
  }
158
160
  }
@@ -167,7 +169,7 @@ const StatusCommand = {
167
169
  projectInfo.gitBranch = (branch === null || branch === void 0 ? void 0 : branch.trim()) || null;
168
170
  }
169
171
  }
170
- catch (_f) {
172
+ catch (_g) {
171
173
  // Not a git repository
172
174
  }
173
175
  // Get Node/npm versions
@@ -175,7 +177,7 @@ const StatusCommand = {
175
177
  projectInfo.nodeVersion = ((_a = (yield system.run('node --version 2>/dev/null'))) === null || _a === void 0 ? void 0 : _a.trim()) || null;
176
178
  projectInfo.npmVersion = ((_b = (yield system.run('npm --version 2>/dev/null'))) === null || _b === void 0 ? void 0 : _b.trim()) || null;
177
179
  }
178
- catch (_g) {
180
+ catch (_h) {
179
181
  // Ignore errors
180
182
  }
181
183
  // Display project info
@@ -234,6 +236,35 @@ const StatusCommand = {
234
236
  info(colors.dim(' Hint: `lt fullstack add-app` to integrate a Nuxt or Angular app.'));
235
237
  }
236
238
  }
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?".
242
+ {
243
+ const registryRoot = ((_c = projectInfo.workspaceSubProject) === null || _c === void 0 ? void 0 : _c.root) ||
244
+ (projectInfo.workspaceLayout.hasWorkspace ? projectInfo.workspaceLayout.workspaceDir : cwd);
245
+ if (registryRoot) {
246
+ const slug = (0, dev_identity_1.projectSlug)(registryRoot);
247
+ const entry = (0, dev_state_1.loadRegistry)().projects[slug];
248
+ if (entry) {
249
+ info('');
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
+ if (entry.dbName)
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
+ if (apiAlive || appAlive) {
260
+ info(` Running: api ${apiAlive ? colors.green('●') : colors.dim('○')} app ${appAlive ? colors.green('●') : colors.dim('○')}`);
261
+ }
262
+ else {
263
+ info(colors.dim(' Hint: `lt dev up` to start API + App behind Caddy.'));
264
+ }
265
+ }
266
+ }
267
+ }
237
268
  // Show monorepo subprojects if we detected any (typically at monorepo root)
238
269
  if (projectInfo.monorepoSubprojects.length > 0) {
239
270
  info('');
@@ -1975,21 +1975,31 @@ class Server {
1975
1975
  literal.setLiteralValue(secretMap.get(text));
1976
1976
  continue;
1977
1977
  }
1978
- // Replace database names (nest-server-ci -> projectDir-ci)
1978
+ // Replace database names so the project gets project-specific DB
1979
+ // names. Matches BOTH the legacy `nest-server-{env}` form AND the
1980
+ // current `nest-server-starter-{env}` form used by the public
1981
+ // nest-server-starter repo. Examples:
1982
+ // nest-server-starter-local → ${projectDir}-local
1983
+ // nest-server-starter-production → ${projectDir}-production
1984
+ // nest-server-ci → ${projectDir}-ci (legacy)
1985
+ // The optional `(?:starter-)?` non-capturing group is what fixes
1986
+ // the previously-broken case where dbName: 'nest-server-starter-local'
1987
+ // turned into 'svl-sports-system-starter-local' (-starter- stayed).
1979
1988
  if (text.includes('nest-server-')) {
1980
- literal.setLiteralValue(text.replace(/nest-server-/g, `${projectDir}-`));
1989
+ literal.setLiteralValue(text.replace(/nest-server-(?:starter-)?/g, `${projectDir}-`));
1981
1990
  }
1982
1991
  }
1983
1992
  sourceFile.saveSync();
1984
1993
  }
1985
1994
  catch (_a) {
1986
- // Fallback to regex-based approach if ts-morph fails
1995
+ // Fallback to regex-based approach if ts-morph fails. Same matcher
1996
+ // as the AST branch — keep them in sync.
1987
1997
  let content = this.filesystem.read(configPath);
1988
1998
  if (!content) {
1989
1999
  return;
1990
2000
  }
1991
2001
  content = this.replaceSecretOrPrivateKeys(content);
1992
- content = content.replace(/nest-server-(\w+)/g, `${projectDir}-$1`);
2002
+ content = content.replace(/nest-server-(?:starter-)?(\w+)/g, `${projectDir}-$1`);
1993
2003
  this.filesystem.write(configPath, content);
1994
2004
  }
1995
2005
  }