@lenne.tech/cli 1.24.1 → 1.26.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 +80 -13
- 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 +59 -4
- package/build/lib/dev-patches.js +66 -24
- package/build/lib/dev-process.js +39 -1
- package/build/lib/hoist-workspace-pnpm-config.js +144 -18
- package/build/lib/package-name.js +99 -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
|
@@ -40,17 +40,52 @@ const dev_state_1 = require("../../lib/dev-state");
|
|
|
40
40
|
* NSC__MONGOOSE__URI, NSC__BASE_URL, NSC__APP_URL, DATABASE_URL,
|
|
41
41
|
* NUXT_PUBLIC_API_PROXY=false (Caddy makes vite-proxy obsolete).
|
|
42
42
|
*/
|
|
43
|
+
function formatBytes(bytes) {
|
|
44
|
+
if (bytes < 1024)
|
|
45
|
+
return `${bytes}B`;
|
|
46
|
+
if (bytes < 1024 * 1024)
|
|
47
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
48
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
49
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
50
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
|
|
51
|
+
}
|
|
52
|
+
function formatRotationNote(label, archivePath, previousSize) {
|
|
53
|
+
const size = formatBytes(previousSize);
|
|
54
|
+
const huge = previousSize > 100 * 1024 * 1024 ? ' (large — consider fixing noisy warnings)' : '';
|
|
55
|
+
return `Rotated previous ${label} log → ${archivePath} (${size})${huge}`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Print the project's bound URLs (app, api, db) as a small info block.
|
|
59
|
+
*
|
|
60
|
+
* Used in two places so the user always sees the URLs next to the PIDs —
|
|
61
|
+
* once after a successful `up` and once when `up` short-circuits on
|
|
62
|
+
* "Already running". Falls back gracefully when only one of api/app is
|
|
63
|
+
* present (single-side projects).
|
|
64
|
+
*/
|
|
65
|
+
function printProjectUrls(info, options) {
|
|
66
|
+
if (options.appHostname) {
|
|
67
|
+
const arrow = options.appUpstreamPort ? ` → 127.0.0.1:${options.appUpstreamPort}` : '';
|
|
68
|
+
info(` app: https://${options.appHostname}${arrow}`);
|
|
69
|
+
}
|
|
70
|
+
if (options.apiHostname) {
|
|
71
|
+
const arrow = options.apiUpstreamPort ? ` → 127.0.0.1:${options.apiUpstreamPort}` : '';
|
|
72
|
+
info(` api: https://${options.apiHostname}${arrow}`);
|
|
73
|
+
}
|
|
74
|
+
if (options.dbName) {
|
|
75
|
+
info(` db: mongodb://127.0.0.1/${options.dbName}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
43
78
|
const UpCommand = {
|
|
44
79
|
alias: ['u'],
|
|
45
80
|
description: 'Start API + App behind Caddy',
|
|
46
81
|
hidden: false,
|
|
47
82
|
name: 'up',
|
|
48
83
|
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
49
|
-
var _a, _b, _c, _d, _e, _f;
|
|
84
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
50
85
|
const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
|
|
51
86
|
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
52
87
|
if (!layout.apiDir && !layout.appDir) {
|
|
53
|
-
error('No API or App project detected at this path. Run `lt dev
|
|
88
|
+
error('No API or App project detected at this path. Run `lt dev init` first.');
|
|
54
89
|
if (!parameters.options.fromGluegunMenu)
|
|
55
90
|
process.exit(1);
|
|
56
91
|
return 'dev up: not a project';
|
|
@@ -72,7 +107,7 @@ const UpCommand = {
|
|
|
72
107
|
const dbName = (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug);
|
|
73
108
|
// Sanft auto-migrate sichere Operationen (ohne Code-Modifikation):
|
|
74
109
|
// CLAUDE.md-URL-Block einfügen + .gitignore ergänzen.
|
|
75
|
-
// Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev
|
|
110
|
+
// Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev init`.
|
|
76
111
|
{
|
|
77
112
|
const claudeCandidates = [
|
|
78
113
|
(0, path_1.join)(layout.root, 'CLAUDE.md'),
|
|
@@ -98,7 +133,7 @@ const UpCommand = {
|
|
|
98
133
|
if (layout.appDir)
|
|
99
134
|
legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
|
|
100
135
|
if (legacyFiles.length > 0) {
|
|
101
|
-
warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev
|
|
136
|
+
warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev init`:');
|
|
102
137
|
legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
|
|
103
138
|
info(colors.dim(' (Continuing — env-aware files will work; legacy files may bind on 3000/3001 and miss Caddy.)'));
|
|
104
139
|
}
|
|
@@ -110,6 +145,17 @@ const UpCommand = {
|
|
|
110
145
|
const appUp = existingSession.pids.app ? (0, dev_state_1.isPidAlive)(existingSession.pids.app) : false;
|
|
111
146
|
if (apiUp || appUp) {
|
|
112
147
|
warning(`Already running (api pid ${(_a = existingSession.pids.api) !== null && _a !== void 0 ? _a : '-'}, app pid ${(_b = existingSession.pids.app) !== null && _b !== void 0 ? _b : '-'}).`);
|
|
148
|
+
// Surface the bound URLs so the user can copy them out without having
|
|
149
|
+
// to look up `lt dev status` separately. Falls back to the in-process
|
|
150
|
+
// identity/registry data — both sources stay in sync via saveRegistry.
|
|
151
|
+
const existingEntry = (0, dev_state_1.loadRegistry)().projects[identity.slug];
|
|
152
|
+
printProjectUrls(info, {
|
|
153
|
+
apiHostname: (_c = identity.subdomains.api) === null || _c === void 0 ? void 0 : _c.hostname,
|
|
154
|
+
apiUpstreamPort: existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.internalPorts.api,
|
|
155
|
+
appHostname: (_d = identity.subdomains.app) === null || _d === void 0 ? void 0 : _d.hostname,
|
|
156
|
+
appUpstreamPort: existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.internalPorts.app,
|
|
157
|
+
dbName: (_e = existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.dbName) !== null && _e !== void 0 ? _e : (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug),
|
|
158
|
+
});
|
|
113
159
|
info('Run `lt dev down` first.');
|
|
114
160
|
if (!parameters.options.fromGluegunMenu)
|
|
115
161
|
process.exit(1);
|
|
@@ -120,10 +166,10 @@ const UpCommand = {
|
|
|
120
166
|
const reg = (0, dev_state_1.loadRegistry)();
|
|
121
167
|
const entry = reg.projects[identity.slug];
|
|
122
168
|
const taken = (0, dev_state_1.takenInternalPorts)(reg, identity.slug);
|
|
123
|
-
const apiPort = (
|
|
169
|
+
const apiPort = (_f = entry === null || entry === void 0 ? void 0 : entry.internalPorts.api) !== null && _f !== void 0 ? _f : (layout.apiDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
|
|
124
170
|
if (apiPort)
|
|
125
171
|
taken.add(apiPort);
|
|
126
|
-
const appPort = (
|
|
172
|
+
const appPort = (_g = entry === null || entry === void 0 ? void 0 : entry.internalPorts.app) !== null && _g !== void 0 ? _g : (layout.appDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
|
|
127
173
|
// Pre-flight: internal ports free?
|
|
128
174
|
const portsToCheck = [apiPort, appPort].filter((p) => typeof p === 'number');
|
|
129
175
|
const snap = yield (0, dev_process_1.listenSnapshot)(portsToCheck);
|
|
@@ -174,23 +220,32 @@ const UpCommand = {
|
|
|
174
220
|
});
|
|
175
221
|
const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
|
|
176
222
|
const pids = {};
|
|
223
|
+
const rotationNotes = [];
|
|
177
224
|
if (layout.apiDir && (0, fs_1.existsSync)((0, path_1.join)(layout.apiDir, 'package.json')) && apiPort) {
|
|
178
|
-
const
|
|
225
|
+
const apiResult = (0, dev_process_1.spawnDetached)(pnpmBin, ['start'], {
|
|
179
226
|
cwd: layout.apiDir,
|
|
180
227
|
env: devEnv.api.env,
|
|
181
228
|
logFile: (0, path_1.join)(layout.root, '.lt-dev', 'api.log'),
|
|
182
229
|
});
|
|
183
|
-
if (
|
|
184
|
-
pids.api =
|
|
230
|
+
if (apiResult) {
|
|
231
|
+
pids.api = apiResult.pid;
|
|
232
|
+
if (apiResult.rotated.rotated && apiResult.rotated.archivePath !== undefined) {
|
|
233
|
+
rotationNotes.push(formatRotationNote('api', apiResult.rotated.archivePath, (_h = apiResult.rotated.previousSize) !== null && _h !== void 0 ? _h : 0));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
185
236
|
}
|
|
186
237
|
if (layout.appDir && (0, fs_1.existsSync)((0, path_1.join)(layout.appDir, 'package.json')) && appPort) {
|
|
187
|
-
const
|
|
238
|
+
const appResult = (0, dev_process_1.spawnDetached)(pnpmBin, ['dev'], {
|
|
188
239
|
cwd: layout.appDir,
|
|
189
240
|
env: devEnv.app.env,
|
|
190
241
|
logFile: (0, path_1.join)(layout.root, '.lt-dev', 'app.log'),
|
|
191
242
|
});
|
|
192
|
-
if (
|
|
193
|
-
pids.app =
|
|
243
|
+
if (appResult) {
|
|
244
|
+
pids.app = appResult.pid;
|
|
245
|
+
if (appResult.rotated.rotated && appResult.rotated.archivePath !== undefined) {
|
|
246
|
+
rotationNotes.push(formatRotationNote('app', appResult.rotated.archivePath, (_j = appResult.rotated.previousSize) !== null && _j !== void 0 ? _j : 0));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
194
249
|
}
|
|
195
250
|
// Persist.
|
|
196
251
|
const subdomainMap = {};
|
|
@@ -209,8 +264,20 @@ const UpCommand = {
|
|
|
209
264
|
// custom shell scripts) can pick up the URLs without inheriting our shell.
|
|
210
265
|
const bridgePath = (0, dev_env_bridge_1.writeEnvBridge)(layout.root, devEnv, dbName);
|
|
211
266
|
info(colors.dim(`ENV bridge: ${bridgePath}`));
|
|
212
|
-
success(`Started: api pid=${(
|
|
267
|
+
success(`Started: api pid=${(_k = pids.api) !== null && _k !== void 0 ? _k : '-'}, app pid=${(_l = pids.app) !== null && _l !== void 0 ? _l : '-'}`);
|
|
268
|
+
// Echo the bound URLs next to the PIDs as well — the "Starting" block
|
|
269
|
+
// prints them before the spawn, but on a long boot log they scroll out
|
|
270
|
+
// of view, so repeating them here keeps PID + URL visually grouped.
|
|
271
|
+
printProjectUrls(info, {
|
|
272
|
+
apiHostname: (_m = identity.subdomains.api) === null || _m === void 0 ? void 0 : _m.hostname,
|
|
273
|
+
apiUpstreamPort: apiPort,
|
|
274
|
+
appHostname: (_o = identity.subdomains.app) === null || _o === void 0 ? void 0 : _o.hostname,
|
|
275
|
+
appUpstreamPort: appPort,
|
|
276
|
+
dbName,
|
|
277
|
+
});
|
|
213
278
|
info(colors.dim('Logs: <root>/.lt-dev/api.log, <root>/.lt-dev/app.log'));
|
|
279
|
+
for (const note of rotationNotes)
|
|
280
|
+
info(colors.dim(note));
|
|
214
281
|
info(colors.dim('Stop with: lt dev down'));
|
|
215
282
|
// Best-effort: kill orphaned children if neither spawned (unlikely, but tidy).
|
|
216
283
|
if (Object.keys(pids).length === 0) {
|
|
@@ -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
|
+
}
|