@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.
- package/README.md +19 -0
- package/build/commands/frontend/angular.js +110 -4
- package/build/commands/frontend/nuxt.js +184 -13
- package/build/commands/fullstack/add-api.js +360 -0
- package/build/commands/fullstack/add-app.js +284 -0
- package/build/commands/fullstack/init.js +46 -0
- package/build/commands/local/down.js +71 -0
- package/build/commands/local/init.js +162 -0
- package/build/commands/local/local.js +30 -0
- package/build/commands/local/status.js +69 -0
- package/build/commands/local/up.js +148 -0
- package/build/commands/ports/ports.js +118 -0
- package/build/commands/ports/scan.js +131 -0
- package/build/commands/server/create.js +118 -7
- package/build/commands/status.js +79 -6
- package/build/commands/tools/ocr.js +6 -1
- package/build/extensions/server.js +14 -4
- package/build/lib/local-patches.js +175 -0
- package/build/lib/local-project.js +101 -0
- package/build/lib/port-registry.js +304 -0
- package/build/lib/workspace-integration.js +351 -0
- package/docs/commands.md +292 -4
- package/package.json +8 -10
|
@@ -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
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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}`, {
|
package/build/commands/status.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
140
|
+
catch (_e) {
|
|
127
141
|
// ignore
|
|
128
142
|
}
|
|
129
143
|
}
|
|
@@ -139,7 +153,7 @@ const StatusCommand = {
|
|
|
139
153
|
});
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
|
-
catch (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|