@lenne.tech/cli 1.9.6 → 1.10.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 (30) hide show
  1. package/README.md +83 -0
  2. package/build/commands/fullstack/init.js +108 -4
  3. package/build/commands/fullstack/update.js +129 -0
  4. package/build/commands/server/add-property.js +29 -2
  5. package/build/commands/server/create.js +41 -3
  6. package/build/commands/server/module.js +58 -25
  7. package/build/commands/server/object.js +26 -5
  8. package/build/commands/server/permissions.js +20 -6
  9. package/build/commands/server/test.js +7 -1
  10. package/build/commands/status.js +13 -1
  11. package/build/config/vendor-runtime-deps.json +9 -0
  12. package/build/extensions/api-mode.js +19 -3
  13. package/build/extensions/server.js +1028 -3
  14. package/build/lib/framework-detection.js +167 -0
  15. package/build/templates/nest-server-module/inputs/template-create.input.ts.ejs +1 -1
  16. package/build/templates/nest-server-module/inputs/template.input.ts.ejs +1 -1
  17. package/build/templates/nest-server-module/outputs/template-fac-result.output.ts.ejs +1 -1
  18. package/build/templates/nest-server-module/template.controller.ts.ejs +1 -1
  19. package/build/templates/nest-server-module/template.model.ts.ejs +1 -1
  20. package/build/templates/nest-server-module/template.module.ts.ejs +1 -1
  21. package/build/templates/nest-server-module/template.resolver.ts.ejs +1 -1
  22. package/build/templates/nest-server-module/template.service.ts.ejs +1 -1
  23. package/build/templates/nest-server-object/template-create.input.ts.ejs +1 -1
  24. package/build/templates/nest-server-object/template.input.ts.ejs +1 -1
  25. package/build/templates/nest-server-object/template.object.ts.ejs +1 -1
  26. package/build/templates/nest-server-tests/tests.e2e-spec.ts.ejs +1 -1
  27. package/build/templates/vendor-scripts/check-vendor-freshness.mjs +131 -0
  28. package/build/templates/vendor-scripts/propose-upstream-pr.ts +269 -0
  29. package/build/templates/vendor-scripts/sync-from-upstream.ts +250 -0
  30. package/package.json +16 -8
package/README.md CHANGED
@@ -55,6 +55,89 @@ $ lt status
55
55
  $ lt completion install
56
56
  ```
57
57
 
58
+ ## Framework consumption modes (nest-server)
59
+
60
+ When you create a new api project (`lt fullstack init` or `lt server create`),
61
+ the CLI supports two framework consumption modes:
62
+
63
+ **`npm` mode (default)** — `@lenne.tech/nest-server` is installed as an npm
64
+ dependency. Framework source lives in `node_modules/@lenne.tech/nest-server/`.
65
+ Imports use the bare specifier `from '@lenne.tech/nest-server'`. Update path:
66
+ `/lt-dev:backend:update-nest-server` (Claude Code agent).
67
+
68
+ **`vendor` mode** — The framework's `core/` directory is copied directly into
69
+ `<api>/src/core/` as first-class project code. No `@lenne.tech/nest-server`
70
+ npm dependency. Generated imports use relative paths (`from '../../../core'`).
71
+ Local patches are allowed and tracked in `src/core/VENDOR.md`. Update path:
72
+ `/lt-dev:backend:update-nest-server-core` (Claude Code agent).
73
+
74
+ ### Creating projects
75
+
76
+ ```bash
77
+ # npm mode (classic, default)
78
+ $ lt fullstack init --name myapp --frontend nuxt --api-mode Rest
79
+
80
+ # vendor mode, HEAD of upstream
81
+ $ lt fullstack init --name myapp --frontend nuxt --api-mode Rest \
82
+ --framework-mode vendor
83
+
84
+ # vendor mode, pinned to a specific upstream branch or tag
85
+ $ lt fullstack init --name myapp --framework-mode vendor \
86
+ --framework-upstream-branch 11.24.1
87
+
88
+ # dry-run: print the plan without touching the filesystem
89
+ $ lt fullstack init --name myapp --framework-mode vendor --dry-run --noConfirm
90
+
91
+ # standalone api project (vendor mode works here too)
92
+ $ lt server create --name myapp --framework-mode vendor
93
+ ```
94
+
95
+ ### Working on an existing project
96
+
97
+ All `lt server …` commands (module, object, addProp, test, permissions)
98
+ **auto-detect** the framework mode via `src/core/VENDOR.md` and generate
99
+ the correct import syntax automatically. You never pass `--framework-mode`
100
+ after `init`; it is persisted in the project's `lt.config.json`.
101
+
102
+ ```bash
103
+ # inside projects/api — generates relative or bare imports automatically
104
+ $ lt server module --name Product --controller Rest
105
+
106
+ # shows the mode + project type
107
+ $ lt status
108
+
109
+ # prints the mode-specific update instructions
110
+ $ lt fullstack update
111
+ ```
112
+
113
+ ### Vendor-mode housekeeping
114
+
115
+ Vendor-mode projects ship three maintenance scripts under `scripts/vendor/`:
116
+
117
+ | Script | Purpose | Invocation |
118
+ |---|---|---|
119
+ | `check-vendor-freshness.mjs` | Non-blocking warning when upstream has a newer release than the current baseline | `pnpm run check:vendor-freshness` (auto-invoked by `pnpm run check` / `check:fix` / `check:naf`) |
120
+ | `sync-from-upstream.ts` | Diff generator consumed by the `nest-server-core-updater` Claude Code agent | `pnpm run vendor:sync` |
121
+ | `propose-upstream-pr.ts` | Patch-list generator consumed by the `nest-server-core-contributor` agent | `pnpm run vendor:propose-upstream` |
122
+
123
+ The vendor-mode baseline (upstream version + commit SHA) is recorded in
124
+ `src/core/VENDOR.md`. Log any substantial local patch there so the updater
125
+ agent can classify it at sync time.
126
+
127
+ ### Integration test
128
+
129
+ A full end-to-end smoke test for all four supported init combinations
130
+ (`npm/Rest`, `vendor/Rest`, `vendor/GraphQL`, `vendor/Both`) ships with the
131
+ CLI:
132
+
133
+ ```bash
134
+ $ pnpm run test:vendor-init
135
+ ```
136
+
137
+ Each scenario runs init → module → object → addProp → test → tsc → build →
138
+ migrate:list and asserts ~30 structural + functional invariants per scenario.
139
+ Run this before releasing a new CLI version to catch upstream drift early.
140
+
58
141
  ## Configuration
59
142
 
60
143
  The CLI supports project-specific configuration via `lt.config` files. This allows you to set default values for commands, reducing repetitive input.
@@ -18,7 +18,7 @@ const NewCommand = {
18
18
  hidden: false,
19
19
  name: 'init',
20
20
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
21
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
21
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
22
22
  // Retrieve the tools we need
23
23
  const { config, filesystem, frontendHelper, git, helper, parameters, patching, print: { error, info, spin, success }, prompt: { ask, confirm }, server, strings: { kebabCase }, system, } = toolbox;
24
24
  // Start timer
@@ -26,7 +26,7 @@ const NewCommand = {
26
26
  // Info
27
27
  info('Create a new fullstack workspace');
28
28
  // Hint for non-interactive callers (e.g. Claude Code)
29
- toolbox.tools.nonInteractiveHint('lt fullstack init --name <name> --frontend <nuxt|angular> --api-mode <Rest|GraphQL|Both> --noConfirm');
29
+ toolbox.tools.nonInteractiveHint('lt fullstack init --name <name> --frontend <nuxt|angular> --api-mode <Rest|GraphQL|Both> --framework-mode <npm|vendor> [--framework-upstream-branch <ref>] [--dry-run] --noConfirm');
30
30
  // Check git
31
31
  if (!(yield git.gitInstalled())) {
32
32
  return;
@@ -43,12 +43,17 @@ const NewCommand = {
43
43
  const configFrontendCopy = (_r = (_q = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _q === void 0 ? void 0 : _q.fullstack) === null || _r === void 0 ? void 0 : _r.frontendCopy;
44
44
  const configApiLink = (_t = (_s = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _s === void 0 ? void 0 : _s.fullstack) === null || _t === void 0 ? void 0 : _t.apiLink;
45
45
  const configFrontendLink = (_v = (_u = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _u === void 0 ? void 0 : _u.fullstack) === null || _v === void 0 ? void 0 : _v.frontendLink;
46
+ const configFrameworkMode = (_x = (_w = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _w === void 0 ? void 0 : _w.fullstack) === null || _x === void 0 ? void 0 : _x.frameworkMode;
46
47
  // Parse CLI arguments
47
- const { 'api-branch': cliApiBranch, 'api-copy': cliApiCopy, 'api-link': cliApiLink, 'api-mode': cliApiMode, frontend: cliFrontend, 'frontend-branch': cliFrontendBranch, 'frontend-copy': cliFrontendCopy, 'frontend-link': cliFrontendLink, git: cliGit, 'git-link': cliGitLink, name: cliName, } = parameters.options;
48
+ const { 'api-branch': cliApiBranch, 'api-copy': cliApiCopy, 'api-link': cliApiLink, 'api-mode': cliApiMode, 'dry-run': cliDryRun, 'framework-mode': cliFrameworkMode, 'framework-upstream-branch': cliFrameworkUpstreamBranch, frontend: cliFrontend, 'frontend-branch': cliFrontendBranch, 'frontend-copy': cliFrontendCopy, 'frontend-link': cliFrontendLink, git: cliGit, 'git-link': cliGitLink, name: cliName, } = parameters.options;
49
+ const dryRun = cliDryRun === true || cliDryRun === 'true';
50
+ const frameworkUpstreamBranch = typeof cliFrameworkUpstreamBranch === 'string' && cliFrameworkUpstreamBranch.length > 0
51
+ ? cliFrameworkUpstreamBranch
52
+ : undefined;
48
53
  // Determine noConfirm with priority: CLI > command > parent > global > default
49
54
  const noConfirm = config.getNoConfirm({
50
55
  cliValue: parameters.options.noConfirm,
51
- commandConfig: (_w = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _w === void 0 ? void 0 : _w.fullstack,
56
+ commandConfig: (_y = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _y === void 0 ? void 0 : _y.fullstack,
52
57
  config: ltConfig,
53
58
  });
54
59
  // Get name of the workspace
@@ -132,6 +137,50 @@ const NewCommand = {
132
137
  });
133
138
  apiMode = apiModeChoice.apiMode.split(' - ')[0];
134
139
  }
140
+ // Determine framework-consumption mode (npm vs vendored)
141
+ //
142
+ // npm — classic: @lenne.tech/nest-server is an npm dependency. Framework
143
+ // source lives in node_modules/@lenne.tech/nest-server. Backend is
144
+ // cloned from nest-server-starter. Updates via
145
+ // `/lt-dev:backend:update-nest-server`.
146
+ //
147
+ // vendor — pilot: the framework's core/ directory is copied directly into
148
+ // projects/api/src/core/ as first-class project code. No npm
149
+ // dependency. Backend is cloned from the nest-server framework repo
150
+ // itself and stripped of framework-internal content. Updates via
151
+ // `/lt-dev:backend:update-nest-server-core`; local patches are logged
152
+ // in src/core/VENDOR.md.
153
+ //
154
+ // Default is still 'npm' until the vendoring pilot is fully evaluated.
155
+ let frameworkMode;
156
+ if (cliFrameworkMode === 'npm' || cliFrameworkMode === 'vendor') {
157
+ frameworkMode = cliFrameworkMode;
158
+ }
159
+ else if (cliFrameworkMode) {
160
+ error(`Invalid --framework-mode value "${cliFrameworkMode}". Use "npm" or "vendor".`);
161
+ return;
162
+ }
163
+ else if (configFrameworkMode === 'npm' || configFrameworkMode === 'vendor') {
164
+ frameworkMode = configFrameworkMode;
165
+ info(`Using framework mode from lt.config: ${frameworkMode}`);
166
+ }
167
+ else if (noConfirm) {
168
+ frameworkMode = 'npm';
169
+ info('Using default framework mode: npm (noConfirm mode)');
170
+ }
171
+ else {
172
+ const frameworkModeChoice = yield ask({
173
+ choices: [
174
+ 'npm - @lenne.tech/nest-server as npm dependency (classic, stable)',
175
+ 'vendor - framework core vendored into projects/api/src/core/ (pilot, allows local patches)',
176
+ ],
177
+ initial: 0,
178
+ message: 'Framework consumption mode?',
179
+ name: 'frameworkMode',
180
+ type: 'select',
181
+ });
182
+ frameworkMode = frameworkModeChoice.frameworkMode.startsWith('vendor') ? 'vendor' : 'npm';
183
+ }
135
184
  // Determine remote push settings with priority: CLI > config > interactive
136
185
  // Git is always initialized; the question is whether to push to a remote
137
186
  let pushToRemote = false;
@@ -190,6 +239,53 @@ const NewCommand = {
190
239
  const apiLink = cliApiLink || configApiLink;
191
240
  const frontendCopy = cliFrontendCopy || configFrontendCopy;
192
241
  const frontendLink = cliFrontendLink || configFrontendLink;
242
+ // Dry-run mode: print the resolved plan and exit without any disk
243
+ // changes. Useful for CI previews, for Claude Code confirmation
244
+ // steps, and for debugging the mode-detection logic without
245
+ // committing to a multi-minute init flow.
246
+ if (dryRun) {
247
+ info('');
248
+ info('Dry-run plan:');
249
+ info(` name: ${name}`);
250
+ info(` projectDir: ${projectDir}`);
251
+ info(` frontend: ${frontend}`);
252
+ info(` apiMode: ${apiMode}`);
253
+ info(` frameworkMode: ${frameworkMode}`);
254
+ if (frameworkUpstreamBranch) {
255
+ info(` frameworkUpstreamBranch: ${frameworkUpstreamBranch}`);
256
+ }
257
+ info(` apiBranch: ${apiBranch || '(default)'}`);
258
+ info(` frontendBranch: ${frontendBranch || '(default)'}`);
259
+ info(` apiCopy: ${apiCopy || '(none)'}`);
260
+ info(` apiLink: ${apiLink || '(none)'}`);
261
+ info(` frontendCopy: ${frontendCopy || '(none)'}`);
262
+ info(` frontendLink: ${frontendLink || '(none)'}`);
263
+ info(` pushToRemote: ${pushToRemote}`);
264
+ if (pushToRemote) {
265
+ info(` gitLink: ${gitLink || '(unset — would abort at run-time)'}`);
266
+ }
267
+ info('');
268
+ info('Would execute:');
269
+ info(` 1. git clone lt-monorepo → ${projectDir}/`);
270
+ info(` 2. setup frontend (${frontend}) → ${projectDir}/projects/app`);
271
+ if (frameworkMode === 'vendor') {
272
+ info(` 3. clone nest-server-starter → ${projectDir}/projects/api`);
273
+ info(` 4. clone @lenne.tech/nest-server${frameworkUpstreamBranch ? ` (branch/tag: ${frameworkUpstreamBranch})` : ''} → /tmp`);
274
+ info(` 5. vendor core/ + flatten-fix + codemod consumer imports`);
275
+ info(` 6. merge upstream deps (dynamic, no hard-coded list)`);
276
+ info(` 7. run processApiMode(${apiMode})`);
277
+ if (apiMode === 'Rest') {
278
+ info(` 8. restore vendored core essentials (graphql-*)`);
279
+ }
280
+ }
281
+ else {
282
+ info(` 3. clone nest-server-starter → ${projectDir}/projects/api`);
283
+ info(` 4. run processApiMode(${apiMode})`);
284
+ }
285
+ info(' N. pnpm install + initial git commit');
286
+ info('');
287
+ return `fullstack init dry-run (${frameworkMode} / ${apiMode})`;
288
+ }
193
289
  const workspaceSpinner = spin(`Create fullstack workspace with ${frontend} in ${projectDir} with ${name} app`);
194
290
  // Clone monorepo
195
291
  try {
@@ -217,6 +313,7 @@ const NewCommand = {
217
313
  .replace(/\{\{PROJECT_NAME\}\}/g, () => name)
218
314
  .replace(/\{\{PROJECT_DIR\}\}/g, () => projectDir)
219
315
  .replace(/\{\{API_MODE\}\}/g, () => apiMode)
316
+ .replace(/\{\{FRAMEWORK_MODE\}\}/g, () => frameworkMode)
220
317
  .replace(/\{\{FRONTEND_FRAMEWORK\}\}/g, () => frontendName));
221
318
  }
222
319
  // Always initialize git
@@ -280,6 +377,8 @@ const NewCommand = {
280
377
  apiMode,
281
378
  branch: apiBranch,
282
379
  copyPath: apiCopy,
380
+ frameworkMode,
381
+ frameworkUpstreamBranch,
283
382
  linkPath: apiLink,
284
383
  name,
285
384
  projectDir,
@@ -289,6 +388,10 @@ const NewCommand = {
289
388
  return;
290
389
  }
291
390
  // Create lt.config.json for API
391
+ // Note: frameworkMode is persisted under meta so that subsequent `lt
392
+ // server module` / `addProp` / `permissions` calls can detect the mode
393
+ // without re-probing src/core/VENDOR.md each time (the VENDOR.md check
394
+ // still works; this is just an explicit marker).
292
395
  const apiConfigPath = filesystem.path(apiDest, 'lt.config.json');
293
396
  filesystem.write(apiConfigPath, {
294
397
  commands: {
@@ -300,6 +403,7 @@ const NewCommand = {
300
403
  },
301
404
  meta: {
302
405
  apiMode,
406
+ frameworkMode,
303
407
  version: '1.0.0',
304
408
  },
305
409
  }, { jsonIndent: 2 });
@@ -0,0 +1,129 @@
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 path_1 = require("path");
13
+ const framework_detection_1 = require("../../lib/framework-detection");
14
+ /**
15
+ * Update a fullstack workspace — mode-aware.
16
+ *
17
+ * lenne.tech fullstack projects currently run in one of two framework
18
+ * consumption modes:
19
+ *
20
+ * - npm mode: `@lenne.tech/nest-server` is an npm dependency. Updates
21
+ * happen via `pnpm update @lenne.tech/nest-server` plus
22
+ * the migration guides, orchestrated by the
23
+ * `lt-dev:nest-server-updater` Claude Code agent.
24
+ *
25
+ * - vendor mode: The framework `core/` tree is vendored into
26
+ * `projects/api/src/core/`. Updates happen via the
27
+ * `lt-dev:nest-server-core-updater` Claude Code agent,
28
+ * which clones the upstream repo, computes a delta,
29
+ * applies the approved hunks, and re-runs the flatten-fix.
30
+ *
31
+ * Detection is based on the presence of `src/core/VENDOR.md` in the api
32
+ * project. This command prints the right instructions for the caller's
33
+ * project; actual update orchestration lives in the Claude Code agents,
34
+ * not in the CLI.
35
+ */
36
+ const NewCommand = {
37
+ alias: ['up', 'upd'],
38
+ description: 'Show the mode-specific update instructions for this fullstack workspace',
39
+ hidden: false,
40
+ name: 'update',
41
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
42
+ const { filesystem, print: { colors, info, success, warning }, } = toolbox;
43
+ info('');
44
+ info(colors.bold('Fullstack Update'));
45
+ info(colors.dim('─'.repeat(60)));
46
+ // Walk from cwd DOWN into projects/api if it exists, otherwise assume
47
+ // the caller is already inside an api project. Users can also point at
48
+ // a specific directory via `--api <path>`.
49
+ const cwd = filesystem.cwd();
50
+ const candidates = [
51
+ toolbox.parameters.options.api ? String(toolbox.parameters.options.api) : null,
52
+ (0, path_1.join)(cwd, 'projects', 'api'),
53
+ (0, path_1.join)(cwd, 'packages', 'api'),
54
+ cwd,
55
+ ].filter((p) => Boolean(p));
56
+ let apiDir;
57
+ for (const candidate of candidates) {
58
+ if (filesystem.exists((0, path_1.join)(candidate, 'package.json'))) {
59
+ apiDir = candidate;
60
+ break;
61
+ }
62
+ }
63
+ if (!apiDir) {
64
+ warning(' Could not locate an api project (no package.json found in cwd or projects/api/).');
65
+ info('');
66
+ info(' Pass --api <path> to point at the api project explicitly.');
67
+ info('');
68
+ return;
69
+ }
70
+ const mode = (0, framework_detection_1.detectFrameworkMode)(apiDir);
71
+ const vendored = (0, framework_detection_1.isVendoredProject)(apiDir);
72
+ info(` API project: ${apiDir}`);
73
+ info(` Framework mode: ${mode}${vendored ? ' (src/core/VENDOR.md present)' : ''}`);
74
+ info('');
75
+ if (mode === 'vendor') {
76
+ info(colors.bold('Vendor-mode update flow:'));
77
+ info('');
78
+ info(' The framework core/ tree lives directly in this project at');
79
+ info(' src/core/');
80
+ info(' and is managed as first-class project code. Local patches are');
81
+ info(' allowed and tracked in src/core/VENDOR.md.');
82
+ info('');
83
+ info(colors.bold(' Recommended update commands:'));
84
+ info('');
85
+ info(' 1. Refresh the upstream baseline + check for new versions');
86
+ info(` ${colors.cyan('(run from the api project)')}`);
87
+ info('');
88
+ info(' /lt-dev:backend:update-nest-server-core');
89
+ info('');
90
+ info(' 2. After the updater completes, run a freshness check:');
91
+ info('');
92
+ info(' pnpm run check:vendor-freshness');
93
+ info('');
94
+ info(' 3. If local changes have become generally useful, propose');
95
+ info(' them as upstream PRs via:');
96
+ info('');
97
+ info(' /lt-dev:backend:contribute-nest-server-core');
98
+ info('');
99
+ success(' All of these operate on src/core/ in-place; no npm dep bump.');
100
+ }
101
+ else {
102
+ info(colors.bold('npm-mode update flow:'));
103
+ info('');
104
+ info(' The framework lives in node_modules/@lenne.tech/nest-server as');
105
+ info(' a pinned npm dependency.');
106
+ info('');
107
+ info(colors.bold(' Recommended update commands:'));
108
+ info('');
109
+ info(' 1. Run the nest-server-updater agent:');
110
+ info('');
111
+ info(' /lt-dev:backend:update-nest-server');
112
+ info('');
113
+ info(' (or manually: pnpm update @lenne.tech/nest-server');
114
+ info(' and walk the migration guides)');
115
+ info('');
116
+ info(' 2. After upgrade, run the full check suite:');
117
+ info('');
118
+ info(' pnpm run check');
119
+ info('');
120
+ success(' The nest-server-updater agent auto-detects vendor projects');
121
+ success(' and delegates to nest-server-core-updater when VENDOR.md is present.');
122
+ }
123
+ info('');
124
+ info(colors.dim('─'.repeat(60)));
125
+ info('');
126
+ return `fullstack update (${mode} mode)`;
127
+ }),
128
+ };
129
+ exports.default = NewCommand;
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const path_1 = require("path");
16
16
  const ts_morph_1 = require("ts-morph");
17
+ const framework_detection_1 = require("../../lib/framework-detection");
17
18
  const module_1 = __importDefault(require("./module"));
18
19
  const object_1 = __importDefault(require("./object"));
19
20
  /**
@@ -427,8 +428,34 @@ const NewCommand = {
427
428
  const mappingPairs = Object.entries(mappings).map(([k, v]) => `${k}: ${v}`);
428
429
  returnStatement.replaceWithText(`return mapClasses(input, { ${mappingPairs.join(', ')} }, this);`);
429
430
  }
430
- // Ensure mapClasses is imported
431
- const existingImports = moduleFile.getImportDeclaration('@lenne.tech/nest-server');
431
+ // Ensure mapClasses is imported. The import specifier differs by
432
+ // framework-consumption mode — in npm projects it's
433
+ // '@lenne.tech/nest-server'; in vendored projects it's a relative
434
+ // path to src/core whose depth depends on the model file location.
435
+ // We search for BOTH forms so this works regardless of how the file
436
+ // was originally generated.
437
+ const vendoredSpec = (0, framework_detection_1.isVendoredProject)(path)
438
+ ? (0, framework_detection_1.getFrameworkImportSpecifier)(path, modelPath)
439
+ : null;
440
+ let existingImports = moduleFile.getImportDeclaration('@lenne.tech/nest-server');
441
+ if (!existingImports && vendoredSpec) {
442
+ existingImports = moduleFile.getImportDeclaration(vendoredSpec);
443
+ }
444
+ if (!existingImports) {
445
+ // Final fallback: scan all imports and match any that resolves to
446
+ // the framework path (covers hand-edited files or unusual depths).
447
+ const allImports = moduleFile.getImportDeclarations();
448
+ existingImports = allImports.find((imp) => {
449
+ const spec = imp.getModuleSpecifierValue();
450
+ if (spec === '@lenne.tech/nest-server')
451
+ return true;
452
+ if (!vendoredSpec)
453
+ return false;
454
+ // Accept any relative path ending with '/core' or '../core' —
455
+ // covers variations across file depths.
456
+ return /(^|\/)core(\/.*)?$/.test(spec) && spec.startsWith('.');
457
+ });
458
+ }
432
459
  if (existingImports) {
433
460
  const namedImports = existingImports.getNamedImports();
434
461
  const hasMapClasses = namedImports.some((ni) => ni.getName() === 'mapClasses');
@@ -18,7 +18,7 @@ const NewCommand = {
18
18
  hidden: false,
19
19
  name: 'create',
20
20
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
21
- 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;
21
+ 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, _1, _2;
22
22
  // Retrieve the tools we need
23
23
  const { config, filesystem, git, helper, meta, parameters, print: { error, info, spin, success }, prompt: { ask, confirm }, server, strings: { kebabCase }, system, } = toolbox;
24
24
  // Handle --help-json flag
@@ -62,6 +62,7 @@ const NewCommand = {
62
62
  const configCopy = (_q = (_p = (_o = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _o === void 0 ? void 0 : _o.server) === null || _p === void 0 ? void 0 : _p.create) === null || _q === void 0 ? void 0 : _q.copy;
63
63
  const configLink = (_t = (_s = (_r = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _r === void 0 ? void 0 : _r.server) === null || _s === void 0 ? void 0 : _s.create) === null || _t === void 0 ? void 0 : _t.link;
64
64
  const configApiMode = (_w = (_v = (_u = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _u === void 0 ? void 0 : _u.server) === null || _v === void 0 ? void 0 : _v.create) === null || _w === void 0 ? void 0 : _w.apiMode;
65
+ const configFrameworkMode = (_z = (_y = (_x = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _x === void 0 ? void 0 : _x.server) === null || _y === void 0 ? void 0 : _y.create) === null || _z === void 0 ? void 0 : _z.frameworkMode;
65
66
  // Load global defaults
66
67
  const globalAuthor = config.getGlobalDefault(ltConfig, 'author');
67
68
  const globalApiMode = config.getGlobalDefault(ltConfig, 'apiMode');
@@ -74,10 +75,12 @@ const NewCommand = {
74
75
  const cliCopy = parameters.options.copy || parameters.options.c;
75
76
  const cliLink = parameters.options.link;
76
77
  const cliApiMode = parameters.options['api-mode'] || parameters.options.apiMode;
78
+ const cliFrameworkMode = parameters.options['framework-mode'];
79
+ const cliFrameworkUpstreamBranch = parameters.options['framework-upstream-branch'];
77
80
  // Determine noConfirm with priority: CLI > config > global > default (false)
78
81
  const noConfirm = config.getNoConfirm({
79
82
  cliValue: cliNoConfirm,
80
- commandConfig: (_y = (_x = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _x === void 0 ? void 0 : _x.server) === null || _y === void 0 ? void 0 : _y.create,
83
+ commandConfig: (_1 = (_0 = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _0 === void 0 ? void 0 : _0.server) === null || _1 === void 0 ? void 0 : _1.create,
81
84
  config: ltConfig,
82
85
  });
83
86
  // Start timer
@@ -178,6 +181,39 @@ const NewCommand = {
178
181
  ]);
179
182
  apiMode = apiModeChoice.apiMode.split(' - ')[0];
180
183
  }
184
+ // Determine framework consumption mode — same resolution cascade as
185
+ // lt fullstack init: CLI flag > lt.config > interactive (default npm).
186
+ let frameworkMode;
187
+ if (cliFrameworkMode === 'npm' || cliFrameworkMode === 'vendor') {
188
+ frameworkMode = cliFrameworkMode;
189
+ }
190
+ else if (cliFrameworkMode) {
191
+ error(`Invalid --framework-mode value "${cliFrameworkMode}". Use "npm" or "vendor".`);
192
+ return;
193
+ }
194
+ else if (configFrameworkMode === 'npm' || configFrameworkMode === 'vendor') {
195
+ frameworkMode = configFrameworkMode;
196
+ info(`Using framework mode from lt.config: ${frameworkMode}`);
197
+ }
198
+ else if (noConfirm) {
199
+ frameworkMode = 'npm';
200
+ }
201
+ else {
202
+ const frameworkModeChoice = yield ask({
203
+ choices: [
204
+ 'npm - @lenne.tech/nest-server as npm dependency (classic, stable)',
205
+ 'vendor - framework core vendored into src/core/ (pilot, allows local patches)',
206
+ ],
207
+ initial: 0,
208
+ message: 'Framework consumption mode?',
209
+ name: 'frameworkMode',
210
+ type: 'select',
211
+ });
212
+ frameworkMode = frameworkModeChoice.frameworkMode.startsWith('vendor') ? 'vendor' : 'npm';
213
+ }
214
+ const frameworkUpstreamBranch = typeof cliFrameworkUpstreamBranch === 'string' && cliFrameworkUpstreamBranch.length > 0
215
+ ? cliFrameworkUpstreamBranch
216
+ : undefined;
181
217
  // Setup server using Server extension
182
218
  const setupSpinner = spin(`Setting up server${linkPath ? ' (link)' : copyPath ? ' (copy)' : branch ? ` (branch: ${branch})` : ''}`);
183
219
  const result = yield server.setupServer(`./${projectDir}`, {
@@ -186,6 +222,8 @@ const NewCommand = {
186
222
  branch,
187
223
  copyPath,
188
224
  description,
225
+ frameworkMode,
226
+ frameworkUpstreamBranch,
189
227
  linkPath,
190
228
  name,
191
229
  projectDir,
@@ -213,7 +251,7 @@ const NewCommand = {
213
251
  }
214
252
  // Git initialization (after npm install which is done in setupServer)
215
253
  if (git) {
216
- const inGit = (_z = (yield system.run('git rev-parse --is-inside-work-tree'))) === null || _z === void 0 ? void 0 : _z.trim();
254
+ const inGit = (_2 = (yield system.run('git rev-parse --is-inside-work-tree'))) === null || _2 === void 0 ? void 0 : _2.trim();
217
255
  if (inGit !== 'true') {
218
256
  // Determine initGit with priority: CLI > config > interactive
219
257
  let initializeGit;