@lenne.tech/cli 1.24.1 → 1.25.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/build/commands/dev/{migrate.js → init.js} +35 -47
- package/build/commands/dev/install.js +37 -132
- package/build/commands/dev/status.js +5 -5
- package/build/commands/dev/tunnel.js +1 -1
- package/build/commands/dev/up.js +3 -3
- package/build/commands/fullstack/init.js +7 -1
- package/build/lib/dev-bootstrap.js +75 -0
- package/build/lib/dev-env.js +6 -1
- package/build/lib/dev-install-helper.js +136 -0
- package/build/lib/dev-migrate-helper.js +41 -3
- package/build/lib/dev-patches.js +66 -24
- package/build/lib/hoist-workspace-pnpm-config.js +144 -18
- package/build/lib/package-name.js +39 -0
- package/docs/commands.md +11 -5
- package/package.json +15 -8
|
@@ -10,15 +10,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const caddy_1 = require("../../lib/caddy");
|
|
13
|
+
const dev_bootstrap_1 = require("../../lib/dev-bootstrap");
|
|
14
|
+
const dev_install_helper_1 = require("../../lib/dev-install-helper");
|
|
13
15
|
const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
|
|
14
16
|
const dev_project_1 = require("../../lib/dev-project");
|
|
17
|
+
const dev_service_1 = require("../../lib/dev-service");
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
17
|
-
* env-aware patches.
|
|
19
|
+
* Initialize an existing project for `lt dev` and apply idempotent
|
|
20
|
+
* env-aware patches. (Formerly `lt dev migrate`; `migrate` stays as an
|
|
21
|
+
* alias for backwards compatibility.)
|
|
18
22
|
*
|
|
19
23
|
* Idempotent — re-running with no changes is a no-op. Safe to invoke
|
|
20
24
|
* automatically after `lt fullstack init` or by developers manually.
|
|
21
25
|
*
|
|
26
|
+
* Auto-chaining: if the machine has not been prepared yet (`lt dev
|
|
27
|
+
* install` never ran), it runs install FIRST, then initializes the
|
|
28
|
+
* project. The chain is one hop deep and cannot recurse, because this
|
|
29
|
+
* command calls the `runInstall` *helper* — never the install command
|
|
30
|
+
* (see `dev-bootstrap.ts`). Pass `--skip-install` to opt out.
|
|
31
|
+
*
|
|
22
32
|
* Steps (delegated to `lib/dev-migrate-helper.ts#runMigrate`):
|
|
23
33
|
* 1. Resolve workspace layout (api/app dirs, root)
|
|
24
34
|
* 2. Build identity (slug + subdomains)
|
|
@@ -27,14 +37,12 @@ const dev_project_1 = require("../../lib/dev-project");
|
|
|
27
37
|
* 4. Inject CLAUDE.md URL block (root + each subproject)
|
|
28
38
|
* 5. Persist project to ~/.lenneTech/projects.json
|
|
29
39
|
* 6. Add `.lt-dev/` to .gitignore
|
|
30
|
-
*
|
|
31
|
-
* Closes with a hint to run `lt dev install` if Caddy is missing.
|
|
32
40
|
*/
|
|
33
|
-
const
|
|
34
|
-
alias: ['m'],
|
|
35
|
-
description: '
|
|
41
|
+
const InitCommand = {
|
|
42
|
+
alias: ['migrate', 'm'],
|
|
43
|
+
description: 'Init project for lt dev (idempotent)',
|
|
36
44
|
hidden: false,
|
|
37
|
-
name: '
|
|
45
|
+
name: 'init',
|
|
38
46
|
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
39
47
|
const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
|
|
40
48
|
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
@@ -42,55 +50,35 @@ const MigrateCommand = {
|
|
|
42
50
|
error('No API (src/config.env.ts) or App (nuxt.config.ts) project detected at this path.');
|
|
43
51
|
if (!parameters.options.fromGluegunMenu)
|
|
44
52
|
process.exit(1);
|
|
45
|
-
return 'dev
|
|
46
|
-
}
|
|
47
|
-
const result = (0, dev_migrate_helper_1.runMigrate)({ layout });
|
|
48
|
-
info('');
|
|
49
|
-
info(colors.bold(`Migrating "${result.identity.slug}"`));
|
|
50
|
-
info(colors.dim('─'.repeat(60)));
|
|
51
|
-
if (result.identity.subdomains.app)
|
|
52
|
-
info(` App URL: https://${result.identity.subdomains.app.hostname}`);
|
|
53
|
-
if (result.identity.subdomains.api)
|
|
54
|
-
info(` API URL: https://${result.identity.subdomains.api.hostname}`);
|
|
55
|
-
info(` DB: mongodb://127.0.0.1/${result.dbName}`);
|
|
56
|
-
info('');
|
|
57
|
-
// Code patches
|
|
58
|
-
if (result.codePatches.length > 0) {
|
|
59
|
-
for (const r of result.codePatches) {
|
|
60
|
-
if (r.patched)
|
|
61
|
-
success(`patched ${r.replacements}× in ${r.file}`);
|
|
62
|
-
else
|
|
63
|
-
info(colors.dim(`already patched: ${r.file}`));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
info(colors.dim(' patches: not needed (already env-aware)'));
|
|
53
|
+
return 'dev init: not a project';
|
|
68
54
|
}
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
55
|
+
// Auto-chain: prepare the machine first if `lt dev install` never ran.
|
|
56
|
+
// `--skip-install` opts out. Calling the runInstall HELPER (not the
|
|
57
|
+
// install command) makes infinite recursion structurally impossible.
|
|
58
|
+
const runInstallFirst = (0, dev_bootstrap_1.shouldRunInstallBeforeInit)({
|
|
59
|
+
machinePrepared: (0, dev_bootstrap_1.isMachinePrepared)(),
|
|
60
|
+
platformSupported: (0, dev_service_1.platformSupported)() !== 'unsupported',
|
|
61
|
+
skipInstall: parameters.options.skipInstall === true,
|
|
62
|
+
});
|
|
63
|
+
if (runInstallFirst) {
|
|
64
|
+
info(colors.dim('Machine not prepared for lt dev yet — running `lt dev install` first ...'));
|
|
65
|
+
yield (0, dev_install_helper_1.runInstall)(toolbox, { auto: true });
|
|
80
66
|
}
|
|
67
|
+
const result = (0, dev_migrate_helper_1.runMigrate)({ layout });
|
|
68
|
+
(0, dev_migrate_helper_1.printMigrateResult)(toolbox, result);
|
|
81
69
|
info('');
|
|
82
|
-
success('
|
|
83
|
-
//
|
|
70
|
+
success('Project initialized for lt dev.');
|
|
71
|
+
// Closing hint based on Caddy availability.
|
|
84
72
|
const caddyOk = yield (0, caddy_1.caddyAvailable)();
|
|
85
73
|
if (!caddyOk) {
|
|
86
|
-
warning('Caddy is not installed yet. Run `lt dev install` first, then `lt dev up`.');
|
|
74
|
+
warning('Caddy is not installed yet. Run `lt dev install` (installs caddy first), then `lt dev up`.');
|
|
87
75
|
}
|
|
88
76
|
else {
|
|
89
77
|
info('Start the project with `lt dev up`.');
|
|
90
78
|
}
|
|
91
79
|
if (!parameters.options.fromGluegunMenu)
|
|
92
80
|
process.exit();
|
|
93
|
-
return `dev
|
|
81
|
+
return `dev init: ${result.identity.slug}`;
|
|
94
82
|
}),
|
|
95
83
|
};
|
|
96
|
-
module.exports =
|
|
84
|
+
module.exports = InitCommand;
|
|
@@ -9,29 +9,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const dev_bootstrap_1 = require("../../lib/dev-bootstrap");
|
|
13
|
+
const dev_install_helper_1 = require("../../lib/dev-install-helper");
|
|
14
|
+
const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
|
|
15
|
+
const dev_project_1 = require("../../lib/dev-project");
|
|
14
16
|
/**
|
|
15
17
|
* One-time per-machine setup for `lt dev`.
|
|
16
18
|
*
|
|
17
19
|
* Owns the full Caddy lifecycle through a dedicated LaunchAgent
|
|
18
|
-
* (macOS) / systemd-user unit (Linux)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* blocked the first real install.
|
|
20
|
+
* (macOS) / systemd-user unit (Linux), bypassing `brew services caddy`
|
|
21
|
+
* (whose plist hardcodes a different Caddyfile path and crash-loops).
|
|
22
|
+
* The actual steps live in `lib/dev-install-helper.ts#runInstall` so
|
|
23
|
+
* `lt dev init` can reuse them without a cross-command call.
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* 4. Install + bootstrap our LaunchAgent/systemd unit
|
|
30
|
-
* 5. Wait for the Caddy admin endpoint to respond
|
|
31
|
-
* 6. Validate the Caddyfile
|
|
32
|
-
* 7. Surface the `sudo -E HOME="$HOME" caddy trust` instruction for
|
|
33
|
-
* installing the local CA (HOME must survive sudo so caddy can
|
|
34
|
-
* find its CA files under the *user* profile, not /var/root)
|
|
25
|
+
* Auto-chaining: when run from inside an lt-dev-capable project that is
|
|
26
|
+
* not yet registered, it initializes that project afterwards (the same
|
|
27
|
+
* work `lt dev init` does). The chain is one hop deep and cannot recurse
|
|
28
|
+
* because it calls the `runMigrate` *helper* — never the init command
|
|
29
|
+
* (see `dev-bootstrap.ts`). Pass `--skip-init` to opt out.
|
|
35
30
|
*/
|
|
36
31
|
const InstallCommand = {
|
|
37
32
|
alias: ['i'],
|
|
@@ -39,134 +34,44 @@ const InstallCommand = {
|
|
|
39
34
|
hidden: false,
|
|
40
35
|
name: 'install',
|
|
41
36
|
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
-
const { parameters, print: { colors,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (plat === 'unsupported') {
|
|
48
|
-
error(`Service management is not supported on ${process.platform}. Only macOS and Linux are covered.`);
|
|
49
|
-
info(` Workaround: run \`${colors.cyan(`caddy run --config ${caddy_1.paths.caddyfile}`)}\` manually.`);
|
|
37
|
+
const { filesystem, parameters, print: { colors, info, success, warning }, } = toolbox;
|
|
38
|
+
const result = yield (0, dev_install_helper_1.runInstall)(toolbox);
|
|
39
|
+
// Fatal preconditions — nothing more to do, and an auto-init would be
|
|
40
|
+
// premature (the user must install caddy / use a supported OS first).
|
|
41
|
+
if (result.unsupported) {
|
|
50
42
|
if (!parameters.options.fromGluegunMenu)
|
|
51
43
|
process.exit(1);
|
|
52
44
|
return 'dev install: unsupported platform';
|
|
53
45
|
}
|
|
54
|
-
|
|
55
|
-
// 1. caddy on PATH
|
|
56
|
-
const hasCaddy = yield (0, caddy_1.caddyAvailable)();
|
|
57
|
-
if (hasCaddy) {
|
|
58
|
-
success('caddy is on PATH');
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
warning('caddy is not installed.');
|
|
62
|
-
info(` → macOS: ${colors.cyan('brew install caddy')}`);
|
|
63
|
-
info(` → Linux: ${colors.cyan('https://caddyserver.com/docs/install')}`);
|
|
64
|
-
info(' (Do NOT start it via `brew services` — `lt dev install` runs its own service.)');
|
|
65
|
-
blocked = true;
|
|
66
|
-
}
|
|
67
|
-
// 2. Caddyfile stub
|
|
68
|
-
(0, caddy_1.writeCaddyfile)('# lt dev — managed Caddyfile\n# Add per-project blocks via `lt dev up`.\n');
|
|
69
|
-
success(`Caddyfile present at ${caddy_1.paths.caddyfile}`);
|
|
70
|
-
if (!hasCaddy) {
|
|
71
|
-
info('');
|
|
72
|
-
error('Cannot continue setup until Caddy is installed. Re-run `lt dev install` afterwards.');
|
|
46
|
+
if (result.caddyMissing) {
|
|
73
47
|
if (!parameters.options.fromGluegunMenu)
|
|
74
|
-
process.exit(
|
|
48
|
+
process.exit(1);
|
|
75
49
|
return 'dev install: caddy missing';
|
|
76
50
|
}
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!installResult.ok) {
|
|
91
|
-
error(installResult.message);
|
|
92
|
-
blocked = true;
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
if (installResult.created)
|
|
96
|
-
success(installResult.message);
|
|
97
|
-
else
|
|
98
|
-
info(colors.dim(installResult.message));
|
|
99
|
-
}
|
|
100
|
-
// 5. Wait for admin endpoint
|
|
101
|
-
if (installResult.ok) {
|
|
102
|
-
info(colors.dim('Waiting for Caddy admin endpoint (:2019) ...'));
|
|
103
|
-
const ready = yield (0, dev_service_1.waitForServiceReady)(8000);
|
|
104
|
-
const status = yield (0, dev_service_1.getServiceStatus)();
|
|
105
|
-
if (ready && status.daemonReachable) {
|
|
106
|
-
success(`Caddy daemon ready${status.pid ? ` (pid ${status.pid})` : ''}.`);
|
|
107
|
-
}
|
|
108
|
-
else if (status.loaded && !status.daemonReachable) {
|
|
109
|
-
warning('Service is loaded but admin endpoint did not respond within 8s.');
|
|
110
|
-
info(colors.dim(` Logs: ${paths.logFile} / ${paths.errFile}`));
|
|
111
|
-
blocked = true;
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
warning('Caddy daemon did not start. See logs:');
|
|
115
|
-
info(colors.dim(` ${paths.logFile}`));
|
|
116
|
-
info(colors.dim(` ${paths.errFile}`));
|
|
117
|
-
blocked = true;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// 6. Validate Caddyfile
|
|
121
|
-
if (installResult.ok) {
|
|
122
|
-
const validation = yield (0, caddy_1.validateCaddyfile)();
|
|
123
|
-
if (validation.ok)
|
|
124
|
-
success('Caddyfile validates');
|
|
125
|
-
else
|
|
126
|
-
warning(`Caddyfile validation: ${validation.stderr.split('\n').slice(0, 2).join(' / ')}`);
|
|
51
|
+
// Auto-chain: if we're inside an un-initialized lt-dev project, run the
|
|
52
|
+
// project init now. Calling the runMigrate HELPER (not the init command)
|
|
53
|
+
// makes infinite recursion structurally impossible. `--skip-init` opts out.
|
|
54
|
+
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
55
|
+
const runInitAfter = (0, dev_bootstrap_1.shouldRunInitAfterInstall)({
|
|
56
|
+
isProject: (0, dev_bootstrap_1.isLtDevProject)(layout),
|
|
57
|
+
projectInitialized: (0, dev_bootstrap_1.isProjectInitialized)(layout),
|
|
58
|
+
skipInit: parameters.options.skipInit === true,
|
|
59
|
+
});
|
|
60
|
+
if (runInitAfter) {
|
|
61
|
+
info('');
|
|
62
|
+
info(colors.dim('Un-initialized lt dev project detected here — running `lt dev init` ...'));
|
|
63
|
+
(0, dev_migrate_helper_1.printMigrateResult)(toolbox, (0, dev_migrate_helper_1.runMigrate)({ layout }));
|
|
127
64
|
}
|
|
128
|
-
// 7. CA trust
|
|
129
|
-
info('');
|
|
130
|
-
info(colors.bold('Local CA trust'));
|
|
131
|
-
info(' Caddy creates its local CA on first run. To trust it system-wide,');
|
|
132
|
-
info(' run this once (HOME must be preserved so sudo keeps the user-scoped');
|
|
133
|
-
info(' CA, otherwise caddy looks in /var/root and fails):');
|
|
134
|
-
info(` ${colors.cyan('sudo -E HOME="$HOME" caddy trust')}`);
|
|
135
|
-
info(` Browsers will then accept ${colors.cyan('https://*.localhost')} without warnings.`);
|
|
136
65
|
info('');
|
|
137
|
-
if (blocked) {
|
|
66
|
+
if (result.blocked) {
|
|
138
67
|
warning('Setup incomplete. Address the items above and re-run `lt dev install`.');
|
|
139
68
|
}
|
|
140
69
|
else {
|
|
141
|
-
success('Setup complete. Use `lt dev
|
|
70
|
+
success('Setup complete. Use `lt dev init` in a project, then `lt dev up`.');
|
|
142
71
|
}
|
|
143
72
|
if (!parameters.options.fromGluegunMenu)
|
|
144
|
-
process.exit(blocked ? 1 : 0);
|
|
145
|
-
return blocked ? 'dev install: incomplete' : 'dev install: ok';
|
|
73
|
+
process.exit(result.blocked ? 1 : 0);
|
|
74
|
+
return result.blocked ? 'dev install: incomplete' : 'dev install: ok';
|
|
146
75
|
}),
|
|
147
76
|
};
|
|
148
|
-
/**
|
|
149
|
-
* Quick `brew services list` scan for a registered caddy service.
|
|
150
|
-
* Returns true on macOS if any entry contains "caddy" — error/started
|
|
151
|
-
* alike, both are conflicts. Always returns false on non-darwin or
|
|
152
|
-
* when `brew` is unavailable (no false positives).
|
|
153
|
-
*/
|
|
154
|
-
function detectBrewCaddyConflict() {
|
|
155
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
-
if (process.platform !== 'darwin')
|
|
157
|
-
return false;
|
|
158
|
-
return new Promise((resolve) => {
|
|
159
|
-
var _a;
|
|
160
|
-
const { spawn } = require('child_process');
|
|
161
|
-
const child = spawn('brew', ['services', 'list'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
162
|
-
let out = '';
|
|
163
|
-
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (out += String(b)));
|
|
164
|
-
child.on('error', () => resolve(false));
|
|
165
|
-
child.on('close', () => {
|
|
166
|
-
const conflict = /\bcaddy\b/.test(out) && !/^caddy\s+none\b/m.test(out);
|
|
167
|
-
resolve(conflict);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
77
|
module.exports = InstallCommand;
|
|
@@ -36,7 +36,7 @@ const StatusCommand = {
|
|
|
36
36
|
info(colors.dim('─'.repeat(60)));
|
|
37
37
|
const slugs = Object.keys(reg.projects).sort();
|
|
38
38
|
if (slugs.length === 0) {
|
|
39
|
-
warning('No projects registered. Run `lt dev
|
|
39
|
+
warning('No projects registered. Run `lt dev init` in a project.');
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
42
|
for (const slug of slugs) {
|
|
@@ -62,7 +62,7 @@ const StatusCommand = {
|
|
|
62
62
|
info(colors.bold(`lt dev status: ${identity.slug}`));
|
|
63
63
|
info(colors.dim('─'.repeat(60)));
|
|
64
64
|
if (!entry) {
|
|
65
|
-
warning('Not registered. Run `lt dev
|
|
65
|
+
warning('Not registered. Run `lt dev init` first.');
|
|
66
66
|
// Show what migrate would do (legacy code present?) so the user
|
|
67
67
|
// can judge urgency before running it.
|
|
68
68
|
const legacyFiles = [];
|
|
@@ -74,11 +74,11 @@ const StatusCommand = {
|
|
|
74
74
|
if (layout.appDir)
|
|
75
75
|
legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
|
|
76
76
|
if (legacyFiles.length > 0) {
|
|
77
|
-
info(colors.dim(' Legacy hardcoded ports detected — `lt dev
|
|
77
|
+
info(colors.dim(' Legacy hardcoded ports detected — `lt dev init` will patch:'));
|
|
78
78
|
legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
|
-
info(colors.dim(' Code is already env-aware; `lt dev
|
|
81
|
+
info(colors.dim(' Code is already env-aware; `lt dev init` will only register + patch CLAUDE.md.'));
|
|
82
82
|
}
|
|
83
83
|
info('');
|
|
84
84
|
if (!parameters.options.fromGluegunMenu)
|
|
@@ -104,7 +104,7 @@ const StatusCommand = {
|
|
|
104
104
|
info('');
|
|
105
105
|
warning(' Legacy hardcoded ports still present:');
|
|
106
106
|
legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
|
|
107
|
-
info(colors.dim(' Run `lt dev
|
|
107
|
+
info(colors.dim(' Run `lt dev init` to patch them; otherwise Caddy may proxy into the void.'));
|
|
108
108
|
}
|
|
109
109
|
// Caddy status — quick view whether the daemon is reachable.
|
|
110
110
|
{
|
|
@@ -49,7 +49,7 @@ const TunnelCommand = {
|
|
|
49
49
|
const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
|
|
50
50
|
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
51
51
|
if (!layout.apiDir && !layout.appDir) {
|
|
52
|
-
error('No API or App project detected at this path. Run `lt dev
|
|
52
|
+
error('No API or App project detected at this path. Run `lt dev init` first.');
|
|
53
53
|
if (!parameters.options.fromGluegunMenu)
|
|
54
54
|
process.exit(1);
|
|
55
55
|
return 'dev tunnel: not a project';
|
package/build/commands/dev/up.js
CHANGED
|
@@ -50,7 +50,7 @@ const UpCommand = {
|
|
|
50
50
|
const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
|
|
51
51
|
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
52
52
|
if (!layout.apiDir && !layout.appDir) {
|
|
53
|
-
error('No API or App project detected at this path. Run `lt dev
|
|
53
|
+
error('No API or App project detected at this path. Run `lt dev init` first.');
|
|
54
54
|
if (!parameters.options.fromGluegunMenu)
|
|
55
55
|
process.exit(1);
|
|
56
56
|
return 'dev up: not a project';
|
|
@@ -72,7 +72,7 @@ const UpCommand = {
|
|
|
72
72
|
const dbName = (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug);
|
|
73
73
|
// Sanft auto-migrate sichere Operationen (ohne Code-Modifikation):
|
|
74
74
|
// CLAUDE.md-URL-Block einfügen + .gitignore ergänzen.
|
|
75
|
-
// Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev
|
|
75
|
+
// Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev init`.
|
|
76
76
|
{
|
|
77
77
|
const claudeCandidates = [
|
|
78
78
|
(0, path_1.join)(layout.root, 'CLAUDE.md'),
|
|
@@ -98,7 +98,7 @@ const UpCommand = {
|
|
|
98
98
|
if (layout.appDir)
|
|
99
99
|
legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
|
|
100
100
|
if (legacyFiles.length > 0) {
|
|
101
|
-
warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev
|
|
101
|
+
warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev init`:');
|
|
102
102
|
legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
|
|
103
103
|
info(colors.dim(' (Continuing — env-aware files will work; legacy files may bind on 3000/3001 and miss Caddy.)'));
|
|
104
104
|
}
|
|
@@ -17,6 +17,7 @@ const caddy_1 = require("../../lib/caddy");
|
|
|
17
17
|
const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
|
|
18
18
|
const dev_project_1 = require("../../lib/dev-project");
|
|
19
19
|
const hoist_workspace_pnpm_config_1 = require("../../lib/hoist-workspace-pnpm-config");
|
|
20
|
+
const package_name_1 = require("../../lib/package-name");
|
|
20
21
|
const workspace_integration_1 = require("../../lib/workspace-integration");
|
|
21
22
|
const add_api_1 = __importDefault(require("./add-api"));
|
|
22
23
|
const add_app_1 = __importDefault(require("./add-app"));
|
|
@@ -440,6 +441,11 @@ const NewCommand = {
|
|
|
440
441
|
.replace(/\{\{FRONTEND_FRAMEWORK\}\}/g, () => frontendName));
|
|
441
442
|
}
|
|
442
443
|
}
|
|
444
|
+
// Rename the cloned monorepo's root package so each project gets a unique
|
|
445
|
+
// `lt dev` slug. The slug is derived from package.json `name`; without
|
|
446
|
+
// this rename every lt-monorepo-based project would register as
|
|
447
|
+
// `lt-monorepo` and collide on `https://lt-monorepo.localhost`.
|
|
448
|
+
(0, package_name_1.setPackageName)({ filesystem, name: projectDir, packageJsonPath: `${projectDir}/package.json` });
|
|
443
449
|
// Always initialize git
|
|
444
450
|
try {
|
|
445
451
|
yield system.run(`cd ${projectDir} && git init --initial-branch=dev`);
|
|
@@ -653,7 +659,7 @@ const NewCommand = {
|
|
|
653
659
|
return;
|
|
654
660
|
}
|
|
655
661
|
}
|
|
656
|
-
// Best-effort `lt dev
|
|
662
|
+
// Best-effort `lt dev init` so the workspace is ready for `lt dev up`
|
|
657
663
|
// out-of-the-box: registers the slug in `~/.lenneTech/projects.json`,
|
|
658
664
|
// injects the URL block into CLAUDE.md, adds `.lt-dev/` to .gitignore.
|
|
659
665
|
// Failures here are non-fatal — `init` itself remains successful.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLtDevProject = isLtDevProject;
|
|
4
|
+
exports.isMachinePrepared = isMachinePrepared;
|
|
5
|
+
exports.isProjectInitialized = isProjectInitialized;
|
|
6
|
+
exports.shouldRunInitAfterInstall = shouldRunInitAfterInstall;
|
|
7
|
+
exports.shouldRunInstallBeforeInit = shouldRunInstallBeforeInit;
|
|
8
|
+
/**
|
|
9
|
+
* Bootstrap-state checks that drive the mutual auto-chaining between
|
|
10
|
+
* `lt dev install` (per-machine setup) and `lt dev init` (per-project
|
|
11
|
+
* setup).
|
|
12
|
+
*
|
|
13
|
+
* These are pure, synchronous predicates so the chaining decision is
|
|
14
|
+
* trivially testable and cannot itself trigger side effects. The actual
|
|
15
|
+
* work is delegated to `runInstall` / `runMigrate`, which never call each
|
|
16
|
+
* other — so the install↔init chaining can never recurse infinitely:
|
|
17
|
+
*
|
|
18
|
+
* - `lt dev init` → if NOT machine-prepared, run install first, then init
|
|
19
|
+
* - `lt dev install`→ if inside an un-initialized project, run init after
|
|
20
|
+
*
|
|
21
|
+
* Because each command calls the *helpers* (not the other command), the
|
|
22
|
+
* chain is at most one hop deep in either direction. The predicates below
|
|
23
|
+
* additionally make re-runs no-ops (nothing repeated unnecessarily).
|
|
24
|
+
*/
|
|
25
|
+
const fs_1 = require("fs");
|
|
26
|
+
const dev_identity_1 = require("./dev-identity");
|
|
27
|
+
const dev_service_1 = require("./dev-service");
|
|
28
|
+
const dev_state_1 = require("./dev-state");
|
|
29
|
+
/**
|
|
30
|
+
* True if this path is an lt-dev-capable project — i.e. `resolveLayout`
|
|
31
|
+
* found an API (`src/config.env.ts`) and/or App (`nuxt.config.ts`).
|
|
32
|
+
* Used by `lt dev install` to decide whether an auto-init makes sense.
|
|
33
|
+
*/
|
|
34
|
+
function isLtDevProject(layout) {
|
|
35
|
+
return !!(layout.apiDir || layout.appDir);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* True once `lt dev install` has set up this machine — i.e. the
|
|
39
|
+
* LaunchAgent/systemd unit file exists. This is the durable marker that
|
|
40
|
+
* install has run; whether the daemon is currently *running* is a
|
|
41
|
+
* separate concern handled by `lt dev up` / `doctor`.
|
|
42
|
+
*
|
|
43
|
+
* Always false on unsupported platforms (no service model), so the
|
|
44
|
+
* chaining never tries to install where it cannot.
|
|
45
|
+
*/
|
|
46
|
+
function isMachinePrepared() {
|
|
47
|
+
const paths = (0, dev_service_1.getServicePaths)();
|
|
48
|
+
return paths.platform !== 'unsupported' && (0, fs_1.existsSync)(paths.unitFile);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* True if this project is already registered with `lt dev` (present in
|
|
52
|
+
* `~/.lenneTech/projects.json` under its slug, pointing at this root).
|
|
53
|
+
* This is the durable marker that `lt dev init` has run for the project.
|
|
54
|
+
*/
|
|
55
|
+
function isProjectInitialized(layout) {
|
|
56
|
+
const slug = (0, dev_identity_1.buildIdentity)(layout.root).slug;
|
|
57
|
+
const entry = (0, dev_state_1.loadRegistry)().projects[slug];
|
|
58
|
+
return !!entry && entry.path === layout.root;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pure decision: should `lt dev install` run `init` AFTERWARDS? Yes only
|
|
62
|
+
* when not opted out, we're inside an lt-dev-capable project, and that
|
|
63
|
+
* project isn't initialized yet. Re-runs become no-ops once initialized.
|
|
64
|
+
*/
|
|
65
|
+
function shouldRunInitAfterInstall(input) {
|
|
66
|
+
return !input.skipInit && input.isProject && !input.projectInitialized;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Pure decision: should `lt dev init` run `install` BEFORE initializing?
|
|
70
|
+
* Yes only when not opted out, the platform supports a service model, and
|
|
71
|
+
* the machine isn't prepared yet. Re-runs become no-ops once prepared.
|
|
72
|
+
*/
|
|
73
|
+
function shouldRunInstallBeforeInit(input) {
|
|
74
|
+
return !input.skipInstall && input.platformSupported && !input.machinePrepared;
|
|
75
|
+
}
|
package/build/lib/dev-env.js
CHANGED
|
@@ -35,7 +35,12 @@ function buildDevEnv(input) {
|
|
|
35
35
|
const apiUrl = apiSub ? `https://${apiSub.hostname}` : '';
|
|
36
36
|
const appUrl = appSub ? `https://${appSub.hostname}` : '';
|
|
37
37
|
const caPath = (0, dev_env_bridge_1.detectCaddyRootCa)();
|
|
38
|
-
const sharedKeys = Object.assign(Object.assign(Object.assign(Object.assign({
|
|
38
|
+
const sharedKeys = Object.assign(Object.assign(Object.assign(Object.assign({
|
|
39
|
+
// Marks the API + App processes as running under `lt dev`. Consumed by the
|
|
40
|
+
// backend to relax dev-only behaviour (rate limiting, Better-Auth
|
|
41
|
+
// user-cache) so E2E suites run without a separate VITEST/PLAYWRIGHT flag.
|
|
42
|
+
// (Also written to the .lt-dev/.env bridge for external test runners.)
|
|
43
|
+
LT_DEV_ACTIVE: 'true' }, (apiUrl ? { BASE_URL: apiUrl, NSC__BASE_URL: apiUrl } : {})), (appUrl ? { APP_URL: appUrl, NSC__APP_URL: appUrl } : {})), (dbName ? { DATABASE_URL: buildPostgresUrl(dbName), NSC__MONGOOSE__URI: `mongodb://127.0.0.1/${dbName}` } : {})), (caPath ? { NODE_EXTRA_CA_CERTS: caPath } : {}));
|
|
39
44
|
return {
|
|
40
45
|
api: {
|
|
41
46
|
env: Object.assign(Object.assign(Object.assign({}, baseEnv), sharedKeys), {
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
exports.runInstall = runInstall;
|
|
13
|
+
const caddy_1 = require("./caddy");
|
|
14
|
+
const dev_service_1 = require("./dev-service");
|
|
15
|
+
/**
|
|
16
|
+
* Run the one-time per-machine `lt dev` setup. Idempotent — safe to
|
|
17
|
+
* re-run. When `opts.auto` is set the heading reflects that it was
|
|
18
|
+
* triggered by another command (e.g. `lt dev init`).
|
|
19
|
+
*/
|
|
20
|
+
function runInstall(toolbox_1) {
|
|
21
|
+
return __awaiter(this, arguments, void 0, function* (toolbox, opts = {}) {
|
|
22
|
+
const { print: { colors, error, info, success, warning }, } = toolbox;
|
|
23
|
+
info('');
|
|
24
|
+
info(colors.bold(opts.auto ? 'Preparing this machine for lt dev (lt dev install)' : 'lt dev install — one-time per-machine setup'));
|
|
25
|
+
info(colors.dim('─'.repeat(60)));
|
|
26
|
+
const plat = (0, dev_service_1.platformSupported)();
|
|
27
|
+
if (plat === 'unsupported') {
|
|
28
|
+
error(`Service management is not supported on ${process.platform}. Only macOS and Linux are covered.`);
|
|
29
|
+
info(` Workaround: run \`${colors.cyan(`caddy run --config ${caddy_1.paths.caddyfile}`)}\` manually.`);
|
|
30
|
+
return { blocked: true, caddyMissing: false, ok: false, unsupported: true };
|
|
31
|
+
}
|
|
32
|
+
let blocked = false;
|
|
33
|
+
// 1. caddy on PATH
|
|
34
|
+
const hasCaddy = yield (0, caddy_1.caddyAvailable)();
|
|
35
|
+
if (hasCaddy) {
|
|
36
|
+
success('caddy is on PATH');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
warning('caddy is not installed.');
|
|
40
|
+
info(` → macOS: ${colors.cyan('brew install caddy')}`);
|
|
41
|
+
info(` → Linux: ${colors.cyan('https://caddyserver.com/docs/install')}`);
|
|
42
|
+
info(' (Do NOT start it via `brew services` — `lt dev install` runs its own service.)');
|
|
43
|
+
blocked = true;
|
|
44
|
+
}
|
|
45
|
+
// 2. Caddyfile stub
|
|
46
|
+
(0, caddy_1.writeCaddyfile)('# lt dev — managed Caddyfile\n# Add per-project blocks via `lt dev up`.\n');
|
|
47
|
+
success(`Caddyfile present at ${caddy_1.paths.caddyfile}`);
|
|
48
|
+
if (!hasCaddy) {
|
|
49
|
+
info('');
|
|
50
|
+
error('Cannot continue setup until Caddy is installed. Re-run `lt dev install` afterwards.');
|
|
51
|
+
return { blocked: true, caddyMissing: true, ok: false, unsupported: false };
|
|
52
|
+
}
|
|
53
|
+
// 3. brew services conflict warning
|
|
54
|
+
const brewConflict = yield detectBrewCaddyConflict();
|
|
55
|
+
if (brewConflict) {
|
|
56
|
+
warning('A `brew services caddy` instance is registered.');
|
|
57
|
+
info(` Stop it (it crash-loops against our Caddyfile): ${colors.cyan('brew services stop caddy')}`);
|
|
58
|
+
info(' `lt dev install` runs its own service — the brew one is no longer needed.');
|
|
59
|
+
}
|
|
60
|
+
// 4. Install our LaunchAgent / systemd unit
|
|
61
|
+
const paths = (0, dev_service_1.getServicePaths)();
|
|
62
|
+
info('');
|
|
63
|
+
info(`Installing ${plat === 'darwin' ? 'LaunchAgent' : 'systemd-user unit'} at:`);
|
|
64
|
+
info(colors.dim(` ${paths.unitFile}`));
|
|
65
|
+
const installResult = yield (0, dev_service_1.installService)();
|
|
66
|
+
if (!installResult.ok) {
|
|
67
|
+
error(installResult.message);
|
|
68
|
+
blocked = true;
|
|
69
|
+
}
|
|
70
|
+
else if (installResult.created) {
|
|
71
|
+
success(installResult.message);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
info(colors.dim(installResult.message));
|
|
75
|
+
}
|
|
76
|
+
// 5. Wait for admin endpoint
|
|
77
|
+
if (installResult.ok) {
|
|
78
|
+
info(colors.dim('Waiting for Caddy admin endpoint (:2019) ...'));
|
|
79
|
+
const ready = yield (0, dev_service_1.waitForServiceReady)(8000);
|
|
80
|
+
const status = yield (0, dev_service_1.getServiceStatus)();
|
|
81
|
+
if (ready && status.daemonReachable) {
|
|
82
|
+
success(`Caddy daemon ready${status.pid ? ` (pid ${status.pid})` : ''}.`);
|
|
83
|
+
}
|
|
84
|
+
else if (status.loaded && !status.daemonReachable) {
|
|
85
|
+
warning('Service is loaded but admin endpoint did not respond within 8s.');
|
|
86
|
+
info(colors.dim(` Logs: ${paths.logFile} / ${paths.errFile}`));
|
|
87
|
+
blocked = true;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
warning('Caddy daemon did not start. See logs:');
|
|
91
|
+
info(colors.dim(` ${paths.logFile}`));
|
|
92
|
+
info(colors.dim(` ${paths.errFile}`));
|
|
93
|
+
blocked = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 6. Validate Caddyfile
|
|
97
|
+
if (installResult.ok) {
|
|
98
|
+
const validation = yield (0, caddy_1.validateCaddyfile)();
|
|
99
|
+
if (validation.ok)
|
|
100
|
+
success('Caddyfile validates');
|
|
101
|
+
else
|
|
102
|
+
warning(`Caddyfile validation: ${validation.stderr.split('\n').slice(0, 2).join(' / ')}`);
|
|
103
|
+
}
|
|
104
|
+
// 7. CA trust
|
|
105
|
+
info('');
|
|
106
|
+
info(colors.bold('Local CA trust'));
|
|
107
|
+
info(' Caddy creates its local CA on first run. To trust it system-wide,');
|
|
108
|
+
info(' run this once (HOME must be preserved so sudo keeps the user-scoped');
|
|
109
|
+
info(' CA, otherwise caddy looks in /var/root and fails):');
|
|
110
|
+
info(` ${colors.cyan('sudo -E HOME="$HOME" caddy trust')}`);
|
|
111
|
+
info(` Browsers will then accept ${colors.cyan('https://*.localhost')} without warnings.`);
|
|
112
|
+
return { blocked, caddyMissing: false, ok: installResult.ok, unsupported: false };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Quick `brew services list` scan for a registered caddy service.
|
|
117
|
+
* Returns true on macOS if any entry contains "caddy" — error/started
|
|
118
|
+
* alike, both are conflicts. Always returns false on non-darwin or
|
|
119
|
+
* when `brew` is unavailable (no false positives).
|
|
120
|
+
*/
|
|
121
|
+
function detectBrewCaddyConflict() {
|
|
122
|
+
if (process.platform !== 'darwin')
|
|
123
|
+
return Promise.resolve(false);
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
var _a;
|
|
126
|
+
const { spawn } = require('child_process');
|
|
127
|
+
const child = spawn('brew', ['services', 'list'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
128
|
+
let out = '';
|
|
129
|
+
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (out += String(b)));
|
|
130
|
+
child.on('error', () => resolve(false));
|
|
131
|
+
child.on('close', () => {
|
|
132
|
+
const conflict = /\bcaddy\b/.test(out) && !/^caddy\s+none\b/m.test(out);
|
|
133
|
+
resolve(conflict);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printMigrateResult = printMigrateResult;
|
|
3
4
|
exports.runMigrate = runMigrate;
|
|
4
5
|
/**
|
|
5
|
-
* Reusable migrate logic — used by `commands/dev/
|
|
6
|
-
* + verbose
|
|
7
|
-
* project creation).
|
|
6
|
+
* Reusable init/migrate logic — used by `commands/dev/init.ts` (interactive
|
|
7
|
+
* + verbose, also chained from `commands/dev/install.ts`) and
|
|
8
|
+
* `commands/fullstack/init.ts` (silent best-effort after project creation).
|
|
8
9
|
*
|
|
9
10
|
* Returns a structured result so callers can decide what to print.
|
|
10
11
|
* Idempotent — safe to run multiple times.
|
|
@@ -15,6 +16,43 @@ const dev_identity_1 = require("./dev-identity");
|
|
|
15
16
|
const dev_patches_1 = require("./dev-patches");
|
|
16
17
|
const dev_project_1 = require("./dev-project");
|
|
17
18
|
const dev_state_1 = require("./dev-state");
|
|
19
|
+
/**
|
|
20
|
+
* Print a `runMigrate` result via the toolbox. Shared by `lt dev init`
|
|
21
|
+
* and the auto-init step of `lt dev install` so both render identically.
|
|
22
|
+
* Does NOT print Caddy-install hints — chaining handles that separately.
|
|
23
|
+
*/
|
|
24
|
+
function printMigrateResult(toolbox, result) {
|
|
25
|
+
const { print: { colors, info, success }, } = toolbox;
|
|
26
|
+
info('');
|
|
27
|
+
info(colors.bold(`Initializing "${result.identity.slug}" for lt dev`));
|
|
28
|
+
info(colors.dim('─'.repeat(60)));
|
|
29
|
+
if (result.identity.subdomains.app)
|
|
30
|
+
info(` App URL: https://${result.identity.subdomains.app.hostname}`);
|
|
31
|
+
if (result.identity.subdomains.api)
|
|
32
|
+
info(` API URL: https://${result.identity.subdomains.api.hostname}`);
|
|
33
|
+
info(` DB: mongodb://127.0.0.1/${result.dbName}`);
|
|
34
|
+
info('');
|
|
35
|
+
if (result.codePatches.length > 0) {
|
|
36
|
+
for (const r of result.codePatches) {
|
|
37
|
+
if (r.patched)
|
|
38
|
+
success(`patched ${r.replacements}× in ${r.file}`);
|
|
39
|
+
else
|
|
40
|
+
info(colors.dim(`already patched: ${r.file}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
info(colors.dim(' patches: not needed (already env-aware)'));
|
|
45
|
+
}
|
|
46
|
+
result.claudePatches.filter((r) => r.patched).forEach((r) => success(`updated CLAUDE.md URL block: ${r.file}`));
|
|
47
|
+
if (result.registryUpdated) {
|
|
48
|
+
success(`registered in ${process.env.LT_DEV_REGISTRY_PATH || '~/.lenneTech/projects.json'}`);
|
|
49
|
+
}
|
|
50
|
+
if (result.addedGitignoreEntry)
|
|
51
|
+
success('added `.lt-dev/` to .gitignore');
|
|
52
|
+
if (result.alreadyMigrated) {
|
|
53
|
+
info(colors.dim(' Project was already initialized — nothing changed.'));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
18
56
|
/**
|
|
19
57
|
* Run all migration steps for a resolved project.
|
|
20
58
|
*
|
package/build/lib/dev-patches.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.patchClaudeMd = patchClaudeMd;
|
|
|
7
7
|
exports.patchNuxtConfig = patchNuxtConfig;
|
|
8
8
|
exports.patchPlaywrightConfig = patchPlaywrightConfig;
|
|
9
9
|
/**
|
|
10
|
-
* Idempotent patches applied by `lt dev
|
|
10
|
+
* Idempotent patches applied by `lt dev init`.
|
|
11
11
|
*
|
|
12
12
|
* Goal: take a project that still has hardcoded `localhost:3000`
|
|
13
13
|
* defaults and make it env-aware so it can be served behind Caddy
|
|
@@ -40,16 +40,32 @@ function autoPatch(file) {
|
|
|
40
40
|
return patchPlaywrightConfig(file);
|
|
41
41
|
return { file, patched: false, replacements: 0 };
|
|
42
42
|
}
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* API: make the server listen port honour `process.env.PORT` (injected by
|
|
45
|
+
* `lt dev up` for its Caddy upstream). Handles two patterns found in
|
|
46
|
+
* nest-server `config.env.ts` files:
|
|
47
|
+
*
|
|
48
|
+
* - the legacy literal `port: 3000,` (e.g. in `deployedConfig()`)
|
|
49
|
+
* - the offers-pattern `port: process.env.NSC__PORT ? parseInt(process.env.NSC__PORT, 10) : 3000`
|
|
50
|
+
* found in `localConfig()` — `lt dev` runs the API in local mode, so this
|
|
51
|
+
* line MUST be patched too or the API ignores the assigned port.
|
|
52
|
+
*
|
|
53
|
+
* Idempotent — lines that already read `process.env.PORT` are left untouched.
|
|
54
|
+
*/
|
|
44
55
|
function patchApiConfig(file) {
|
|
45
56
|
if (!(0, fs_1.existsSync)(file))
|
|
46
57
|
return { file, patched: false, replacements: 0 };
|
|
47
58
|
const before = (0, fs_1.readFileSync)(file, 'utf8');
|
|
48
59
|
let count = 0;
|
|
49
|
-
|
|
60
|
+
let after = before.replace(/^(\s*)port:\s*3000\s*,$/gm, (_m, indent) => {
|
|
50
61
|
count++;
|
|
51
62
|
return `${indent}port: Number(process.env.PORT) || 3000,`;
|
|
52
63
|
});
|
|
64
|
+
// localConfig() keeps the NSC__PORT operator override; PORT (lt dev) wins.
|
|
65
|
+
after = after.replace(/^(\s*)port:\s*process\.env\.NSC__PORT\s*\?[^\n]*:\s*3000\s*,$/gm, (_m, indent) => {
|
|
66
|
+
count++;
|
|
67
|
+
return `${indent}port: Number(process.env.PORT) || (process.env.NSC__PORT ? parseInt(process.env.NSC__PORT, 10) : 3000),`;
|
|
68
|
+
});
|
|
53
69
|
if (count === 0)
|
|
54
70
|
return { file, patched: false, replacements: 0 };
|
|
55
71
|
(0, fs_1.writeFileSync)(file, after, 'utf8');
|
|
@@ -69,6 +85,7 @@ function patchClaudeMd(file, options) {
|
|
|
69
85
|
const appSub = identity.subdomains.app;
|
|
70
86
|
const lines = [
|
|
71
87
|
startMarker,
|
|
88
|
+
'',
|
|
72
89
|
'## Local Development (lt dev)',
|
|
73
90
|
'',
|
|
74
91
|
`This project is registered with \`lt dev\` (slug: \`${identity.slug}\`). Use these commands to run alongside other lt projects without cross-wiring or port collisions:`,
|
|
@@ -77,9 +94,12 @@ function patchClaudeMd(file, options) {
|
|
|
77
94
|
'lt dev up # Start API + App behind Caddy with project-specific URLs',
|
|
78
95
|
'lt dev down # Stop the detached processes + remove Caddy block',
|
|
79
96
|
'lt dev status # Show running PIDs + bound URLs',
|
|
97
|
+
'lt dev test # Ensure up + run the E2E suite with project URLs injected',
|
|
80
98
|
'lt dev doctor # Diagnose Caddy/CA/DNS/port issues',
|
|
81
99
|
'```',
|
|
82
100
|
'',
|
|
101
|
+
'**Start and test local apps via `lt dev`** — never `pnpm dev` / `pnpm start` / a bare `playwright test` directly; those bind the framework default ports (3000/3001) and collide with parallel projects.',
|
|
102
|
+
'',
|
|
83
103
|
'**Active URLs for THIS project:**',
|
|
84
104
|
'',
|
|
85
105
|
];
|
|
@@ -88,7 +108,7 @@ function patchClaudeMd(file, options) {
|
|
|
88
108
|
if (apiSub)
|
|
89
109
|
lines.push(`- API: \`https://${apiSub.hostname}\``);
|
|
90
110
|
if (dbName)
|
|
91
|
-
lines.push(`- DB:
|
|
111
|
+
lines.push(`- DB: \`mongodb://127.0.0.1/${dbName}\``);
|
|
92
112
|
lines.push('');
|
|
93
113
|
lines.push('Env vars set automatically by `lt dev up`: `BASE_URL`, `APP_URL`, `NUXT_API_URL`, `NUXT_PUBLIC_API_URL`, `NUXT_PUBLIC_SITE_URL`, `NUXT_PUBLIC_STORAGE_PREFIX`, `NSC__MONGOOSE__URI`, `DATABASE_URL`. **Never assume `localhost:3000` / `localhost:3001` for this project** — those are the framework defaults, not the active URLs.');
|
|
94
114
|
lines.push('');
|
|
@@ -159,30 +179,52 @@ function patchPlaywrightConfig(file) {
|
|
|
159
179
|
return `${key}: process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:3001'`;
|
|
160
180
|
});
|
|
161
181
|
}
|
|
162
|
-
// 2. Top-of-file dotenv bridge —
|
|
182
|
+
// 2. Top-of-file dotenv bridge — inject it, or replace an outdated block.
|
|
183
|
+
// The loader walks UP from cwd to find `.lt-dev/.env`: that file lives
|
|
184
|
+
// at the repo root, while playwright.config.ts (and the process cwd of
|
|
185
|
+
// a direct `playwright test` run) usually sit in `projects/app`. The
|
|
186
|
+
// original cwd-only resolve missed it, so direct runs fell back to
|
|
187
|
+
// `localhost:3001` and could collide with a parallel project.
|
|
163
188
|
const bridgeStart = '// >>> lt-dev:bridge >>>';
|
|
164
189
|
const bridgeEnd = '// <<< lt-dev:bridge <<<';
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
const bridgeBlock = [
|
|
191
|
+
bridgeStart,
|
|
192
|
+
'// Auto-load <root>/.lt-dev/.env when `lt dev up` is active so',
|
|
193
|
+
'// external test runners (CLI, IDE, VS Code Playwright Extension)',
|
|
194
|
+
'// pick up project URLs + Caddy CA without inheriting the parent shell.',
|
|
195
|
+
'// Searches upward from cwd because `.lt-dev/` sits at the repo root',
|
|
196
|
+
'// while playwright.config.ts (and cwd) usually sit in projects/app.',
|
|
197
|
+
"import { existsSync as __ltDevExists, readFileSync as __ltDevRead } from 'node:fs';",
|
|
198
|
+
"import { dirname as __ltDevDirname, resolve as __ltDevResolve } from 'node:path';",
|
|
199
|
+
"let __ltDevEnvFile = '';",
|
|
200
|
+
'for (let __ltDevDir = process.cwd(), __i = 0; __i < 6; __i++) {',
|
|
201
|
+
" const __candidate = __ltDevResolve(__ltDevDir, '.lt-dev/.env');",
|
|
202
|
+
' if (__ltDevExists(__candidate)) { __ltDevEnvFile = __candidate; break; }',
|
|
203
|
+
' const __parent = __ltDevDirname(__ltDevDir);',
|
|
204
|
+
' if (__parent === __ltDevDir) break;',
|
|
205
|
+
' __ltDevDir = __parent;',
|
|
206
|
+
'}',
|
|
207
|
+
'if (__ltDevEnvFile) {',
|
|
208
|
+
' for (const __ln of __ltDevRead(__ltDevEnvFile, "utf8").split(/\\r?\\n/)) {',
|
|
209
|
+
' const __m = __ln.match(/^([A-Z][A-Z0-9_]*)=(.*)$/);',
|
|
210
|
+
' if (__m && process.env[__m[1]] === undefined) process.env[__m[1]] = __m[2];',
|
|
211
|
+
' }',
|
|
212
|
+
'}',
|
|
213
|
+
bridgeEnd,
|
|
214
|
+
].join('\n');
|
|
215
|
+
const bridgeStartIdx = after.indexOf(bridgeStart);
|
|
216
|
+
const bridgeEndIdx = after.indexOf(bridgeEnd);
|
|
217
|
+
if (bridgeStartIdx === -1) {
|
|
218
|
+
after = `${bridgeBlock}\n${after}`;
|
|
184
219
|
count++;
|
|
185
220
|
}
|
|
221
|
+
else if (bridgeEndIdx !== -1) {
|
|
222
|
+
const rebuilt = after.slice(0, bridgeStartIdx) + bridgeBlock + after.slice(bridgeEndIdx + bridgeEnd.length);
|
|
223
|
+
if (rebuilt !== after) {
|
|
224
|
+
after = rebuilt;
|
|
225
|
+
count++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
186
228
|
if (count === 0)
|
|
187
229
|
return { file, patched: false, replacements: 0 };
|
|
188
230
|
(0, fs_1.writeFileSync)(file, after, 'utf8');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.hoistWorkspacePnpmConfig = hoistWorkspacePnpmConfig;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const js_yaml_1 = require("js-yaml");
|
|
4
6
|
/**
|
|
5
7
|
* pnpm workspace-scoped fields that must live at the workspace root.
|
|
6
8
|
* When present in sub-project package.json files, pnpm emits:
|
|
@@ -15,6 +17,16 @@ exports.hoistWorkspacePnpmConfig = hoistWorkspacePnpmConfig;
|
|
|
15
17
|
* and the actual dependency-resolution behavior.
|
|
16
18
|
*/
|
|
17
19
|
const WORKSPACE_SCOPED_PNPM_FIELDS = ['overrides', 'onlyBuiltDependencies', 'ignoredOptionalDependencies'];
|
|
20
|
+
/**
|
|
21
|
+
* pnpm 11 renamed `onlyBuiltDependencies` (string array) to `allowBuilds`
|
|
22
|
+
* (a `{ pkg: boolean }` map). A migrated sub-project pnpm-workspace.yaml
|
|
23
|
+
* usually carries BOTH for cross-version compatibility, but we must not
|
|
24
|
+
* rely on the array twin always being present: we normalise `allowBuilds`
|
|
25
|
+
* back into `onlyBuiltDependencies` before hoisting (see
|
|
26
|
+
* `normalizeAllowBuilds`) so the build-allowlist survives into the pnpm-10
|
|
27
|
+
* monorepo root even when the file only carries the pnpm-11 object form.
|
|
28
|
+
*/
|
|
29
|
+
const PNPM11_BUILD_KEY = 'allowBuilds';
|
|
18
30
|
/**
|
|
19
31
|
* Hoist workspace-scoped pnpm config from sub-projects into the root
|
|
20
32
|
* package.json. After this runs, sub-project package.json files no
|
|
@@ -22,6 +34,27 @@ const WORKSPACE_SCOPED_PNPM_FIELDS = ['overrides', 'onlyBuiltDependencies', 'ign
|
|
|
22
34
|
* `ignoredOptionalDependencies`, and the root package.json contains
|
|
23
35
|
* the merged union.
|
|
24
36
|
*
|
|
37
|
+
* Two sources are read per sub-project, because the two starters store
|
|
38
|
+
* their pnpm config differently:
|
|
39
|
+
*
|
|
40
|
+
* 1. `<sub>/package.json` `pnpm` block — nest-server-starter, and
|
|
41
|
+
* nuxt-base-template before its pnpm-11 migration.
|
|
42
|
+
* 2. `<sub>/pnpm-workspace.yaml` — nuxt-base-template after the
|
|
43
|
+
* migration. pnpm 11 silently ignores the `pnpm` block in
|
|
44
|
+
* package.json, so the template moved overrides into
|
|
45
|
+
* pnpm-workspace.yaml. Inside a monorepo that nested file would
|
|
46
|
+
* (a) not be hoisted by the old package.json-only logic, regressing
|
|
47
|
+
* the CVE overrides, and (b) declare a nested workspace root that
|
|
48
|
+
* conflicts with the monorepo's own pnpm-workspace.yaml. We hoist
|
|
49
|
+
* its fields into the root package.json (the lt-monorepo root pins
|
|
50
|
+
* pnpm@10 via `packageManager`, where package.json#pnpm IS honored)
|
|
51
|
+
* and remove the now-redundant nested file.
|
|
52
|
+
*
|
|
53
|
+
* Symlinked sub-projects are skipped entirely: in `--frontend-link` /
|
|
54
|
+
* `--api-link` mode `projects/app` (or `projects/api`) points at the
|
|
55
|
+
* user's local framework checkout, and stripping its config or deleting
|
|
56
|
+
* its pnpm-workspace.yaml would corrupt that source repo.
|
|
57
|
+
*
|
|
25
58
|
* Idempotent: running twice has the same effect as running once.
|
|
26
59
|
*
|
|
27
60
|
* @param options.filesystem Gluegun filesystem tool
|
|
@@ -38,36 +71,106 @@ function hoistWorkspacePnpmConfig(options) {
|
|
|
38
71
|
if (!rootPkg)
|
|
39
72
|
return;
|
|
40
73
|
(_a = rootPkg.pnpm) !== null && _a !== void 0 ? _a : (rootPkg.pnpm = {});
|
|
74
|
+
const rootPnpm = rootPkg.pnpm;
|
|
41
75
|
let rootChanged = false;
|
|
42
76
|
for (const subDir of subProjects) {
|
|
43
|
-
const
|
|
44
|
-
if (!filesystem.exists(
|
|
77
|
+
const subPath = `${projectDir}/${subDir}`;
|
|
78
|
+
if (!filesystem.exists(subPath))
|
|
45
79
|
continue;
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
// Never mutate a symlinked sub-project — it points at the user's own
|
|
81
|
+
// checkout in link mode.
|
|
82
|
+
if (isSymlink(subPath))
|
|
48
83
|
continue;
|
|
49
|
-
|
|
50
|
-
for (const field of WORKSPACE_SCOPED_PNPM_FIELDS) {
|
|
51
|
-
const subValue = subPkg.pnpm[field];
|
|
52
|
-
if (subValue === undefined)
|
|
53
|
-
continue;
|
|
54
|
-
rootPkg.pnpm[field] = mergePnpmFieldValue(field, rootPkg.pnpm[field], subValue);
|
|
84
|
+
if (hoistFromSubPackageJson({ filesystem, rootPnpm, subPath })) {
|
|
55
85
|
rootChanged = true;
|
|
56
|
-
delete subPkg.pnpm[field];
|
|
57
|
-
subChanged = true;
|
|
58
86
|
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
if (subPkg.pnpm && Object.keys(subPkg.pnpm).length === 0) {
|
|
62
|
-
delete subPkg.pnpm;
|
|
63
|
-
}
|
|
64
|
-
filesystem.write(subPkgPath, `${JSON.stringify(subPkg, null, 2)}\n`);
|
|
87
|
+
if (hoistFromSubWorkspaceYaml({ filesystem, rootPnpm, subPath })) {
|
|
88
|
+
rootChanged = true;
|
|
65
89
|
}
|
|
66
90
|
}
|
|
67
91
|
if (rootChanged) {
|
|
68
92
|
filesystem.write(rootPkgPath, `${JSON.stringify(rootPkg, null, 2)}\n`);
|
|
69
93
|
}
|
|
70
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Move the workspace-scoped pnpm fields from `source` into `rootPnpm`,
|
|
97
|
+
* deleting each moved field from `source`. Returns true if anything moved.
|
|
98
|
+
*/
|
|
99
|
+
function hoistFields(rootPnpm, source) {
|
|
100
|
+
let changed = false;
|
|
101
|
+
for (const field of WORKSPACE_SCOPED_PNPM_FIELDS) {
|
|
102
|
+
if (source[field] === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
rootPnpm[field] = mergePnpmFieldValue(field, rootPnpm[field], source[field]);
|
|
105
|
+
delete source[field];
|
|
106
|
+
changed = true;
|
|
107
|
+
}
|
|
108
|
+
return changed;
|
|
109
|
+
}
|
|
110
|
+
/** Source 1: the sub-project's package.json `pnpm` block. */
|
|
111
|
+
function hoistFromSubPackageJson(options) {
|
|
112
|
+
const { filesystem, rootPnpm, subPath } = options;
|
|
113
|
+
const subPkgPath = `${subPath}/package.json`;
|
|
114
|
+
if (!filesystem.exists(subPkgPath))
|
|
115
|
+
return false;
|
|
116
|
+
const subPkg = filesystem.read(subPkgPath, 'json');
|
|
117
|
+
if (!(subPkg === null || subPkg === void 0 ? void 0 : subPkg.pnpm))
|
|
118
|
+
return false;
|
|
119
|
+
if (!hoistFields(rootPnpm, subPkg.pnpm))
|
|
120
|
+
return false;
|
|
121
|
+
// If the sub-project's pnpm section is now empty, drop it entirely.
|
|
122
|
+
if (Object.keys(subPkg.pnpm).length === 0) {
|
|
123
|
+
delete subPkg.pnpm;
|
|
124
|
+
}
|
|
125
|
+
filesystem.write(subPkgPath, `${JSON.stringify(subPkg, null, 2)}\n`);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/** Source 2: the sub-project's pnpm-workspace.yaml (pnpm-11 layout). */
|
|
129
|
+
function hoistFromSubWorkspaceYaml(options) {
|
|
130
|
+
const { filesystem, rootPnpm, subPath } = options;
|
|
131
|
+
const subWsPath = `${subPath}/pnpm-workspace.yaml`;
|
|
132
|
+
if (!filesystem.exists(subWsPath))
|
|
133
|
+
return false;
|
|
134
|
+
const raw = filesystem.read(subWsPath);
|
|
135
|
+
if (!raw)
|
|
136
|
+
return false;
|
|
137
|
+
let parsed;
|
|
138
|
+
try {
|
|
139
|
+
parsed = (0, js_yaml_1.load)(raw);
|
|
140
|
+
}
|
|
141
|
+
catch (_a) {
|
|
142
|
+
// Malformed YAML — leave it untouched rather than risk data loss.
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
146
|
+
return false;
|
|
147
|
+
const ws = parsed;
|
|
148
|
+
// Fold the pnpm-11 `allowBuilds` map into `onlyBuiltDependencies` so it is
|
|
149
|
+
// hoisted rather than discarded — even when the array twin is absent.
|
|
150
|
+
normalizeAllowBuilds(ws);
|
|
151
|
+
if (!hoistFields(rootPnpm, ws))
|
|
152
|
+
return false;
|
|
153
|
+
// A settings-only file (no `packages:`) exists solely to carry these
|
|
154
|
+
// hoisted keys — once emptied it would only declare a nested workspace
|
|
155
|
+
// root, so remove it. A file that declares `packages:` is a real (rare)
|
|
156
|
+
// nested workspace; keep it minus the hoisted keys.
|
|
157
|
+
if (Array.isArray(ws.packages) && ws.packages.length > 0) {
|
|
158
|
+
filesystem.write(subWsPath, (0, js_yaml_1.dump)(ws));
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
filesystem.remove(subWsPath);
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
/** Whether `path` is a symbolic link (false on any stat error). */
|
|
166
|
+
function isSymlink(path) {
|
|
167
|
+
try {
|
|
168
|
+
return (0, fs_1.lstatSync)(path).isSymbolicLink();
|
|
169
|
+
}
|
|
170
|
+
catch (_a) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
71
174
|
/**
|
|
72
175
|
* Merge two values for a pnpm workspace-scoped field.
|
|
73
176
|
*
|
|
@@ -95,3 +198,26 @@ function mergePnpmFieldValue(field, rootValue, subValue) {
|
|
|
95
198
|
const merged = Object.assign(Object.assign({}, rootObj), subObj);
|
|
96
199
|
return Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)));
|
|
97
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Fold a pnpm-11 `allowBuilds: { pkg: boolean }` map into the pnpm-10
|
|
203
|
+
* `onlyBuiltDependencies: string[]` form (packages whose value is `true`),
|
|
204
|
+
* unioned with any existing array, then remove the `allowBuilds` key so the
|
|
205
|
+
* redundant object form does not linger. Mutates `ws` in place.
|
|
206
|
+
*
|
|
207
|
+
* No-op when `allowBuilds` is absent or not an object map — an unexpected
|
|
208
|
+
* shape is left untouched rather than risk silent data loss.
|
|
209
|
+
*/
|
|
210
|
+
function normalizeAllowBuilds(ws) {
|
|
211
|
+
const raw = ws[PNPM11_BUILD_KEY];
|
|
212
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
213
|
+
return;
|
|
214
|
+
const allowed = Object.entries(raw)
|
|
215
|
+
.filter(([, enabled]) => enabled === true)
|
|
216
|
+
.map(([pkg]) => pkg);
|
|
217
|
+
if (allowed.length > 0) {
|
|
218
|
+
const existing = Array.isArray(ws.onlyBuiltDependencies) ? ws.onlyBuiltDependencies : [];
|
|
219
|
+
// Order here is irrelevant — mergePnpmFieldValue sorts the union on hoist.
|
|
220
|
+
ws.onlyBuiltDependencies = Array.from(new Set([...allowed, ...existing]));
|
|
221
|
+
}
|
|
222
|
+
delete ws[PNPM11_BUILD_KEY];
|
|
223
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setPackageName = setPackageName;
|
|
4
|
+
/**
|
|
5
|
+
* Set the `name` field of a package.json on disk.
|
|
6
|
+
*
|
|
7
|
+
* Used by `lt fullstack init` to rename the cloned monorepo's root package
|
|
8
|
+
* so each project gets a unique `lt dev` slug (the slug is derived from
|
|
9
|
+
* package.json `name`; without a rename every lt-monorepo-based project would
|
|
10
|
+
* register as `lt-monorepo` and collide on `https://lt-monorepo.localhost`).
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: this reads/writes the file as parsed JSON rather than running a
|
|
13
|
+
* string regex through `patching.update`. Gluegun's `patching.update` hands
|
|
14
|
+
* the callback a *parsed object* for any `.json` file, so a String-based
|
|
15
|
+
* `content.replace(...)` callback throws `content.replace is not a function`
|
|
16
|
+
* at runtime. Going through parsed JSON here is both correct and robust: it
|
|
17
|
+
* adds a `name` field if one is missing instead of silently no-op'ing.
|
|
18
|
+
*
|
|
19
|
+
* Idempotent: if the name already equals `name`, the file is left untouched
|
|
20
|
+
* and the function returns false.
|
|
21
|
+
*
|
|
22
|
+
* @param options.filesystem Gluegun filesystem tool
|
|
23
|
+
* @param options.name New value for the `name` field
|
|
24
|
+
* @param options.packageJsonPath Absolute path to the package.json
|
|
25
|
+
* @returns true if the file was written, false otherwise (missing/unreadable/unchanged)
|
|
26
|
+
*/
|
|
27
|
+
function setPackageName(options) {
|
|
28
|
+
const { filesystem, name, packageJsonPath } = options;
|
|
29
|
+
if (!filesystem.exists(packageJsonPath))
|
|
30
|
+
return false;
|
|
31
|
+
const pkg = filesystem.read(packageJsonPath, 'json');
|
|
32
|
+
if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg))
|
|
33
|
+
return false;
|
|
34
|
+
if (pkg.name === name)
|
|
35
|
+
return false;
|
|
36
|
+
pkg.name = name;
|
|
37
|
+
filesystem.write(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
package/docs/commands.md
CHANGED
|
@@ -312,10 +312,13 @@ One-time per-machine setup. Idempotent — re-run anytime to diagnose what's mis
|
|
|
312
312
|
**Usage:**
|
|
313
313
|
```bash
|
|
314
314
|
lt dev install
|
|
315
|
+
lt dev install --skip-init # do NOT auto-run `lt dev init` afterwards
|
|
315
316
|
```
|
|
316
317
|
|
|
317
318
|
**Alias:** `lt d i`
|
|
318
319
|
|
|
320
|
+
**Auto-chaining:** when run from inside an lt-dev-capable project that is not yet initialized, `lt dev install` runs `lt dev init` for that project **afterwards**. Pass `--skip-init` to opt out. This is one hop deep and never recurses — `install` calls the init *helper*, not the init command.
|
|
321
|
+
|
|
319
322
|
**What it does:**
|
|
320
323
|
1. Verifies `caddy` is on PATH (suggests `brew install caddy` if missing).
|
|
321
324
|
2. Creates `~/.lenneTech/Caddyfile` stub if absent.
|
|
@@ -355,16 +358,19 @@ lt dev uninstall --noConfirm # skip the purge prompt (keep files)
|
|
|
355
358
|
|
|
356
359
|
---
|
|
357
360
|
|
|
358
|
-
### `lt dev
|
|
361
|
+
### `lt dev init`
|
|
359
362
|
|
|
360
|
-
|
|
363
|
+
Initialize an existing project for `lt dev` and apply idempotent env-aware patches. Safe to run multiple times; safe to run after `lt fullstack init`.
|
|
361
364
|
|
|
362
365
|
**Usage:**
|
|
363
366
|
```bash
|
|
364
|
-
lt dev
|
|
367
|
+
lt dev init
|
|
368
|
+
lt dev init --skip-install # do NOT auto-run `lt dev install` first
|
|
365
369
|
```
|
|
366
370
|
|
|
367
|
-
**Alias:** `lt d m`
|
|
371
|
+
**Alias:** `lt d init`, `lt d migrate`, `lt d m` (`migrate` is the former name, kept for backwards compatibility)
|
|
372
|
+
|
|
373
|
+
**Auto-chaining:** if the machine has not been prepared yet (no `lt dev install` has run), `lt dev init` runs the install step **first**, then initializes the project. Pass `--skip-install` to opt out. This is one hop deep and never recurses — `init` calls the install *helper*, not the install command.
|
|
368
374
|
|
|
369
375
|
**What it does:**
|
|
370
376
|
1. Detects the workspace layout (monorepo `projects/api`+`projects/app`, or standalone).
|
|
@@ -538,7 +544,7 @@ lt dev test -- --ui spec.ts # everything after `--` is forwarded to playwri
|
|
|
538
544
|
| `LT_DEV_ACTIVE`, `LT_DEV_DB_NAME` | Marker keys for consumers |
|
|
539
545
|
| `NODE_EXTRA_CA_CERTS` | Path to Caddy's root CA cert (auto-detected) |
|
|
540
546
|
|
|
541
|
-
`lt dev
|
|
547
|
+
`lt dev init` injects a tiny `// >>> lt-dev:bridge >>>` block at the top of `playwright.config.ts` that loads this file at config-load time — making Playwright (CLI, IDE, VS Code extension) automatically use the `lt dev` URLs and trust the local CA, without inheriting the parent shell.
|
|
542
548
|
|
|
543
549
|
`lt dev down` removes the bridge file so subsequent runs without `lt dev up` fall back cleanly to the classic `localhost:3000`/`localhost:3001` defaults.
|
|
544
550
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "lenne.Tech CLI: lt",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lenne.Tech",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"test:vendor-init": "bash scripts/test-vendor-init.sh",
|
|
33
33
|
"test:frontend-vendor-init": "bash scripts/test-frontend-vendor-init.sh",
|
|
34
34
|
"test:incremental-fullstack": "bash scripts/test-incremental-fullstack.sh",
|
|
35
|
+
"test:manual": "jest --testMatch '<rootDir>/*.manual.ts' --testTimeout=60000",
|
|
36
|
+
"test:e2e:service": "jest --testMatch '<rootDir>/dev-service-e2e.manual.ts' --testTimeout=60000",
|
|
35
37
|
"format": "prettier --write 'src/**/*.{js,ts,tsx,json}' '!src/templates/**/*'",
|
|
36
38
|
"lint": "eslint './src/**/*.{ts,js,vue}'",
|
|
37
39
|
"lint:fix": "eslint './src/**/*.{ts,js,vue}' --fix",
|
|
@@ -58,9 +60,9 @@
|
|
|
58
60
|
"bin"
|
|
59
61
|
],
|
|
60
62
|
"dependencies": {
|
|
61
|
-
"@aws-sdk/client-s3": "3.
|
|
63
|
+
"@aws-sdk/client-s3": "3.1053.0",
|
|
62
64
|
"@lenne.tech/cli-plugin-helper": "0.0.14",
|
|
63
|
-
"axios": "1.16.
|
|
65
|
+
"axios": "1.16.1",
|
|
64
66
|
"bcrypt": "6.0.0",
|
|
65
67
|
"defuddle": "0.18.1",
|
|
66
68
|
"glob": "13.0.6",
|
|
@@ -70,7 +72,7 @@
|
|
|
70
72
|
"jsdom": "29.1.1",
|
|
71
73
|
"lodash": "4.18.1",
|
|
72
74
|
"open": "11.0.0",
|
|
73
|
-
"playwright-core": "1.
|
|
75
|
+
"playwright-core": "1.60.0",
|
|
74
76
|
"ts-morph": "28.0.0",
|
|
75
77
|
"ts-node": "10.9.2",
|
|
76
78
|
"turndown": "7.2.4",
|
|
@@ -85,7 +87,7 @@
|
|
|
85
87
|
"@types/js-yaml": "4.0.9",
|
|
86
88
|
"@types/jsdom": "28.0.1",
|
|
87
89
|
"@types/lodash": "4.17.24",
|
|
88
|
-
"@types/node": "25.
|
|
90
|
+
"@types/node": "25.9.1",
|
|
89
91
|
"@types/turndown": "5.0.6",
|
|
90
92
|
"ejs": "5.0.2",
|
|
91
93
|
"eslint": "9.39.4",
|
|
@@ -94,18 +96,23 @@
|
|
|
94
96
|
"prettier": "3.8.3",
|
|
95
97
|
"rimraf": "6.1.3",
|
|
96
98
|
"standard-version": "9.5.0",
|
|
97
|
-
"ts-jest": "29.4.
|
|
99
|
+
"ts-jest": "29.4.11"
|
|
98
100
|
},
|
|
99
101
|
"//overrides": {
|
|
100
|
-
"
|
|
102
|
+
"brace-expansion@5.0.2 - 5.0.5": "Security fix: GHSA-jxxr-4gwj-5jf2 (large numeric range defeats max DoS protection) in brace-expansion 5.0.2-5.0.5 - transitive via minimatch under glob, @ts-morph/common, @typescript-eslint/typescript-estree. Remove once those parents resolve minimatch to a brace-expansion >=5.0.6.",
|
|
103
|
+
"semver@*": "Force latest semver 7.x across all sub-deps; gluegun@5.2.2 pins semver@7.7.0 which is stale - remove once gluegun updates its dep."
|
|
101
104
|
},
|
|
102
105
|
"overrides": {
|
|
103
|
-
"
|
|
106
|
+
"brace-expansion@5.0.2 - 5.0.5": "5.0.6",
|
|
107
|
+
"semver@*": "7.8.1"
|
|
104
108
|
},
|
|
105
109
|
"jest": {
|
|
106
110
|
"testEnvironment": "node",
|
|
107
111
|
"rootDir": "__tests__",
|
|
108
112
|
"testTimeout": 60000,
|
|
113
|
+
"testMatch": [
|
|
114
|
+
"<rootDir>/*.test.ts"
|
|
115
|
+
],
|
|
109
116
|
"transform": {
|
|
110
117
|
"^.+\\.tsx?$": [
|
|
111
118
|
"ts-jest",
|