@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.
- package/README.md +83 -0
- package/build/commands/fullstack/init.js +108 -4
- package/build/commands/fullstack/update.js +129 -0
- package/build/commands/server/add-property.js +29 -2
- package/build/commands/server/create.js +41 -3
- package/build/commands/server/module.js +58 -25
- package/build/commands/server/object.js +26 -5
- package/build/commands/server/permissions.js +20 -6
- package/build/commands/server/test.js +7 -1
- package/build/commands/status.js +13 -1
- package/build/config/vendor-runtime-deps.json +9 -0
- package/build/extensions/api-mode.js +19 -3
- package/build/extensions/server.js +1028 -3
- package/build/lib/framework-detection.js +167 -0
- package/build/templates/nest-server-module/inputs/template-create.input.ts.ejs +1 -1
- package/build/templates/nest-server-module/inputs/template.input.ts.ejs +1 -1
- package/build/templates/nest-server-module/outputs/template-fac-result.output.ts.ejs +1 -1
- package/build/templates/nest-server-module/template.controller.ts.ejs +1 -1
- package/build/templates/nest-server-module/template.model.ts.ejs +1 -1
- package/build/templates/nest-server-module/template.module.ts.ejs +1 -1
- package/build/templates/nest-server-module/template.resolver.ts.ejs +1 -1
- package/build/templates/nest-server-module/template.service.ts.ejs +1 -1
- package/build/templates/nest-server-object/template-create.input.ts.ejs +1 -1
- package/build/templates/nest-server-object/template.input.ts.ejs +1 -1
- package/build/templates/nest-server-object/template.object.ts.ejs +1 -1
- package/build/templates/nest-server-tests/tests.e2e-spec.ts.ejs +1 -1
- package/build/templates/vendor-scripts/check-vendor-freshness.mjs +131 -0
- package/build/templates/vendor-scripts/propose-upstream-pr.ts +269 -0
- package/build/templates/vendor-scripts/sync-from-upstream.ts +250 -0
- 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: (
|
|
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
|
-
|
|
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: (
|
|
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 = (
|
|
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;
|