@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 +5 -3
- package/build/commands/config/validate.js +2 -0
- package/build/commands/frontend/convert-mode.js +198 -0
- package/build/commands/fullstack/convert-mode.js +368 -0
- package/build/commands/fullstack/init.js +44 -2
- package/build/commands/fullstack/update.js +49 -1
- package/build/commands/server/convert-mode.js +197 -0
- package/build/commands/status.js +81 -2
- package/build/config/vendor-frontend-runtime-deps.json +4 -0
- package/build/extensions/frontend-helper.js +652 -0
- package/build/extensions/server.js +515 -68
- package/build/lib/frontend-framework-detection.js +129 -0
- package/docs/LT-ECOSYSTEM-GUIDE.md +972 -0
- package/docs/VENDOR-MODE-WORKFLOW.md +471 -0
- package/docs/commands.md +196 -0
- package/docs/lt.config.md +9 -7
- package/package.json +2 -1
- package/build/templates/vendor-scripts/check-vendor-freshness.mjs +0 -131
- package/build/templates/vendor-scripts/propose-upstream-pr.ts +0 -269
- package/build/templates/vendor-scripts/sync-from-upstream.ts +0 -250
package/README.md
CHANGED
|
@@ -38,9 +38,11 @@ $ lt
|
|
|
38
38
|
|
|
39
39
|
## Documentation
|
|
40
40
|
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
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;
|