@lenne.tech/cli 1.10.0 → 1.11.1

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 CHANGED
@@ -38,9 +38,11 @@ $ lt
38
38
 
39
39
  ## Documentation
40
40
 
41
- - [Command Reference](docs/commands.md) - Complete list of all commands with options
42
- - [Configuration Guide](docs/lt.config.md) - Configuration file documentation
43
- - [Plugin Guide](docs/plugins.md) - How to create plugins
41
+ - **[LT-ECOSYSTEM-GUIDE](docs/LT-ECOSYSTEM-GUIDE.md)** Complete reference for `lt` CLI **and** the `lt-dev` Claude-Code Plugin (architecture, functions, vendor-mode workflows, agents, skills)
42
+ - **[VENDOR-MODE-WORKFLOW](docs/VENDOR-MODE-WORKFLOW.md)** — Step-by-step guide for npm → vendor conversion, updates, and rollback
43
+ - [Command Reference](docs/commands.md) Complete list of all commands with options
44
+ - [Configuration Guide](docs/lt.config.md) — Configuration file documentation
45
+ - [Plugin Guide](docs/plugins.md) — How to create plugins
44
46
 
45
47
  ## Quick Start
46
48
 
@@ -56,9 +56,11 @@ const KNOWN_KEYS = {
56
56
  apiCopy: 'string',
57
57
  apiLink: 'string',
58
58
  apiMode: ['Rest', 'GraphQL', 'Both'],
59
+ frameworkMode: ['npm', 'vendor'],
59
60
  frontend: ['angular', 'nuxt'],
60
61
  frontendBranch: 'string',
61
62
  frontendCopy: 'string',
63
+ frontendFrameworkMode: ['npm', 'vendor'],
62
64
  frontendLink: 'string',
63
65
  git: 'boolean',
64
66
  gitLink: 'string',
@@ -0,0 +1,198 @@
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 frontend_framework_detection_1 = require("../../lib/frontend-framework-detection");
13
+ /**
14
+ * Convert an existing frontend project between npm mode and vendor mode.
15
+ *
16
+ * Usage:
17
+ * lt frontend convert-mode --to vendor [--upstream-branch 1.5.3]
18
+ * lt frontend convert-mode --to npm [--version 1.5.3]
19
+ */
20
+ const ConvertModeCommand = {
21
+ description: 'Convert app framework mode',
22
+ hidden: false,
23
+ name: 'convert-mode',
24
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
25
+ const { filesystem, frontendHelper, parameters, print: { error, info, spin, success, warning }, prompt: { confirm }, } = toolbox;
26
+ // Handle --help-json flag
27
+ if (toolbox.tools.helpJson({
28
+ description: 'Convert frontend project between npm and vendor framework modes',
29
+ name: 'convert-mode',
30
+ options: [
31
+ {
32
+ description: 'Target mode',
33
+ flag: '--to',
34
+ required: true,
35
+ type: 'string',
36
+ values: ['vendor', 'npm'],
37
+ },
38
+ {
39
+ description: 'Upstream branch/tag to vendor from (only with --to vendor)',
40
+ flag: '--upstream-branch',
41
+ required: false,
42
+ type: 'string',
43
+ },
44
+ {
45
+ description: 'nuxt-extensions version to install (only with --to npm, default: from VENDOR.md baseline)',
46
+ flag: '--version',
47
+ required: false,
48
+ type: 'string',
49
+ },
50
+ {
51
+ default: false,
52
+ description: 'Skip confirmation prompt',
53
+ flag: '--noConfirm',
54
+ required: false,
55
+ type: 'boolean',
56
+ },
57
+ {
58
+ default: false,
59
+ description: 'Show the resolved plan without making any changes',
60
+ flag: '--dry-run',
61
+ required: false,
62
+ type: 'boolean',
63
+ },
64
+ ],
65
+ })) {
66
+ return;
67
+ }
68
+ const targetMode = parameters.options.to;
69
+ if (!targetMode || !['npm', 'vendor'].includes(targetMode)) {
70
+ error('Missing or invalid --to flag. Use: --to vendor or --to npm');
71
+ return;
72
+ }
73
+ // Find the frontend project root
74
+ const cwd = filesystem.cwd();
75
+ const appDir = (0, frontend_framework_detection_1.findAppDir)(cwd);
76
+ if (!appDir) {
77
+ error('Could not find a nuxt.config.ts in the current directory or any parent. Are you inside a Nuxt project?');
78
+ return;
79
+ }
80
+ // Detect current mode
81
+ const currentMode = (0, frontend_framework_detection_1.detectFrontendFrameworkMode)(appDir);
82
+ info(`Detected current mode: ${currentMode}`);
83
+ if (currentMode === targetMode) {
84
+ warning(`Project is already in ${targetMode} mode. Nothing to do.`);
85
+ return;
86
+ }
87
+ // Dry-run: print the resolved plan and exit without any disk changes.
88
+ const dryRun = parameters.options['dry-run'] === true || parameters.options['dry-run'] === 'true';
89
+ if (dryRun) {
90
+ info('');
91
+ info('Dry-run plan:');
92
+ info(` appDir: ${appDir}`);
93
+ info(` currentMode: ${currentMode}`);
94
+ info(` targetMode: ${targetMode}`);
95
+ if (targetMode === 'vendor') {
96
+ const upstreamBranch = parameters.options['upstream-branch'];
97
+ info(` upstreamBranch: ${upstreamBranch || '(auto-detect from package.json)'}`);
98
+ info('');
99
+ info('Would execute:');
100
+ info(' 1. Clone @lenne.tech/nuxt-extensions → /tmp/lt-vendor-nuxt-ext-*');
101
+ info(' 2. Copy src/module.ts + src/runtime/ → app/core/');
102
+ info(' 3. Rewrite nuxt.config.ts: @lenne.tech/nuxt-extensions → ./app/core/module');
103
+ info(' 4. Codemod consumer imports (app/**/*.{ts,vue}, tests/**/*.ts)');
104
+ info(' 5. Remove @lenne.tech/nuxt-extensions from package.json');
105
+ info(' 6. Add check:vendor-freshness script');
106
+ info(' 7. Create app/core/VENDOR.md with baseline metadata');
107
+ info(' 8. Prepend vendor-mode notice to CLAUDE.md');
108
+ }
109
+ else {
110
+ const targetVersion = parameters.options.version;
111
+ info(` targetVersion: ${targetVersion || '(from VENDOR.md baseline)'}`);
112
+ info('');
113
+ info('Would execute:');
114
+ info(' 1. Read baseline version from app/core/VENDOR.md');
115
+ info(' 2. Warn if local patches exist in VENDOR.md');
116
+ info(' 3. Rewrite consumer imports: relative → @lenne.tech/nuxt-extensions');
117
+ info(' 4. Delete app/core/');
118
+ info(' 5. Restore @lenne.tech/nuxt-extensions in package.json');
119
+ info(' 6. Rewrite nuxt.config.ts: ./app/core/module → @lenne.tech/nuxt-extensions');
120
+ info(' 7. Remove vendor scripts and CLAUDE.md marker');
121
+ }
122
+ info('');
123
+ return `frontend convert-mode dry-run (${currentMode} → ${targetMode})`;
124
+ }
125
+ // Confirm
126
+ const noConfirm = parameters.options.noConfirm;
127
+ if (!noConfirm) {
128
+ const proceed = yield confirm(`Convert project from ${currentMode} mode to ${targetMode} mode?\n` +
129
+ ` Project: ${appDir}\n` +
130
+ ` This will modify nuxt.config.ts, package.json, imports, and CLAUDE.md.`);
131
+ if (!proceed) {
132
+ info('Aborted.');
133
+ return;
134
+ }
135
+ }
136
+ // Execute conversion
137
+ if (targetMode === 'vendor') {
138
+ // npm → vendor
139
+ const upstreamBranch = parameters.options['upstream-branch'];
140
+ // Auto-detect version from current @lenne.tech/nuxt-extensions dep
141
+ let branch = upstreamBranch;
142
+ if (!branch) {
143
+ try {
144
+ const pkg = filesystem.read(`${appDir}/package.json`, 'json');
145
+ const deps = Object.assign(Object.assign({}, ((pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) || {})), ((pkg === null || pkg === void 0 ? void 0 : pkg.devDependencies) || {}));
146
+ const version = deps['@lenne.tech/nuxt-extensions'];
147
+ if (version) {
148
+ // Strip semver range chars (^, ~, >=, etc.) to get the bare version
149
+ branch = version.replace(/^[^0-9]*/, '');
150
+ info(`Auto-detected @lenne.tech/nuxt-extensions version: ${branch}`);
151
+ }
152
+ }
153
+ catch (_a) {
154
+ // Will use HEAD
155
+ }
156
+ }
157
+ const spinner = spin('Converting to vendor mode...');
158
+ try {
159
+ yield frontendHelper.convertAppToVendorMode({
160
+ dest: appDir,
161
+ upstreamBranch: branch,
162
+ });
163
+ spinner.succeed('Converted to vendor mode successfully.');
164
+ success('\nNext steps:');
165
+ info(' 1. Run: pnpm install');
166
+ info(' 2. Run: pnpm run build (or nuxt build)');
167
+ info(' 3. Commit the changes');
168
+ }
169
+ catch (err) {
170
+ spinner.fail(`Conversion failed: ${err.message}`);
171
+ }
172
+ }
173
+ else {
174
+ // vendor → npm
175
+ const targetVersion = parameters.options.version;
176
+ const spinner = spin('Converting to npm mode...');
177
+ try {
178
+ yield frontendHelper.convertAppToNpmMode({
179
+ dest: appDir,
180
+ targetVersion,
181
+ });
182
+ spinner.succeed('Converted to npm mode successfully.');
183
+ success('\nNext steps:');
184
+ info(' 1. Run: pnpm install');
185
+ info(' 2. Run: pnpm run build (or nuxt build)');
186
+ info(' 3. Commit the changes');
187
+ }
188
+ catch (err) {
189
+ spinner.fail(`Conversion failed: ${err.message}`);
190
+ }
191
+ }
192
+ if (!parameters.options.fromGluegunMenu) {
193
+ process.exit();
194
+ }
195
+ return `converted frontend to ${targetMode} mode`;
196
+ }),
197
+ };
198
+ exports.default = ConvertModeCommand;
@@ -0,0 +1,368 @@
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
+ const frontend_framework_detection_1 = require("../../lib/frontend-framework-detection");
15
+ /**
16
+ * Convert both backend and frontend of a fullstack monorepo between
17
+ * npm mode and vendor mode in a single command.
18
+ *
19
+ * Usage:
20
+ * lt fullstack convert-mode --to vendor [--framework-upstream-branch 11.24.3] [--frontend-framework-upstream-branch 1.5.3]
21
+ * lt fullstack convert-mode --to npm
22
+ * lt fullstack convert-mode --to vendor --skip-frontend
23
+ * lt fullstack convert-mode --to vendor --skip-backend
24
+ * lt fullstack convert-mode --to vendor --dry-run
25
+ *
26
+ * Orchestrates `lt server convert-mode` + `lt frontend convert-mode` with
27
+ * auto-detection of `projects/api/` and `projects/app/` subdirectories.
28
+ */
29
+ const ConvertModeCommand = {
30
+ description: 'Convert fullstack monorepo between npm and vendor modes',
31
+ hidden: false,
32
+ name: 'convert-mode',
33
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
34
+ const { filesystem, frontendHelper, parameters, print: { colors, error, info, spin, success, warning }, prompt: { confirm }, server, } = toolbox;
35
+ // Handle --help-json flag
36
+ if (toolbox.tools.helpJson({
37
+ description: 'Convert fullstack monorepo (backend + frontend) between npm and vendor modes',
38
+ name: 'convert-mode',
39
+ options: [
40
+ {
41
+ description: 'Target mode',
42
+ flag: '--to',
43
+ required: true,
44
+ type: 'string',
45
+ values: ['vendor', 'npm'],
46
+ },
47
+ {
48
+ description: 'Backend upstream branch/tag (only with --to vendor, e.g. "11.24.3")',
49
+ flag: '--framework-upstream-branch',
50
+ required: false,
51
+ type: 'string',
52
+ },
53
+ {
54
+ description: 'Frontend upstream branch/tag (only with --to vendor, e.g. "1.5.3")',
55
+ flag: '--frontend-framework-upstream-branch',
56
+ required: false,
57
+ type: 'string',
58
+ },
59
+ {
60
+ description: 'Backend version to install (only with --to npm, default: from VENDOR.md baseline)',
61
+ flag: '--framework-version',
62
+ required: false,
63
+ type: 'string',
64
+ },
65
+ {
66
+ description: 'Frontend version to install (only with --to npm, default: from VENDOR.md baseline)',
67
+ flag: '--frontend-framework-version',
68
+ required: false,
69
+ type: 'string',
70
+ },
71
+ {
72
+ default: false,
73
+ description: 'Skip backend conversion',
74
+ flag: '--skip-backend',
75
+ required: false,
76
+ type: 'boolean',
77
+ },
78
+ {
79
+ default: false,
80
+ description: 'Skip frontend conversion',
81
+ flag: '--skip-frontend',
82
+ required: false,
83
+ type: 'boolean',
84
+ },
85
+ {
86
+ default: false,
87
+ description: 'Show the resolved plan without making any changes',
88
+ flag: '--dry-run',
89
+ required: false,
90
+ type: 'boolean',
91
+ },
92
+ {
93
+ default: false,
94
+ description: 'Skip confirmation prompt',
95
+ flag: '--noConfirm',
96
+ required: false,
97
+ type: 'boolean',
98
+ },
99
+ ],
100
+ })) {
101
+ return;
102
+ }
103
+ const targetMode = parameters.options.to;
104
+ if (!targetMode || !['npm', 'vendor'].includes(targetMode)) {
105
+ error('Missing or invalid --to flag. Use: --to vendor or --to npm');
106
+ return;
107
+ }
108
+ const skipBackend = parameters.options['skip-backend'] === true || parameters.options['skip-backend'] === 'true';
109
+ const skipFrontend = parameters.options['skip-frontend'] === true || parameters.options['skip-frontend'] === 'true';
110
+ const dryRun = parameters.options['dry-run'] === true || parameters.options['dry-run'] === 'true';
111
+ const noConfirm = parameters.options.noConfirm === true || parameters.options.noConfirm === 'true';
112
+ if (skipBackend && skipFrontend) {
113
+ error('Cannot skip both backend and frontend — nothing would happen.');
114
+ return;
115
+ }
116
+ // Locate the subprojects. Typical monorepo layouts: projects/{api,app} or packages/{api,app}.
117
+ const cwd = filesystem.cwd();
118
+ const backendCandidates = [(0, path_1.join)(cwd, 'projects', 'api'), (0, path_1.join)(cwd, 'packages', 'api')];
119
+ const frontendCandidates = [(0, path_1.join)(cwd, 'projects', 'app'), (0, path_1.join)(cwd, 'packages', 'app')];
120
+ let backendDir;
121
+ for (const candidate of backendCandidates) {
122
+ if (filesystem.exists((0, path_1.join)(candidate, 'package.json'))) {
123
+ backendDir = candidate;
124
+ break;
125
+ }
126
+ }
127
+ let frontendDir;
128
+ for (const candidate of frontendCandidates) {
129
+ if (filesystem.exists((0, path_1.join)(candidate, 'package.json'))) {
130
+ frontendDir = candidate;
131
+ break;
132
+ }
133
+ }
134
+ if (!backendDir && !frontendDir) {
135
+ error('Could not find any api or app subproject. Expected projects/api, projects/app, packages/api, or packages/app.');
136
+ return;
137
+ }
138
+ // Detect current modes
139
+ const backendCurrentMode = backendDir ? (0, framework_detection_1.detectFrameworkMode)(backendDir) : null;
140
+ const frontendCurrentMode = frontendDir ? (0, frontend_framework_detection_1.detectFrontendFrameworkMode)(frontendDir) : null;
141
+ // Decide what will actually happen
142
+ const willConvertBackend = !skipBackend && backendDir && backendCurrentMode && backendCurrentMode !== targetMode;
143
+ const willConvertFrontend = !skipFrontend && frontendDir && frontendCurrentMode && frontendCurrentMode !== targetMode;
144
+ // ── Plan output ─────────────────────────────────────────────────────
145
+ info('');
146
+ info(colors.bold('Fullstack convert-mode plan:'));
147
+ info(colors.dim('─'.repeat(60)));
148
+ if (backendDir) {
149
+ const backendLabel = skipBackend
150
+ ? colors.yellow('(skipped via --skip-backend)')
151
+ : backendCurrentMode === targetMode
152
+ ? colors.dim(`(already in ${targetMode} mode — nothing to do)`)
153
+ : colors.green(`${backendCurrentMode} → ${targetMode}`);
154
+ info(` Backend: ${backendDir}`);
155
+ info(` ${backendLabel}`);
156
+ }
157
+ else {
158
+ info(` Backend: ${colors.dim('(not found)')}`);
159
+ }
160
+ if (frontendDir) {
161
+ const frontendLabel = skipFrontend
162
+ ? colors.yellow('(skipped via --skip-frontend)')
163
+ : frontendCurrentMode === targetMode
164
+ ? colors.dim(`(already in ${targetMode} mode — nothing to do)`)
165
+ : colors.green(`${frontendCurrentMode} → ${targetMode}`);
166
+ info(` Frontend: ${frontendDir}`);
167
+ info(` ${frontendLabel}`);
168
+ }
169
+ else {
170
+ info(` Frontend: ${colors.dim('(not found)')}`);
171
+ }
172
+ info(colors.dim('─'.repeat(60)));
173
+ if (!willConvertBackend && !willConvertFrontend) {
174
+ info('');
175
+ warning('Nothing to do: both subprojects are already in the target mode (or skipped).');
176
+ return;
177
+ }
178
+ if (dryRun) {
179
+ info('');
180
+ info(colors.bold('Would execute:'));
181
+ if (willConvertBackend) {
182
+ if (targetMode === 'vendor') {
183
+ const branch = parameters.options['framework-upstream-branch'] || '(auto-detect from package.json)';
184
+ info(` [Backend] lt server convert-mode --to vendor --upstream-branch ${branch} --noConfirm`);
185
+ }
186
+ else {
187
+ const version = parameters.options['framework-version'] || '(from VENDOR.md baseline)';
188
+ info(` [Backend] lt server convert-mode --to npm --version ${version} --noConfirm`);
189
+ }
190
+ }
191
+ if (willConvertFrontend) {
192
+ if (targetMode === 'vendor') {
193
+ const branch = parameters.options['frontend-framework-upstream-branch'] || '(auto-detect from package.json)';
194
+ info(` [Frontend] lt frontend convert-mode --to vendor --upstream-branch ${branch} --noConfirm`);
195
+ }
196
+ else {
197
+ const version = parameters.options['frontend-framework-version'] || '(from VENDOR.md baseline)';
198
+ info(` [Frontend] lt frontend convert-mode --to npm --version ${version} --noConfirm`);
199
+ }
200
+ }
201
+ info('');
202
+ return `fullstack convert-mode dry-run (target: ${targetMode})`;
203
+ }
204
+ // ── Confirmation ────────────────────────────────────────────────────
205
+ if (!noConfirm) {
206
+ info('');
207
+ const proceed = yield confirm(`Convert ${[willConvertBackend && 'backend', willConvertFrontend && 'frontend'].filter(Boolean).join(' + ')} to ${targetMode} mode?`);
208
+ if (!proceed) {
209
+ info('Aborted.');
210
+ return;
211
+ }
212
+ }
213
+ // ── Execute ─────────────────────────────────────────────────────────
214
+ const results = [];
215
+ // 1. Backend conversion
216
+ if (willConvertBackend && backendDir) {
217
+ info('');
218
+ info(colors.bold(`[1/2] Backend: ${backendCurrentMode} → ${targetMode}`));
219
+ if (targetMode === 'vendor') {
220
+ let branch = parameters.options['framework-upstream-branch'];
221
+ // Auto-detect version from package.json if not provided
222
+ if (!branch) {
223
+ try {
224
+ const pkg = filesystem.read(`${backendDir}/package.json`, 'json');
225
+ const deps = Object.assign(Object.assign({}, ((pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) || {})), ((pkg === null || pkg === void 0 ? void 0 : pkg.devDependencies) || {}));
226
+ const version = deps['@lenne.tech/nest-server'];
227
+ if (version) {
228
+ branch = version.replace(/^[^0-9]*/, '');
229
+ info(` Auto-detected @lenne.tech/nest-server version: ${branch}`);
230
+ }
231
+ }
232
+ catch (_a) {
233
+ // Will use HEAD
234
+ }
235
+ }
236
+ const spinner = spin(' Converting backend to vendor mode...');
237
+ try {
238
+ yield server.convertToVendorMode({
239
+ dest: backendDir,
240
+ upstreamBranch: branch,
241
+ });
242
+ spinner.succeed(' Backend converted to vendor mode');
243
+ results.push({ part: 'backend', status: 'ok' });
244
+ }
245
+ catch (err) {
246
+ spinner.fail(` Backend conversion failed: ${err.message}`);
247
+ results.push({ message: err.message, part: 'backend', status: 'failed' });
248
+ }
249
+ }
250
+ else {
251
+ const targetVersion = parameters.options['framework-version'];
252
+ const spinner = spin(' Converting backend to npm mode...');
253
+ try {
254
+ yield server.convertToNpmMode({
255
+ dest: backendDir,
256
+ targetVersion,
257
+ });
258
+ spinner.succeed(' Backend converted to npm mode');
259
+ results.push({ part: 'backend', status: 'ok' });
260
+ }
261
+ catch (err) {
262
+ spinner.fail(` Backend conversion failed: ${err.message}`);
263
+ results.push({ message: err.message, part: 'backend', status: 'failed' });
264
+ }
265
+ }
266
+ }
267
+ else if (backendDir) {
268
+ const reason = skipBackend ? 'skipped via flag' : `already in ${targetMode} mode`;
269
+ info('');
270
+ info(colors.dim(`[1/2] Backend: ${reason}`));
271
+ results.push({ message: reason, part: 'backend', status: 'skipped' });
272
+ }
273
+ // 2. Frontend conversion
274
+ if (willConvertFrontend && frontendDir) {
275
+ info('');
276
+ info(colors.bold(`[2/2] Frontend: ${frontendCurrentMode} → ${targetMode}`));
277
+ if (targetMode === 'vendor') {
278
+ let branch = parameters.options['frontend-framework-upstream-branch'];
279
+ if (!branch) {
280
+ try {
281
+ const pkg = filesystem.read(`${frontendDir}/package.json`, 'json');
282
+ const deps = Object.assign(Object.assign({}, ((pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) || {})), ((pkg === null || pkg === void 0 ? void 0 : pkg.devDependencies) || {}));
283
+ const version = deps['@lenne.tech/nuxt-extensions'];
284
+ if (version) {
285
+ branch = version.replace(/^[^0-9]*/, '');
286
+ info(` Auto-detected @lenne.tech/nuxt-extensions version: ${branch}`);
287
+ }
288
+ }
289
+ catch (_b) {
290
+ // Will use HEAD
291
+ }
292
+ }
293
+ const spinner = spin(' Converting frontend to vendor mode...');
294
+ try {
295
+ yield frontendHelper.convertAppToVendorMode({
296
+ dest: frontendDir,
297
+ upstreamBranch: branch,
298
+ });
299
+ spinner.succeed(' Frontend converted to vendor mode');
300
+ results.push({ part: 'frontend', status: 'ok' });
301
+ }
302
+ catch (err) {
303
+ spinner.fail(` Frontend conversion failed: ${err.message}`);
304
+ results.push({ message: err.message, part: 'frontend', status: 'failed' });
305
+ }
306
+ }
307
+ else {
308
+ const targetVersion = parameters.options['frontend-framework-version'];
309
+ const spinner = spin(' Converting frontend to npm mode...');
310
+ try {
311
+ yield frontendHelper.convertAppToNpmMode({
312
+ dest: frontendDir,
313
+ targetVersion,
314
+ });
315
+ spinner.succeed(' Frontend converted to npm mode');
316
+ results.push({ part: 'frontend', status: 'ok' });
317
+ }
318
+ catch (err) {
319
+ spinner.fail(` Frontend conversion failed: ${err.message}`);
320
+ results.push({ message: err.message, part: 'frontend', status: 'failed' });
321
+ }
322
+ }
323
+ }
324
+ else if (frontendDir) {
325
+ const reason = skipFrontend ? 'skipped via flag' : `already in ${targetMode} mode`;
326
+ info('');
327
+ info(colors.dim(`[2/2] Frontend: ${reason}`));
328
+ results.push({ message: reason, part: 'frontend', status: 'skipped' });
329
+ }
330
+ // ── Summary ─────────────────────────────────────────────────────────
331
+ info('');
332
+ info(colors.bold('Summary:'));
333
+ info(colors.dim('─'.repeat(60)));
334
+ for (const result of results) {
335
+ const icon = result.status === 'ok' ? colors.green('✓') : result.status === 'skipped' ? colors.dim('–') : colors.red('✗');
336
+ const label = result.part.padEnd(10);
337
+ info(` ${icon} ${label} ${result.message || result.status}`);
338
+ }
339
+ info(colors.dim('─'.repeat(60)));
340
+ const failed = results.filter((r) => r.status === 'failed');
341
+ if (failed.length > 0) {
342
+ info('');
343
+ error(`${failed.length} conversion(s) failed. See messages above.`);
344
+ if (!parameters.options.fromGluegunMenu) {
345
+ process.exit(1);
346
+ }
347
+ return `fullstack convert-mode failed (${failed.length} error(s))`;
348
+ }
349
+ info('');
350
+ success('All conversions completed successfully.');
351
+ info('');
352
+ info(colors.bold('Next steps:'));
353
+ info(' 1. Run: pnpm install (from monorepo root)');
354
+ if (backendDir && results.find((r) => r.part === 'backend' && r.status === 'ok')) {
355
+ info(' 2. Verify backend: cd projects/api && pnpm exec tsc --noEmit && pnpm test');
356
+ }
357
+ if (frontendDir && results.find((r) => r.part === 'frontend' && r.status === 'ok')) {
358
+ info(' 3. Verify frontend: cd projects/app && pnpm run build');
359
+ }
360
+ info(' 4. Commit the changes');
361
+ info('');
362
+ if (!parameters.options.fromGluegunMenu) {
363
+ process.exit();
364
+ }
365
+ return `fullstack convert-mode completed (target: ${targetMode})`;
366
+ }),
367
+ };
368
+ exports.default = ConvertModeCommand;