@lenne.tech/cli 1.9.6 → 1.11.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.
Files changed (38) hide show
  1. package/README.md +88 -3
  2. package/build/commands/config/validate.js +2 -0
  3. package/build/commands/frontend/convert-mode.js +198 -0
  4. package/build/commands/fullstack/convert-mode.js +368 -0
  5. package/build/commands/fullstack/init.js +150 -4
  6. package/build/commands/fullstack/update.js +177 -0
  7. package/build/commands/server/add-property.js +29 -2
  8. package/build/commands/server/convert-mode.js +197 -0
  9. package/build/commands/server/create.js +41 -3
  10. package/build/commands/server/module.js +58 -25
  11. package/build/commands/server/object.js +26 -5
  12. package/build/commands/server/permissions.js +20 -6
  13. package/build/commands/server/test.js +7 -1
  14. package/build/commands/status.js +94 -3
  15. package/build/config/vendor-frontend-runtime-deps.json +4 -0
  16. package/build/config/vendor-runtime-deps.json +9 -0
  17. package/build/extensions/api-mode.js +19 -3
  18. package/build/extensions/frontend-helper.js +652 -0
  19. package/build/extensions/server.js +1475 -3
  20. package/build/lib/framework-detection.js +167 -0
  21. package/build/lib/frontend-framework-detection.js +129 -0
  22. package/build/templates/nest-server-module/inputs/template-create.input.ts.ejs +1 -1
  23. package/build/templates/nest-server-module/inputs/template.input.ts.ejs +1 -1
  24. package/build/templates/nest-server-module/outputs/template-fac-result.output.ts.ejs +1 -1
  25. package/build/templates/nest-server-module/template.controller.ts.ejs +1 -1
  26. package/build/templates/nest-server-module/template.model.ts.ejs +1 -1
  27. package/build/templates/nest-server-module/template.module.ts.ejs +1 -1
  28. package/build/templates/nest-server-module/template.resolver.ts.ejs +1 -1
  29. package/build/templates/nest-server-module/template.service.ts.ejs +1 -1
  30. package/build/templates/nest-server-object/template-create.input.ts.ejs +1 -1
  31. package/build/templates/nest-server-object/template.input.ts.ejs +1 -1
  32. package/build/templates/nest-server-object/template.object.ts.ejs +1 -1
  33. package/build/templates/nest-server-tests/tests.e2e-spec.ts.ejs +1 -1
  34. package/docs/LT-ECOSYSTEM-GUIDE.md +973 -0
  35. package/docs/VENDOR-MODE-WORKFLOW.md +471 -0
  36. package/docs/commands.md +196 -0
  37. package/docs/lt.config.md +9 -7
  38. package/package.json +17 -8
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const path_1 = require("path");
16
+ const framework_detection_1 = require("../../lib/framework-detection");
16
17
  const object_1 = __importDefault(require("./object"));
17
18
  /**
18
19
  * Detect controller type based on existing modules
@@ -272,27 +273,43 @@ const NewCommand = {
272
273
  const inputTemplate = server.propsForInput(props, { modelName: name, nullable: true });
273
274
  const createTemplate = server.propsForInput(props, { create: true, modelName: name, nullable: false });
274
275
  const modelTemplate = server.propsForModel(props, { modelName: name });
276
+ // Compute the correct framework-import specifier for each generated file.
277
+ // In vendored projects this resolves to a relative path to src/core (depth
278
+ // depends on file location); in npm projects it stays '@lenne.tech/nest-server'.
279
+ const importFor = (target) => (0, framework_detection_1.getFrameworkImportSpecifier)(path, target);
275
280
  // nest-server-module/inputs/xxx.input.ts
281
+ const inputTarget = (0, path_1.join)(directory, 'inputs', `${nameKebab}.input.ts`);
276
282
  yield template.generate({
277
- props: { imports: inputTemplate.imports, nameCamel, nameKebab, namePascal, props: inputTemplate.props },
278
- target: (0, path_1.join)(directory, 'inputs', `${nameKebab}.input.ts`),
283
+ props: {
284
+ frameworkImport: importFor(inputTarget),
285
+ imports: inputTemplate.imports,
286
+ nameCamel,
287
+ nameKebab,
288
+ namePascal,
289
+ props: inputTemplate.props,
290
+ },
291
+ target: inputTarget,
279
292
  template: 'nest-server-module/inputs/template.input.ts.ejs',
280
293
  });
281
294
  if (controller === 'Rest' || controller === 'Both') {
295
+ const controllerTarget = (0, path_1.join)(directory, `${nameKebab}.controller.ts`);
282
296
  yield template.generate({
283
297
  props: {
298
+ frameworkImport: importFor(controllerTarget),
284
299
  lowercase: name.toLowerCase(),
285
300
  nameCamel: camelCase(name),
286
301
  nameKebab: kebabCase(name),
287
302
  namePascal: pascalCase(name),
288
303
  },
289
- target: (0, path_1.join)(directory, `${nameKebab}.controller.ts`),
304
+ target: controllerTarget,
290
305
  template: 'nest-server-module/template.controller.ts.ejs',
291
306
  });
292
307
  }
293
308
  // nest-server-module/inputs/xxx-create.input.ts
309
+ const createInputTarget = (0, path_1.join)(directory, 'inputs', `${nameKebab}-create.input.ts`);
294
310
  yield template.generate({
295
311
  props: {
312
+ frameworkImport: importFor(createInputTarget),
296
313
  imports: createTemplate.imports,
297
314
  isGql: controller === 'GraphQL' || controller === 'Both',
298
315
  nameCamel,
@@ -300,18 +317,27 @@ const NewCommand = {
300
317
  namePascal,
301
318
  props: createTemplate.props,
302
319
  },
303
- target: (0, path_1.join)(directory, 'inputs', `${nameKebab}-create.input.ts`),
320
+ target: createInputTarget,
304
321
  template: 'nest-server-module/inputs/template-create.input.ts.ejs',
305
322
  });
306
323
  // nest-server-module/output/find-and-count-xxxs-result.output.ts
324
+ const facOutputTarget = (0, path_1.join)(directory, 'outputs', `find-and-count-${nameKebab}s-result.output.ts`);
307
325
  yield template.generate({
308
- props: { isGql: controller === 'GraphQL' || controller === 'Both', nameCamel, nameKebab, namePascal },
309
- target: (0, path_1.join)(directory, 'outputs', `find-and-count-${nameKebab}s-result.output.ts`),
326
+ props: {
327
+ frameworkImport: importFor(facOutputTarget),
328
+ isGql: controller === 'GraphQL' || controller === 'Both',
329
+ nameCamel,
330
+ nameKebab,
331
+ namePascal,
332
+ },
333
+ target: facOutputTarget,
310
334
  template: 'nest-server-module/outputs/template-fac-result.output.ts.ejs',
311
335
  });
312
336
  // nest-server-module/xxx.model.ts
337
+ const modelTarget = (0, path_1.join)(directory, `${nameKebab}.model.ts`);
313
338
  yield template.generate({
314
339
  props: {
340
+ frameworkImport: importFor(modelTarget),
315
341
  imports: modelTemplate.imports,
316
342
  isGql: controller === 'GraphQL' || controller === 'Both',
317
343
  mappings: modelTemplate.mappings,
@@ -320,27 +346,36 @@ const NewCommand = {
320
346
  namePascal,
321
347
  props: modelTemplate.props,
322
348
  },
323
- target: (0, path_1.join)(directory, `${nameKebab}.model.ts`),
349
+ target: modelTarget,
324
350
  template: 'nest-server-module/template.model.ts.ejs',
325
351
  });
326
352
  // nest-server-module/xxx.module.ts
353
+ const moduleTarget = (0, path_1.join)(directory, `${nameKebab}.module.ts`);
327
354
  yield template.generate({
328
- props: { controller, nameCamel, nameKebab, namePascal },
329
- target: (0, path_1.join)(directory, `${nameKebab}.module.ts`),
355
+ props: { controller, frameworkImport: importFor(moduleTarget), nameCamel, nameKebab, namePascal },
356
+ target: moduleTarget,
330
357
  template: 'nest-server-module/template.module.ts.ejs',
331
358
  });
332
359
  if (controller === 'GraphQL' || controller === 'Both') {
333
360
  // nest-server-module/xxx.resolver.ts
361
+ const resolverTarget = (0, path_1.join)(directory, `${nameKebab}.resolver.ts`);
334
362
  yield template.generate({
335
- props: { nameCamel, nameKebab, namePascal },
336
- target: (0, path_1.join)(directory, `${nameKebab}.resolver.ts`),
363
+ props: { frameworkImport: importFor(resolverTarget), nameCamel, nameKebab, namePascal },
364
+ target: resolverTarget,
337
365
  template: 'nest-server-module/template.resolver.ts.ejs',
338
366
  });
339
367
  }
340
368
  // nest-server-module/xxx.service.ts
369
+ const serviceTarget = (0, path_1.join)(directory, `${nameKebab}.service.ts`);
341
370
  yield template.generate({
342
- props: { isGql: controller === 'GraphQL' || controller === 'Both', nameCamel, nameKebab, namePascal },
343
- target: (0, path_1.join)(directory, `${nameKebab}.service.ts`),
371
+ props: {
372
+ frameworkImport: importFor(serviceTarget),
373
+ isGql: controller === 'GraphQL' || controller === 'Both',
374
+ nameCamel,
375
+ nameKebab,
376
+ namePascal,
377
+ },
378
+ target: serviceTarget,
344
379
  template: 'nest-server-module/template.service.ts.ejs',
345
380
  });
346
381
  generateSpinner.succeed('Files generated');
@@ -365,20 +400,18 @@ const NewCommand = {
365
400
  });
366
401
  // Ensure forwardRef is imported from @nestjs/common
367
402
  const serverModuleContent = filesystem.read(serverModule);
368
- if (serverModuleContent && !serverModuleContent.includes('forwardRef')) {
369
- // Add forwardRef to @nestjs/common import
370
- yield patching.patch(serverModule, {
371
- insert: '$1, forwardRef$2',
372
- replace: /from '@nestjs\/common'(.*?)}/,
373
- });
374
- }
375
- else if (serverModuleContent &&
403
+ if (serverModuleContent &&
376
404
  serverModuleContent.includes('@nestjs/common') &&
377
- !serverModuleContent.match(/forwardRef.*@nestjs\/common|@nestjs\/common.*forwardRef/)) {
378
- // forwardRef exists but not in @nestjs/common import - add it
405
+ !serverModuleContent.match(/import\s*\{[^}]*forwardRef[^}]*}\s*from\s+['"]@nestjs\/common['"]/)) {
406
+ // Add forwardRef into the existing `import { ... } from '@nestjs/common'`
407
+ // statement by inserting it before the closing brace. The regex
408
+ // captures two groups: (1) everything up to (but not including) the
409
+ // closing brace of the named-import list and (2) the closing brace
410
+ // plus the `from '@nestjs/common'` clause. The replacement wedges
411
+ // `, forwardRef` in between.
379
412
  yield patching.patch(serverModule, {
380
- insert: '$1, forwardRef$2',
381
- replace: /(\w+)\s*}\s*from\s+'@nestjs\/common'/,
413
+ insert: "$1, forwardRef$2",
414
+ replace: /(import\s*\{\s*[^}]*?)(\s*\}\s*from\s+['"]@nestjs\/common['"])/,
382
415
  });
383
416
  }
384
417
  }
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const path_1 = require("path");
16
+ const framework_detection_1 = require("../../lib/framework-detection");
16
17
  const module_1 = __importDefault(require("./module"));
17
18
  /**
18
19
  * Create a new server object
@@ -133,21 +134,41 @@ const NewCommand = {
133
134
  const inputTemplate = server.propsForInput(props, { modelName: name, nullable: true });
134
135
  const createTemplate = server.propsForInput(props, { create: true, modelName: name, nullable: false });
135
136
  const objectTemplate = server.propsForModel(props, { modelName: name });
137
+ // Framework-import specifier (bare in npm mode, relative in vendored mode)
138
+ const importFor = (target) => (0, framework_detection_1.getFrameworkImportSpecifier)(path, target);
136
139
  // nest-server-module/inputs/xxx.input.ts
140
+ const inputTarget = (0, path_1.join)(directory, `${nameKebab}.input.ts`);
137
141
  yield template.generate({
138
- props: { imports: inputTemplate.imports, nameCamel, nameKebab, namePascal, props: inputTemplate.props },
139
- target: (0, path_1.join)(directory, `${nameKebab}.input.ts`),
142
+ props: {
143
+ frameworkImport: importFor(inputTarget),
144
+ imports: inputTemplate.imports,
145
+ nameCamel,
146
+ nameKebab,
147
+ namePascal,
148
+ props: inputTemplate.props,
149
+ },
150
+ target: inputTarget,
140
151
  template: 'nest-server-object/template.input.ts.ejs',
141
152
  });
142
153
  // nest-server-object/inputs/xxx-create.input.ts
154
+ const createInputTarget = (0, path_1.join)(directory, `${nameKebab}-create.input.ts`);
143
155
  yield template.generate({
144
- props: { imports: createTemplate.imports, nameCamel, nameKebab, namePascal, props: createTemplate.props },
145
- target: (0, path_1.join)(directory, `${nameKebab}-create.input.ts`),
156
+ props: {
157
+ frameworkImport: importFor(createInputTarget),
158
+ imports: createTemplate.imports,
159
+ nameCamel,
160
+ nameKebab,
161
+ namePascal,
162
+ props: createTemplate.props,
163
+ },
164
+ target: createInputTarget,
146
165
  template: 'nest-server-object/template-create.input.ts.ejs',
147
166
  });
148
167
  // nest-server-module/xxx.model.ts
168
+ const objectTarget = (0, path_1.join)(directory, `${nameKebab}.object.ts`);
149
169
  yield template.generate({
150
170
  props: {
171
+ frameworkImport: importFor(objectTarget),
151
172
  imports: objectTemplate.imports,
152
173
  mappings: objectTemplate.mappings,
153
174
  nameCamel,
@@ -155,7 +176,7 @@ const NewCommand = {
155
176
  namePascal,
156
177
  props: objectTemplate.props,
157
178
  },
158
- target: (0, path_1.join)(directory, `${nameKebab}.object.ts`),
179
+ target: objectTarget,
159
180
  template: 'nest-server-object/template.object.ts.ejs',
160
181
  });
161
182
  generateSpinner.succeed('Files generated');
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const child_process_1 = require("child_process");
13
13
  const fs_1 = require("fs");
14
14
  const path_1 = require("path");
15
+ const framework_detection_1 = require("../../lib/framework-detection");
15
16
  // ────────────────────────────────────────────────────────────────────────────
16
17
  // Helper functions (alphabetically sorted per ESLint rules)
17
18
  // ────────────────────────────────────────────────────────────────────────────
@@ -82,11 +83,23 @@ function generateJson(report, projectPath) {
82
83
  */
83
84
  function loadScanner(projectPath) {
84
85
  return __awaiter(this, void 0, void 0, function* () {
85
- // Try 1: Load from project's nest-server (preferred)
86
- const scannerPaths = [
87
- (0, path_1.join)(projectPath, 'node_modules', '@lenne.tech', 'nest-server', 'dist', 'core', 'modules', 'permissions', 'permissions-scanner'),
88
- (0, path_1.join)(projectPath, 'node_modules', '@lenne.tech', 'nest-server', 'dist', 'core', 'modules', 'permissions', 'permissions-scanner.js'),
89
- ];
86
+ // Build an ordered list of candidate paths, mode-aware:
87
+ //
88
+ // - Vendored projects: the framework code lives in src/core/ as
89
+ // project-compiled TypeScript. After `nest build` the compiled output
90
+ // lands under dist/src/core/modules/permissions/. Before the build,
91
+ // the .ts source sits at src/core/modules/permissions/permissions-scanner.ts
92
+ // (which Node's `require()` cannot load directly). We try the dist
93
+ // variant first and fall back to the bundled CLI scanner if the
94
+ // project hasn't been built yet.
95
+ // - npm projects: classic path under node_modules/@lenne.tech/nest-server/dist.
96
+ const scannerPaths = [];
97
+ if ((0, framework_detection_1.isVendoredProject)(projectPath)) {
98
+ scannerPaths.push((0, path_1.join)(projectPath, 'dist', 'src', 'core', 'modules', 'permissions', 'permissions-scanner'), (0, path_1.join)(projectPath, 'dist', 'src', 'core', 'modules', 'permissions', 'permissions-scanner.js'), (0, path_1.join)(projectPath, 'dist', 'core', 'modules', 'permissions', 'permissions-scanner'), (0, path_1.join)(projectPath, 'dist', 'core', 'modules', 'permissions', 'permissions-scanner.js'));
99
+ }
100
+ else {
101
+ scannerPaths.push((0, path_1.join)(projectPath, 'node_modules', '@lenne.tech', 'nest-server', 'dist', 'core', 'modules', 'permissions', 'permissions-scanner'), (0, path_1.join)(projectPath, 'node_modules', '@lenne.tech', 'nest-server', 'dist', 'core', 'modules', 'permissions', 'permissions-scanner.js'));
102
+ }
90
103
  for (const scannerPath of scannerPaths) {
91
104
  try {
92
105
  const mod = require(scannerPath);
@@ -101,7 +114,8 @@ function loadScanner(projectPath) {
101
114
  // Not available at this path
102
115
  }
103
116
  }
104
- // Try 2: Use CLI's bundled fallback scanner
117
+ // Fallback: Use CLI's bundled scanner. Covers both "not yet built" (vendor
118
+ // mode before `nest build`) and "framework not installed" (detached clone).
105
119
  try {
106
120
  const fallback = require('../../lib/fallback-scanner');
107
121
  if (typeof fallback.scanPermissions === 'function') {
@@ -10,6 +10,7 @@ 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 framework_detection_1 = require("../../lib/framework-detection");
13
14
  /**
14
15
  * Create a new server
15
16
  */
@@ -55,7 +56,12 @@ const NewCommand = {
55
56
  const generateSpinner = spin('Generate test file');
56
57
  // nest-server-tests/tests.e2e-spec.ts.ejs
57
58
  yield template.generate({
58
- props: { nameCamel, nameKebab, namePascal },
59
+ props: {
60
+ frameworkImport: (0, framework_detection_1.getFrameworkImportSpecifier)(path, filePath),
61
+ nameCamel,
62
+ nameKebab,
63
+ namePascal,
64
+ },
59
65
  target: filePath,
60
66
  template: 'nest-server-tests/tests.e2e-spec.ts.ejs',
61
67
  });
@@ -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 framework_detection_1 = require("../lib/framework-detection");
14
+ const frontend_framework_detection_1 = require("../lib/frontend-framework-detection");
13
15
  /**
14
16
  * Show project status and context
15
17
  */
@@ -27,11 +29,14 @@ const StatusCommand = {
27
29
  info(colors.dim('─'.repeat(50)));
28
30
  const projectInfo = {
29
31
  configFiles: [],
32
+ frameworkMode: null,
33
+ frontendFrameworkMode: null,
30
34
  gitBranch: null,
31
35
  gitRoot: null,
32
36
  hasGit: false,
33
37
  hasLtConfig: false,
34
38
  hasPackageJson: false,
39
+ monorepoSubprojects: [],
35
40
  nodeVersion: null,
36
41
  npmVersion: null,
37
42
  packageName: null,
@@ -56,14 +61,22 @@ const StatusCommand = {
56
61
  projectInfo.packageVersion = packageJson.version || null;
57
62
  // Detect project type
58
63
  const deps = Object.assign(Object.assign({}, packageJson.dependencies), packageJson.devDependencies);
59
- if (deps['@lenne.tech/nest-server']) {
64
+ // A project is a nest-server project if it EITHER has the npm dep
65
+ // (classic) OR has vendored the core/ directory. The frameworkMode
66
+ // field records which of the two modes this project runs in.
67
+ if (deps['@lenne.tech/nest-server'] || (0, framework_detection_1.isVendoredProject)(cwd)) {
60
68
  projectInfo.projectType = 'nest-server';
69
+ projectInfo.frameworkMode = (0, framework_detection_1.detectFrameworkMode)(cwd);
61
70
  }
62
71
  else if (deps['@nestjs/core']) {
63
72
  projectInfo.projectType = 'nestjs';
64
73
  }
65
74
  else if (deps['nuxt']) {
66
75
  projectInfo.projectType = 'nuxt';
76
+ // Detect frontend framework mode if nuxt-extensions is present
77
+ if (deps['@lenne.tech/nuxt-extensions'] || (0, frontend_framework_detection_1.isVendoredAppProject)(cwd)) {
78
+ projectInfo.frontendFrameworkMode = (0, frontend_framework_detection_1.detectFrontendFrameworkMode)(cwd);
79
+ }
67
80
  }
68
81
  else if (deps['@angular/core']) {
69
82
  projectInfo.projectType = 'angular';
@@ -85,6 +98,52 @@ const StatusCommand = {
85
98
  // Ignore parse errors
86
99
  }
87
100
  }
101
+ // Monorepo subproject detection: scan projects/api and projects/app for
102
+ // framework modes so that `lt status` at the monorepo root surfaces
103
+ // backend + frontend framework consumption modes even when the root
104
+ // itself is not a Nest/Nuxt project.
105
+ const monorepoCandidates = [
106
+ { kind: 'backend', path: (0, path_1.join)(cwd, 'projects', 'api') },
107
+ { kind: 'backend', path: (0, path_1.join)(cwd, 'packages', 'api') },
108
+ { kind: 'frontend', path: (0, path_1.join)(cwd, 'projects', 'app') },
109
+ { kind: 'frontend', path: (0, path_1.join)(cwd, 'packages', 'app') },
110
+ ];
111
+ for (const candidate of monorepoCandidates) {
112
+ if (!filesystem.exists((0, path_1.join)(candidate.path, 'package.json')))
113
+ continue;
114
+ if (candidate.kind === 'backend') {
115
+ try {
116
+ const subPkg = JSON.parse(filesystem.read((0, path_1.join)(candidate.path, 'package.json')) || '{}');
117
+ const subDeps = Object.assign(Object.assign({}, subPkg.dependencies), subPkg.devDependencies);
118
+ if (subDeps['@lenne.tech/nest-server'] || (0, framework_detection_1.isVendoredProject)(candidate.path)) {
119
+ projectInfo.monorepoSubprojects.push({
120
+ frameworkMode: (0, framework_detection_1.detectFrameworkMode)(candidate.path),
121
+ kind: 'backend',
122
+ path: candidate.path,
123
+ });
124
+ }
125
+ }
126
+ catch (_d) {
127
+ // ignore
128
+ }
129
+ }
130
+ else {
131
+ try {
132
+ const subPkg = JSON.parse(filesystem.read((0, path_1.join)(candidate.path, 'package.json')) || '{}');
133
+ const subDeps = Object.assign(Object.assign({}, subPkg.dependencies), subPkg.devDependencies);
134
+ if (subDeps['@lenne.tech/nuxt-extensions'] || (0, frontend_framework_detection_1.isVendoredAppProject)(candidate.path)) {
135
+ projectInfo.monorepoSubprojects.push({
136
+ frameworkMode: (0, frontend_framework_detection_1.detectFrontendFrameworkMode)(candidate.path),
137
+ kind: 'frontend',
138
+ path: candidate.path,
139
+ });
140
+ }
141
+ }
142
+ catch (_e) {
143
+ // ignore
144
+ }
145
+ }
146
+ }
88
147
  // Check for git
89
148
  try {
90
149
  const gitRoot = yield system.run('git rev-parse --show-toplevel 2>/dev/null');
@@ -95,7 +154,7 @@ const StatusCommand = {
95
154
  projectInfo.gitBranch = (branch === null || branch === void 0 ? void 0 : branch.trim()) || null;
96
155
  }
97
156
  }
98
- catch (_d) {
157
+ catch (_f) {
99
158
  // Not a git repository
100
159
  }
101
160
  // Get Node/npm versions
@@ -103,7 +162,7 @@ const StatusCommand = {
103
162
  projectInfo.nodeVersion = ((_a = (yield system.run('node --version 2>/dev/null'))) === null || _a === void 0 ? void 0 : _a.trim()) || null;
104
163
  projectInfo.npmVersion = ((_b = (yield system.run('npm --version 2>/dev/null'))) === null || _b === void 0 ? void 0 : _b.trim()) || null;
105
164
  }
106
- catch (_e) {
165
+ catch (_g) {
107
166
  // Ignore errors
108
167
  }
109
168
  // Display project info
@@ -120,6 +179,38 @@ const StatusCommand = {
120
179
  info(` Version: ${projectInfo.packageVersion}`);
121
180
  }
122
181
  info(` Type: ${formatProjectType(projectInfo.projectType)}`);
182
+ if (projectInfo.frameworkMode) {
183
+ const modeLabel = projectInfo.frameworkMode === 'vendor'
184
+ ? 'vendor (src/core/, VENDOR.md)'
185
+ : 'npm (@lenne.tech/nest-server dependency)';
186
+ info(` Framework: ${modeLabel}`);
187
+ }
188
+ if (projectInfo.frontendFrameworkMode) {
189
+ const frontendModeLabel = projectInfo.frontendFrameworkMode === 'vendor'
190
+ ? 'vendor (app/core/, VENDOR.md)'
191
+ : 'npm (@lenne.tech/nuxt-extensions dependency)';
192
+ info(` Frontend Framework: ${frontendModeLabel}`);
193
+ }
194
+ }
195
+ // Show monorepo subprojects if we detected any (typically at monorepo root)
196
+ if (projectInfo.monorepoSubprojects.length > 0) {
197
+ info('');
198
+ info(colors.bold('Monorepo Subprojects:'));
199
+ for (const sub of projectInfo.monorepoSubprojects) {
200
+ const relPath = sub.path.replace(`${cwd}/`, '');
201
+ if (sub.kind === 'backend') {
202
+ const label = sub.frameworkMode === 'vendor'
203
+ ? 'vendor (src/core/, VENDOR.md)'
204
+ : 'npm (@lenne.tech/nest-server dependency)';
205
+ info(` Backend: ${relPath} → ${label}`);
206
+ }
207
+ else {
208
+ const label = sub.frameworkMode === 'vendor'
209
+ ? 'vendor (app/core/, VENDOR.md)'
210
+ : 'npm (@lenne.tech/nuxt-extensions dependency)';
211
+ info(` Frontend: ${relPath} → ${label}`);
212
+ }
213
+ }
123
214
  }
124
215
  if (projectInfo.hasGit) {
125
216
  info('');
@@ -0,0 +1,4 @@
1
+ {
2
+ "$comment": "Upstream @lenne.tech/nuxt-extensions devDependencies that must be promoted to regular dependencies in vendor mode. Currently empty because nuxt-extensions has only @nuxt/kit as a direct dependency (which is handled explicitly during vendoring).",
3
+ "runtimeHelpers": []
4
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "./vendor-runtime-deps.schema.json",
3
+ "description": "Upstream @lenne.tech/nest-server devDependencies that are actually needed at runtime in a consumer project. When vendoring, these entries are promoted from upstream devDependencies into the project's dependencies so production runtime has them available.",
4
+ "runtimeHelpers": [
5
+ "find-file-up"
6
+ ]
7
+ }
8
+ </content>
9
+ </invoke>
@@ -373,9 +373,25 @@ class ApiMode {
373
373
  }
374
374
  i++;
375
375
  }
376
- // Get the non-import part of the file
377
- const maxImportEnd = importLines.reduce((max, il) => Math.max(max, il.end), -1);
378
- const codeContent = lines.slice(maxImportEnd + 1).join('\n');
376
+ // Build the "code content" view against which import usage is checked.
377
+ //
378
+ // Previous implementation: `lines.slice(maxImportEnd + 1)` — only the
379
+ // lines AFTER the last import. That breaks for files where imports and
380
+ // top-level code are interleaved (e.g. a helper `const` declared between
381
+ // two import groups). Those inter-import usages were never seen, so the
382
+ // still-used identifiers got pruned.
383
+ //
384
+ // Fix: build a mask where all import lines are blanked out but every
385
+ // other line is preserved, so inter-import usages still count.
386
+ const importLineSet = new Set();
387
+ for (const imp of importLines) {
388
+ for (let j = imp.start; j <= imp.end; j++) {
389
+ importLineSet.add(j);
390
+ }
391
+ }
392
+ const codeContent = lines
393
+ .map((line, idx) => (importLineSet.has(idx) ? '' : line))
394
+ .join('\n');
379
395
  // Check each import
380
396
  const linesToRemove = new Set();
381
397
  for (const imp of importLines) {