@lenne.tech/cli 1.19.0 → 1.21.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,131 @@
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 port_registry_1 = require("../../lib/port-registry");
15
+ /**
16
+ * Rebuild the port registry from the filesystem.
17
+ *
18
+ * Walks the given directory (default: cwd) up to depth 3, looking for
19
+ * `lt.config.json` + `package.json` pairs or workspace markers. Re-allocates
20
+ * a slot for each new project found; preserves slots for projects whose
21
+ * names already exist in the registry.
22
+ */
23
+ const ScanCommand = {
24
+ alias: [],
25
+ description: 'Rebuild port registry',
26
+ hidden: false,
27
+ name: 'scan',
28
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
29
+ const { filesystem, parameters, print: { colors, info, success }, } = toolbox;
30
+ const startDir = parameters.first ? filesystem.path(parameters.first) : filesystem.cwd();
31
+ info('');
32
+ info(colors.bold(`Scanning for projects under ${startDir} ...`));
33
+ const found = walkForProjects(startDir, 0, 3);
34
+ info(colors.dim(`Found ${found.length} candidate project(s)`));
35
+ const registry = (0, port_registry_1.loadRegistry)();
36
+ let added = 0;
37
+ let kept = 0;
38
+ let dirty = false;
39
+ for (const project of found) {
40
+ const slug = (0, port_registry_1.projectSlug)(project.path);
41
+ const existing = registry.projects[slug];
42
+ if (existing) {
43
+ if (existing.path !== project.path) {
44
+ existing.path = project.path;
45
+ dirty = true;
46
+ }
47
+ kept++;
48
+ continue;
49
+ }
50
+ const slot = (0, port_registry_1.allocateSlot)(slug, registry);
51
+ registry.projects[slug] = { path: project.path, ports: (0, port_registry_1.portsForSlot)(slot), slot };
52
+ added++;
53
+ dirty = true;
54
+ }
55
+ if (dirty)
56
+ (0, port_registry_1.saveRegistry)(registry);
57
+ success(`Registry updated: ${added} new, ${kept} kept (total: ${Object.keys(registry.projects).length})`);
58
+ if (!parameters.options.fromGluegunMenu) {
59
+ process.exit();
60
+ }
61
+ return `ports scan: ${added} new`;
62
+ }),
63
+ };
64
+ /** A directory looks like a project if it has `package.json` with a non-empty `name` field. */
65
+ function looksLikeProject(dir) {
66
+ if (!(0, fs_1.existsSync)((0, path_1.join)(dir, 'package.json')))
67
+ return false;
68
+ try {
69
+ const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(dir, 'package.json'), 'utf8'));
70
+ return Boolean(pkg.name);
71
+ }
72
+ catch (_a) {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Recursive project discovery.
78
+ *
79
+ * Stops descending into a directory as soon as a project marker is detected
80
+ * (lt.config.json with package.json, pnpm-workspace.yaml, or `projects/`).
81
+ * Skips dotdirs and `node_modules`. Skips symlinks (lstatSync) to avoid
82
+ * traversal loops on pathological filesystems.
83
+ */
84
+ function walkForProjects(dir, depth, maxDepth) {
85
+ if (depth > maxDepth)
86
+ return [];
87
+ if (!(0, fs_1.existsSync)(dir))
88
+ return [];
89
+ const out = [];
90
+ // A project is detected if EITHER an lt.config.json exists OR a workspace marker is found.
91
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'lt.config.json'))) {
92
+ if (looksLikeProject(dir)) {
93
+ out.push({ path: dir });
94
+ return out; // don't recurse into a detected project
95
+ }
96
+ }
97
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'pnpm-workspace.yaml')) || (0, fs_1.existsSync)((0, path_1.join)(dir, 'projects'))) {
98
+ out.push({ path: dir });
99
+ return out;
100
+ }
101
+ let entries;
102
+ try {
103
+ entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
104
+ }
105
+ catch (_a) {
106
+ return [];
107
+ }
108
+ for (const entry of entries) {
109
+ if (entry.name.startsWith('.') || entry.name === 'node_modules')
110
+ continue;
111
+ // Use the cheap Dirent check first — it doesn't need an extra syscall.
112
+ if (!entry.isDirectory()) {
113
+ // Could still be a symlink that points to a directory. Skip those to
114
+ // avoid traversal loops.
115
+ if (!entry.isSymbolicLink())
116
+ continue;
117
+ let s;
118
+ try {
119
+ s = (0, fs_1.lstatSync)((0, path_1.join)(dir, entry.name));
120
+ }
121
+ catch (_b) {
122
+ continue;
123
+ }
124
+ if (s.isSymbolicLink())
125
+ continue;
126
+ }
127
+ out.push(...walkForProjects((0, path_1.join)(dir, entry.name), depth + 1, maxDepth));
128
+ }
129
+ return out;
130
+ }
131
+ module.exports = ScanCommand;
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const workspace_integration_1 = require("../../lib/workspace-integration");
12
13
  /**
13
14
  * Create a new server
14
15
  */
@@ -42,6 +43,19 @@ const NewCommand = {
42
43
  { description: 'Git branch to clone from', flag: '--branch', required: false, type: 'string' },
43
44
  { description: 'Copy from local path instead of cloning', flag: '--copy', required: false, type: 'string' },
44
45
  { description: 'Symlink to local path instead of cloning', flag: '--link', required: false, type: 'string' },
46
+ {
47
+ description: 'Backend framework consumption mode',
48
+ flag: '--framework-mode',
49
+ required: false,
50
+ type: 'string',
51
+ values: ['npm', 'vendor'],
52
+ },
53
+ {
54
+ description: 'Upstream nest-server branch/tag to vendor (with --framework-mode vendor)',
55
+ flag: '--framework-upstream-branch',
56
+ required: false,
57
+ type: 'string',
58
+ },
45
59
  {
46
60
  default: false,
47
61
  description: 'Use experimental nest-base template (Bun + Prisma + Postgres)',
@@ -49,6 +63,20 @@ const NewCommand = {
49
63
  required: false,
50
64
  type: 'boolean',
51
65
  },
66
+ {
67
+ default: false,
68
+ description: 'Print resolved plan and exit without making any changes',
69
+ flag: '--dry-run',
70
+ required: false,
71
+ type: 'boolean',
72
+ },
73
+ {
74
+ default: false,
75
+ description: 'Override the workspace-detection abort under --noConfirm',
76
+ flag: '--force',
77
+ required: false,
78
+ type: 'boolean',
79
+ },
52
80
  {
53
81
  default: false,
54
82
  description: 'Skip all interactive prompts',
@@ -84,7 +112,11 @@ const NewCommand = {
84
112
  const cliApiMode = parameters.options['api-mode'] || parameters.options.apiMode;
85
113
  const cliFrameworkMode = parameters.options['framework-mode'];
86
114
  const cliFrameworkUpstreamBranch = parameters.options['framework-upstream-branch'];
115
+ const cliDryRun = parameters.options['dry-run'];
116
+ const cliForce = parameters.options.force;
87
117
  const experimental = parameters.options.next === true || parameters.options.next === 'true';
118
+ const dryRun = cliDryRun === true || cliDryRun === 'true';
119
+ const force = cliForce === true || cliForce === 'true';
88
120
  // Determine noConfirm with priority: CLI > config > global > default (false)
89
121
  const noConfirm = config.getNoConfirm({
90
122
  cliValue: cliNoConfirm,
@@ -96,16 +128,41 @@ const NewCommand = {
96
128
  // Info
97
129
  info('Create a new server');
98
130
  // Hint for non-interactive callers (e.g. Claude Code)
99
- toolbox.tools.nonInteractiveHint('lt server create --name <name> --api-mode <Rest|GraphQL|Both> [--next] --noConfirm');
131
+ toolbox.tools.nonInteractiveHint('lt server create --name <name> --api-mode <Rest|GraphQL|Both> --framework-mode <npm|vendor> [--next] [--dry-run] --noConfirm');
100
132
  // Check git
101
133
  if (!(yield git.gitInstalled())) {
102
134
  return;
103
135
  }
104
- // Get name
105
- const name = yield helper.getInput(parameters.first, {
106
- name: 'server name',
107
- showError: true,
136
+ // Workspace-awareness: bundled into runStandaloneWorkspaceGate so
137
+ // server/frontend commands share the print + prompt + decision +
138
+ // exit logic. Three modes:
139
+ // - interactive → confirm prompt
140
+ // - non-interactive → refuse (KI/CI default — fail loud)
141
+ // - non-interactive + force → proceed with a hint
142
+ const proceed = yield (0, workspace_integration_1.runStandaloneWorkspaceGate)({
143
+ cwd: '.',
144
+ filesystem,
145
+ force,
146
+ fromGluegunMenu: Boolean(toolbox.parameters.options.fromGluegunMenu),
147
+ noConfirmFlag: noConfirm,
148
+ pieceName: 'api',
149
+ print: { confirm, error, info },
150
+ projectKind: 'server',
151
+ suggestion: 'lt fullstack add-api',
108
152
  });
153
+ if (!proceed)
154
+ return;
155
+ // Get name. Honour the explicit `--name <slug>` flag (declared as
156
+ // required in the help-json contract) before falling back to the
157
+ // first positional argument or interactive input. Without this, a
158
+ // non-interactive caller passing only `--name my-srv` is forced
159
+ // into the prompt because `parameters.first` is empty.
160
+ const cliName = parameters.options.name;
161
+ const name = cliName ||
162
+ (yield helper.getInput(parameters.first, {
163
+ name: 'server name',
164
+ showError: true,
165
+ }));
109
166
  if (!name) {
110
167
  return;
111
168
  }
@@ -122,7 +179,10 @@ const NewCommand = {
122
179
  const linkPath = cliLink || configLink;
123
180
  // Determine branch with priority: CLI > config
124
181
  const branch = cliBranch || configBranch;
125
- // Determine description with priority: CLI > config > interactive
182
+ // Determine description with priority: CLI > config > interactive.
183
+ // Skip the interactive prompt under --noConfirm; description is
184
+ // optional and defaulting to the project name keeps the package.json
185
+ // valid for non-interactive callers.
126
186
  let description;
127
187
  if (cliDescription) {
128
188
  description = cliDescription;
@@ -131,13 +191,17 @@ const NewCommand = {
131
191
  description = configDescription.replace('{name}', name);
132
192
  info(`Using description from lt.config: ${description}`);
133
193
  }
194
+ else if (noConfirm) {
195
+ description = '';
196
+ }
134
197
  else {
135
198
  description = yield helper.getInput(parameters.second, {
136
199
  name: 'Description',
137
200
  showError: false,
138
201
  });
139
202
  }
140
- // Determine author with priority: CLI > config > global > interactive
203
+ // Determine author with priority: CLI > config > global > interactive.
204
+ // Skip the prompt under --noConfirm.
141
205
  let author;
142
206
  if (cliAuthor) {
143
207
  author = cliAuthor;
@@ -150,6 +214,9 @@ const NewCommand = {
150
214
  author = globalAuthor;
151
215
  info(`Using author from lt.config defaults: ${author}`);
152
216
  }
217
+ else if (noConfirm) {
218
+ author = '';
219
+ }
153
220
  else {
154
221
  author = yield helper.getInput('', {
155
222
  name: 'Author',
@@ -229,6 +296,50 @@ const NewCommand = {
229
296
  const frameworkUpstreamBranch = typeof cliFrameworkUpstreamBranch === 'string' && cliFrameworkUpstreamBranch.length > 0
230
297
  ? cliFrameworkUpstreamBranch
231
298
  : undefined;
299
+ // Dry-run: print the resolved plan and exit without any disk
300
+ // changes. Mirrors the dry-run surface of `lt fullstack init` /
301
+ // `add-api` / `add-app` so agent workflows can preview the
302
+ // standalone path the same way.
303
+ if (dryRun) {
304
+ info('');
305
+ info('Dry-run plan:');
306
+ info(` name: ${name}`);
307
+ info(` projectDir: ${projectDir}`);
308
+ info(` apiMode: ${apiMode}`);
309
+ info(` frameworkMode: ${frameworkMode}`);
310
+ if (frameworkUpstreamBranch) {
311
+ info(` frameworkUpstreamBranch: ${frameworkUpstreamBranch}`);
312
+ }
313
+ info(` branch: ${branch || '(default)'}`);
314
+ info(` copy: ${copyPath || '(none)'}`);
315
+ info(` link: ${linkPath || '(none)'}`);
316
+ info(` experimental (--next): ${experimental}`);
317
+ info(` description: ${description || '(none)'}`);
318
+ info(` author: ${author || '(none)'}`);
319
+ info('');
320
+ info('Would execute:');
321
+ if (experimental) {
322
+ info(` 1. clone nest-base → ./${projectDir}`);
323
+ info(` 2. patch package.json (name = ${projectDir})`);
324
+ }
325
+ else if (frameworkMode === 'vendor') {
326
+ info(` 1. clone nest-server-starter → ./${projectDir}`);
327
+ info(` 2. clone @lenne.tech/nest-server${frameworkUpstreamBranch ? ` (${frameworkUpstreamBranch})` : ''} → /tmp`);
328
+ info(` 3. vendor core/ + flatten-fix + codemod consumer imports`);
329
+ info(` 4. merge upstream deps`);
330
+ info(` 5. run processApiMode(${apiMode})`);
331
+ }
332
+ else {
333
+ info(` 1. clone nest-server-starter → ./${projectDir}`);
334
+ info(` 2. run processApiMode(${apiMode})`);
335
+ }
336
+ info(` N. write ./${projectDir}/lt.config.json`);
337
+ info('');
338
+ if (!toolbox.parameters.options.fromGluegunMenu) {
339
+ process.exit();
340
+ }
341
+ return `dry-run server create (${frameworkMode} / ${apiMode})`;
342
+ }
232
343
  // Setup server using Server extension
233
344
  const setupSpinner = spin(`Setting up server${linkPath ? ' (link)' : copyPath ? ' (copy)' : branch ? ` (branch: ${branch})` : ''}`);
234
345
  const result = yield server.setupServer(`./${projectDir}`, {
@@ -12,6 +12,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const path_1 = require("path");
13
13
  const framework_detection_1 = require("../lib/framework-detection");
14
14
  const frontend_framework_detection_1 = require("../lib/frontend-framework-detection");
15
+ const port_registry_1 = require("../lib/port-registry");
16
+ const workspace_integration_1 = require("../lib/workspace-integration");
15
17
  /**
16
18
  * Show project status and context
17
19
  */
@@ -21,7 +23,7 @@ const StatusCommand = {
21
23
  hidden: false,
22
24
  name: 'status',
23
25
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
24
- var _a, _b;
26
+ var _a, _b, _c;
25
27
  const { filesystem, print: { colors, info, success, warning }, system, } = toolbox;
26
28
  const cwd = filesystem.cwd();
27
29
  info('');
@@ -42,6 +44,18 @@ const StatusCommand = {
42
44
  packageName: null,
43
45
  packageVersion: null,
44
46
  projectType: 'unknown',
47
+ // Probe layout at the workspace root if cwd is inside a
48
+ // sub-project; otherwise probe at cwd. Without this, status
49
+ // shown from `projects/api/src/` would report both halves
50
+ // missing because there's no `projects/` underneath cwd.
51
+ workspaceLayout: (() => {
52
+ const ctx = (0, workspace_integration_1.detectSubProjectContext)(cwd, filesystem);
53
+ return (0, workspace_integration_1.detectWorkspaceLayout)(ctx ? ctx.workspaceRoot : cwd, filesystem);
54
+ })(),
55
+ workspaceSubProject: (() => {
56
+ const ctx = (0, workspace_integration_1.detectSubProjectContext)(cwd, filesystem);
57
+ return ctx ? { kind: ctx.kind, root: ctx.workspaceRoot } : null;
58
+ })(),
45
59
  };
46
60
  // Check for lt.config
47
61
  const ltConfigFiles = ['lt.config.json', 'lt.config.yaml', 'lt.config'];
@@ -94,7 +108,7 @@ const StatusCommand = {
94
108
  projectInfo.projectType = 'node';
95
109
  }
96
110
  }
97
- catch (_c) {
111
+ catch (_d) {
98
112
  // Ignore parse errors
99
113
  }
100
114
  }
@@ -123,7 +137,7 @@ const StatusCommand = {
123
137
  });
124
138
  }
125
139
  }
126
- catch (_d) {
140
+ catch (_e) {
127
141
  // ignore
128
142
  }
129
143
  }
@@ -139,7 +153,7 @@ const StatusCommand = {
139
153
  });
140
154
  }
141
155
  }
142
- catch (_e) {
156
+ catch (_f) {
143
157
  // ignore
144
158
  }
145
159
  }
@@ -154,7 +168,7 @@ const StatusCommand = {
154
168
  projectInfo.gitBranch = (branch === null || branch === void 0 ? void 0 : branch.trim()) || null;
155
169
  }
156
170
  }
157
- catch (_f) {
171
+ catch (_g) {
158
172
  // Not a git repository
159
173
  }
160
174
  // Get Node/npm versions
@@ -162,7 +176,7 @@ const StatusCommand = {
162
176
  projectInfo.nodeVersion = ((_a = (yield system.run('node --version 2>/dev/null'))) === null || _a === void 0 ? void 0 : _a.trim()) || null;
163
177
  projectInfo.npmVersion = ((_b = (yield system.run('npm --version 2>/dev/null'))) === null || _b === void 0 ? void 0 : _b.trim()) || null;
164
178
  }
165
- catch (_g) {
179
+ catch (_h) {
166
180
  // Ignore errors
167
181
  }
168
182
  // Display project info
@@ -192,6 +206,65 @@ const StatusCommand = {
192
206
  info(` Frontend Framework: ${frontendModeLabel}`);
193
207
  }
194
208
  }
209
+ // Workspace overview — surfaces the layout that drives the
210
+ // `add-api` / `add-app` / standalone gate decisions. Shown
211
+ // whenever we're inside a workspace OR a sub-project of one,
212
+ // so users immediately see what's missing and where the
213
+ // workspace root is.
214
+ if (projectInfo.workspaceLayout.hasWorkspace || projectInfo.workspaceSubProject) {
215
+ info('');
216
+ info(colors.bold('Workspace:'));
217
+ if (projectInfo.workspaceSubProject) {
218
+ const { kind, root } = projectInfo.workspaceSubProject;
219
+ warning(` You are inside projects/${kind}/ of a workspace.`);
220
+ info(` Root: ${root}`);
221
+ info(colors.dim(` Hint: cd to the workspace root for \`lt fullstack add-${kind === 'api' ? 'app' : 'api'}\``));
222
+ }
223
+ else {
224
+ success(' Detected (pnpm-workspace.yaml, package.json#workspaces, or projects/)');
225
+ }
226
+ const apiMark = projectInfo.workspaceLayout.hasApi ? colors.green('✓') : colors.yellow('✗');
227
+ const appMark = projectInfo.workspaceLayout.hasApp ? colors.green('✓') : colors.yellow('✗');
228
+ info(` ${apiMark} projects/api/`);
229
+ info(` ${appMark} projects/app/`);
230
+ // Suggest the next step when only one half is present.
231
+ if (projectInfo.workspaceLayout.hasApp && !projectInfo.workspaceLayout.hasApi) {
232
+ info(colors.dim(' Hint: `lt fullstack add-api` to integrate a NestJS server.'));
233
+ }
234
+ else if (projectInfo.workspaceLayout.hasApi && !projectInfo.workspaceLayout.hasApp) {
235
+ info(colors.dim(' Hint: `lt fullstack add-app` to integrate a Nuxt or Angular app.'));
236
+ }
237
+ }
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?".
241
+ {
242
+ const registryRoot = ((_c = projectInfo.workspaceSubProject) === null || _c === void 0 ? void 0 : _c.root) ||
243
+ (projectInfo.workspaceLayout.hasWorkspace ? projectInfo.workspaceLayout.workspaceDir : cwd);
244
+ if (registryRoot) {
245
+ const slug = (0, port_registry_1.projectSlug)(registryRoot);
246
+ const entry = (0, port_registry_1.loadRegistry)().projects[slug];
247
+ if (entry) {
248
+ 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}`);
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;
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 local up` to start API + App with these ports.'));
264
+ }
265
+ }
266
+ }
267
+ }
195
268
  // Show monorepo subprojects if we detected any (typically at monorepo root)
196
269
  if (projectInfo.monorepoSubprojects.length > 0) {
197
270
  info('');
@@ -30,8 +30,13 @@ const NewCommand = {
30
30
  description: 'OCR PDFs to Markdown via marker-pdf (MPS-accelerated on Apple Silicon)',
31
31
  hidden: false,
32
32
  name: 'ocr',
33
- run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
33
+ // GluegunCommand types `run` against the base `Toolbox`, but the lt CLI
34
+ // augments it with `helper`, `git`, etc. Cast inside so the implementation
35
+ // remains typed against the project-specific `ExtendedGluegunToolbox` while
36
+ // satisfying the upstream `(toolbox: Toolbox) => void` signature.
37
+ run: (rawToolbox) => __awaiter(void 0, void 0, void 0, function* () {
34
38
  var _a, _b, _c, _d, _e;
39
+ const toolbox = rawToolbox;
35
40
  const { parameters, print: { error, info, spin, warning }, } = toolbox;
36
41
  const showStatus = !!parameters.options.status;
37
42
  const installOnly = !!parameters.options.install;
@@ -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
  }