@lenne.tech/cli 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/build/commands/dev/dev.js +32 -0
- package/build/commands/dev/doctor.js +110 -0
- package/build/commands/dev/down.js +76 -0
- package/build/commands/dev/install.js +113 -0
- package/build/commands/dev/migrate.js +96 -0
- package/build/commands/dev/status.js +153 -0
- package/build/commands/dev/test.js +191 -0
- package/build/commands/dev/up.js +228 -0
- package/build/commands/fullstack/init.js +36 -2
- package/build/commands/status.js +37 -6
- package/build/extensions/server.js +14 -4
- package/build/lib/caddy.js +169 -0
- package/build/lib/dev-env-bridge.js +109 -0
- package/build/lib/dev-env.js +35 -0
- package/build/lib/dev-identity.js +100 -0
- package/build/lib/dev-migrate-helper.js +81 -0
- package/build/lib/dev-patches.js +190 -0
- package/build/lib/dev-process.js +152 -0
- package/build/lib/dev-project.js +86 -0
- package/build/lib/dev-state.js +152 -0
- package/docs/commands.md +202 -0
- package/package.json +7 -10
package/README.md
CHANGED
|
@@ -57,6 +57,31 @@ $ lt status
|
|
|
57
57
|
$ lt completion install
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Local dev orchestration (parallel projects, HTTPS URLs)
|
|
61
|
+
|
|
62
|
+
Run multiple lt projects on the same machine without port collisions or
|
|
63
|
+
cross-wiring. Each project gets stable HTTPS URLs (`<slug>.localhost`,
|
|
64
|
+
`api.<slug>.localhost`) served by Caddy, and the matching env vars
|
|
65
|
+
(`BASE_URL`, `APP_URL`, `NUXT_PUBLIC_*`, `NSC__MONGOOSE__URI`, …) are
|
|
66
|
+
injected into the spawned dev servers automatically.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Once per machine:
|
|
70
|
+
$ lt dev install # verify Caddy, prep Caddyfile + local CA
|
|
71
|
+
|
|
72
|
+
# Once per project (idempotent):
|
|
73
|
+
$ lt dev migrate # patch legacy hardcoded ports + register
|
|
74
|
+
|
|
75
|
+
# Daily:
|
|
76
|
+
$ lt dev up # start API + App behind Caddy
|
|
77
|
+
$ lt dev status # current project status
|
|
78
|
+
$ lt dev status --all # all registered projects
|
|
79
|
+
$ lt dev down # SIGTERM the process group + remove Caddy block
|
|
80
|
+
$ lt dev doctor # diagnose Caddy / CA / DNS / port issues
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Full reference: [docs/commands.md → Local Development Commands](docs/commands.md#local-development-commands).
|
|
84
|
+
|
|
60
85
|
## Framework consumption modes (nest-server)
|
|
61
86
|
|
|
62
87
|
When you create a new api project (`lt fullstack init` or `lt server create`),
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
/**
|
|
13
|
+
* Local development orchestration commands (`lt dev <subcommand>`).
|
|
14
|
+
*
|
|
15
|
+
* Subcommands:
|
|
16
|
+
* - `install` — one-time per-machine setup (Caddy + local CA + LaunchAgent)
|
|
17
|
+
* - `migrate` — register an existing project (idempotent ENV patches)
|
|
18
|
+
* - `up` — start API + App behind Caddy with project-specific URLs
|
|
19
|
+
* - `down` — stop the detached processes + remove the Caddy block
|
|
20
|
+
* - `status` — show what is running
|
|
21
|
+
* - `doctor` — diagnose Caddy/CA/DNS/port issues
|
|
22
|
+
*/
|
|
23
|
+
module.exports = {
|
|
24
|
+
alias: ['d'],
|
|
25
|
+
description: 'Local dev orchestration (Caddy + per-project URLs)',
|
|
26
|
+
hidden: false,
|
|
27
|
+
name: 'dev',
|
|
28
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
yield toolbox.helper.showMenu('dev');
|
|
30
|
+
return 'dev';
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const child_process_1 = require("child_process");
|
|
13
|
+
const caddy_1 = require("../../lib/caddy");
|
|
14
|
+
const dev_process_1 = require("../../lib/dev-process");
|
|
15
|
+
const dev_state_1 = require("../../lib/dev-state");
|
|
16
|
+
/**
|
|
17
|
+
* Diagnose Caddy / CA / DNS / port issues for `lt dev`.
|
|
18
|
+
*
|
|
19
|
+
* Categorical output (OK / WARN / FAIL) so developers can quickly see
|
|
20
|
+
* what is missing on a fresh machine. Exit code 0 = all green,
|
|
21
|
+
* 1 = at least one FAIL.
|
|
22
|
+
*/
|
|
23
|
+
const DoctorCommand = {
|
|
24
|
+
alias: ['doc'],
|
|
25
|
+
description: 'Diagnose Caddy/CA/DNS/port issues',
|
|
26
|
+
hidden: false,
|
|
27
|
+
name: 'doctor',
|
|
28
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
const { parameters, print: { colors, info }, } = toolbox;
|
|
30
|
+
info('');
|
|
31
|
+
info(colors.bold('lt dev doctor'));
|
|
32
|
+
info(colors.dim('─'.repeat(60)));
|
|
33
|
+
let fails = 0;
|
|
34
|
+
// 1. Caddy installed
|
|
35
|
+
const hasCaddy = yield (0, caddy_1.caddyAvailable)();
|
|
36
|
+
if (hasCaddy)
|
|
37
|
+
line('OK', colors.green, 'caddy on PATH');
|
|
38
|
+
else {
|
|
39
|
+
line('FAIL', colors.red, 'caddy not installed — run `brew install caddy`');
|
|
40
|
+
fails++;
|
|
41
|
+
}
|
|
42
|
+
// 2. Caddy daemon
|
|
43
|
+
if (hasCaddy) {
|
|
44
|
+
const daemon = yield (0, caddy_1.caddyDaemonRunning)();
|
|
45
|
+
if (daemon)
|
|
46
|
+
line('OK', colors.green, 'caddy daemon running (admin :2019 reachable)');
|
|
47
|
+
else {
|
|
48
|
+
line('FAIL', colors.red, 'caddy daemon not running — `brew services start caddy`');
|
|
49
|
+
fails++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 3. Caddyfile validates
|
|
53
|
+
if (hasCaddy) {
|
|
54
|
+
const v = yield (0, caddy_1.validateCaddyfile)();
|
|
55
|
+
if (v.ok)
|
|
56
|
+
line('OK', colors.green, `Caddyfile valid (${caddy_1.paths.caddyfile})`);
|
|
57
|
+
else
|
|
58
|
+
line('WARN', colors.yellow, `Caddyfile validation: ${v.stderr.split('\n')[0]}`);
|
|
59
|
+
}
|
|
60
|
+
// 4. Port 80 / 443 free or held by Caddy
|
|
61
|
+
for (const port of [80, 443]) {
|
|
62
|
+
const r = yield (0, dev_process_1.checkPortInUse)(port);
|
|
63
|
+
if (r === null)
|
|
64
|
+
line('WARN', colors.yellow, `lsof unavailable — cannot probe port ${port}`);
|
|
65
|
+
else if (!r.inUse)
|
|
66
|
+
line('OK', colors.green, `port ${port} free`);
|
|
67
|
+
else if (r.command === 'caddy')
|
|
68
|
+
line('OK', colors.green, `port ${port} held by caddy (pid ${r.pid})`);
|
|
69
|
+
else {
|
|
70
|
+
line('FAIL', colors.red, `port ${port} held by ${r.command} (pid ${r.pid}) — Caddy cannot bind`);
|
|
71
|
+
fails++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 5. *.localhost resolves to 127.0.0.1
|
|
75
|
+
const dnsOk = yield dnsResolvesLocalhost('lt-dev-doctor.localhost');
|
|
76
|
+
if (dnsOk)
|
|
77
|
+
line('OK', colors.green, '*.localhost resolves to 127.0.0.1 (RFC 6761)');
|
|
78
|
+
else
|
|
79
|
+
line('WARN', colors.yellow, '*.localhost may not resolve — check /etc/hosts or system resolver');
|
|
80
|
+
// 6. Registry
|
|
81
|
+
const reg = (0, dev_state_1.loadRegistry)();
|
|
82
|
+
const count = Object.keys(reg.projects).length;
|
|
83
|
+
line('OK', colors.green, `registry: ${count} project(s) at ${dev_state_1.paths.registry}`);
|
|
84
|
+
info('');
|
|
85
|
+
if (fails > 0)
|
|
86
|
+
info(colors.red(`✗ ${fails} fail(s) — see above`));
|
|
87
|
+
else
|
|
88
|
+
info(colors.green('✓ all checks passed'));
|
|
89
|
+
if (!parameters.options.fromGluegunMenu)
|
|
90
|
+
process.exit(fails > 0 ? 1 : 0);
|
|
91
|
+
return fails > 0 ? `dev doctor: ${fails} fails` : 'dev doctor: ok';
|
|
92
|
+
function line(tag, color, msg) {
|
|
93
|
+
info(` ${color(`[${tag.padEnd(4)}]`)} ${msg}`);
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
/** Probe DNS — RFC 6761 says *.localhost MUST resolve to loopback. */
|
|
98
|
+
function dnsResolvesLocalhost(host) {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
const child = (0, child_process_1.spawn)('node', [
|
|
101
|
+
'-e',
|
|
102
|
+
`require('dns').lookup(${JSON.stringify(host)}, (e, a) => { process.exit(e || a !== '127.0.0.1' ? 1 : 0); })`,
|
|
103
|
+
], {
|
|
104
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
105
|
+
});
|
|
106
|
+
child.on('error', () => resolve(false));
|
|
107
|
+
child.on('close', (code) => resolve(code === 0));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
module.exports = DoctorCommand;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const caddy_1 = require("../../lib/caddy");
|
|
13
|
+
const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
|
|
14
|
+
const dev_identity_1 = require("../../lib/dev-identity");
|
|
15
|
+
const dev_process_1 = require("../../lib/dev-process");
|
|
16
|
+
const dev_project_1 = require("../../lib/dev-project");
|
|
17
|
+
const dev_state_1 = require("../../lib/dev-state");
|
|
18
|
+
/**
|
|
19
|
+
* Stop the processes started by `lt dev up` and remove the project's
|
|
20
|
+
* Caddy block.
|
|
21
|
+
*
|
|
22
|
+
* - SIGTERM is sent to the detached process GROUP (negative PID) so
|
|
23
|
+
* children (Vite, Nest watcher) receive the signal too.
|
|
24
|
+
* - The Caddy block is removed and `caddy reload` is invoked, so the
|
|
25
|
+
* subdomain stops resolving immediately.
|
|
26
|
+
*/
|
|
27
|
+
const DownCommand = {
|
|
28
|
+
alias: ['d'],
|
|
29
|
+
description: 'Stop API + App and remove Caddy block',
|
|
30
|
+
hidden: false,
|
|
31
|
+
name: 'down',
|
|
32
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
|
+
const { filesystem, parameters, print: { colors, info, success, warning }, } = toolbox;
|
|
34
|
+
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
35
|
+
const identity = (0, dev_identity_1.buildIdentity)(layout.root);
|
|
36
|
+
const session = (0, dev_state_1.loadSession)(layout.root);
|
|
37
|
+
const stopped = [];
|
|
38
|
+
if (session) {
|
|
39
|
+
for (const [name, pid] of Object.entries(session.pids)) {
|
|
40
|
+
if (!pid)
|
|
41
|
+
continue;
|
|
42
|
+
if (!(0, dev_state_1.isPidAlive)(pid)) {
|
|
43
|
+
stopped.push(`${name} (pid ${pid}, already dead)`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if ((0, dev_process_1.killProcessGroup)(pid))
|
|
47
|
+
stopped.push(`${name} (pid ${pid})`);
|
|
48
|
+
else
|
|
49
|
+
warning(`Failed to stop ${name} (pid ${pid})`);
|
|
50
|
+
}
|
|
51
|
+
(0, dev_state_1.clearSession)(layout.root);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
info(colors.dim('No running processes registered for this project.'));
|
|
55
|
+
}
|
|
56
|
+
// Remove Caddy block (best-effort — even if no session was active).
|
|
57
|
+
const removed = (0, caddy_1.removeProjectBlock)(identity.slug);
|
|
58
|
+
if (removed) {
|
|
59
|
+
const r = yield (0, caddy_1.reloadCaddy)();
|
|
60
|
+
if (r.ok)
|
|
61
|
+
success(`Removed Caddy block for "${identity.slug}".`);
|
|
62
|
+
else
|
|
63
|
+
warning(`Removed Caddy block but reload failed: ${r.stderr.split('\n')[0]}`);
|
|
64
|
+
}
|
|
65
|
+
// Clear ENV bridge so subsequent test runs without `lt dev up`
|
|
66
|
+
// do not pick up stale URLs.
|
|
67
|
+
if ((0, dev_env_bridge_1.clearEnvBridge)(layout.root))
|
|
68
|
+
info(colors.dim('Removed .lt-dev/.env bridge.'));
|
|
69
|
+
if (stopped.length > 0)
|
|
70
|
+
success(`Stopped: ${stopped.join(', ')}`);
|
|
71
|
+
if (!parameters.options.fromGluegunMenu)
|
|
72
|
+
process.exit();
|
|
73
|
+
return `dev down: ${stopped.length} stopped`;
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
module.exports = DownCommand;
|
|
@@ -0,0 +1,113 @@
|
|
|
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.runShell = runShell;
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const caddy_1 = require("../../lib/caddy");
|
|
15
|
+
/**
|
|
16
|
+
* One-time per-machine setup for `lt dev`.
|
|
17
|
+
*
|
|
18
|
+
* Idempotent — re-running diagnoses the current state and only acts on
|
|
19
|
+
* what is missing.
|
|
20
|
+
*
|
|
21
|
+
* Steps:
|
|
22
|
+
* 1. Ensure `caddy` is on PATH (suggest `brew install caddy` if missing)
|
|
23
|
+
* 2. Ensure `~/.lenneTech/Caddyfile` exists (empty stub if missing)
|
|
24
|
+
* 3. Ensure Caddy is running as a background service (suggest
|
|
25
|
+
* `brew services start caddy`)
|
|
26
|
+
* 4. Trust the local Caddy CA (`caddy trust` — needs sudo once)
|
|
27
|
+
*/
|
|
28
|
+
const InstallCommand = {
|
|
29
|
+
alias: ['i'],
|
|
30
|
+
description: 'One-time setup: install + start Caddy, trust local CA',
|
|
31
|
+
hidden: false,
|
|
32
|
+
name: 'install',
|
|
33
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
const { parameters, print: { colors, error, info, success, warning }, } = toolbox;
|
|
35
|
+
info('');
|
|
36
|
+
info(colors.bold('lt dev install — one-time per-machine setup'));
|
|
37
|
+
info(colors.dim('─'.repeat(60)));
|
|
38
|
+
let blocked = false;
|
|
39
|
+
// 1. Caddy on PATH
|
|
40
|
+
const hasCaddy = yield (0, caddy_1.caddyAvailable)();
|
|
41
|
+
if (hasCaddy) {
|
|
42
|
+
success('caddy is on PATH');
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
warning('caddy is not installed.');
|
|
46
|
+
info(` → Install with: ${colors.cyan('brew install caddy')} (macOS)`);
|
|
47
|
+
info(` → Linux: ${colors.cyan('https://caddyserver.com/docs/install')}`);
|
|
48
|
+
blocked = true;
|
|
49
|
+
}
|
|
50
|
+
// 2. Caddyfile stub
|
|
51
|
+
(0, caddy_1.writeCaddyfile)('# lt dev — managed Caddyfile\n# Add per-project blocks via `lt dev up`.\n');
|
|
52
|
+
success(`Caddyfile present at ${caddy_1.paths.caddyfile}`);
|
|
53
|
+
if (!hasCaddy) {
|
|
54
|
+
error('Cannot continue setup until Caddy is installed.');
|
|
55
|
+
if (!parameters.options.fromGluegunMenu)
|
|
56
|
+
process.exit(blocked ? 1 : 0);
|
|
57
|
+
return 'dev install: caddy missing';
|
|
58
|
+
}
|
|
59
|
+
// 3. Caddy daemon
|
|
60
|
+
const daemon = yield (0, caddy_1.caddyDaemonRunning)();
|
|
61
|
+
if (daemon) {
|
|
62
|
+
success('caddy daemon is running');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
warning('caddy daemon is not running.');
|
|
66
|
+
info(` → Start as a service (macOS): ${colors.cyan('brew services start caddy')}`);
|
|
67
|
+
info(` → Start manually: ${colors.cyan(`caddy run --config ${caddy_1.paths.caddyfile}`)}`);
|
|
68
|
+
blocked = true;
|
|
69
|
+
}
|
|
70
|
+
// 4. Validate Caddyfile (catches config issues early)
|
|
71
|
+
const validation = yield (0, caddy_1.validateCaddyfile)();
|
|
72
|
+
if (validation.ok) {
|
|
73
|
+
success('Caddyfile validates');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
warning('Caddyfile validation reported issues:');
|
|
77
|
+
info(colors.dim(validation.stderr.split('\n').slice(0, 3).join('\n')));
|
|
78
|
+
}
|
|
79
|
+
// 5. CA trust
|
|
80
|
+
info('');
|
|
81
|
+
info(colors.bold('Local CA'));
|
|
82
|
+
info(' Caddy installs a local CA on first run. To trust it system-wide,');
|
|
83
|
+
info(' execute (one-time, requires sudo):');
|
|
84
|
+
info(` ${colors.cyan('sudo caddy trust')}`);
|
|
85
|
+
info(` Browsers will then accept ${colors.cyan('https://*.localhost')} without warnings.`);
|
|
86
|
+
info('');
|
|
87
|
+
if (blocked) {
|
|
88
|
+
warning('Setup incomplete. Address the items above and re-run `lt dev install`.');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
success('Setup complete. Use `lt dev migrate` in a project, then `lt dev up`.');
|
|
92
|
+
}
|
|
93
|
+
if (!parameters.options.fromGluegunMenu)
|
|
94
|
+
process.exit(blocked ? 1 : 0);
|
|
95
|
+
return blocked ? 'dev install: incomplete' : 'dev install: ok';
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
// Helper: spawn a command and wait for it (used by tests / future
|
|
99
|
+
// auto-install. Currently unused but exported for symmetry with doctor).
|
|
100
|
+
function runShell(cmd, args) {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
const child = (0, child_process_1.spawn)(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
104
|
+
let stdout = '';
|
|
105
|
+
let stderr = '';
|
|
106
|
+
let errored = false;
|
|
107
|
+
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (stdout += String(b)));
|
|
108
|
+
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (b) => (stderr += String(b)));
|
|
109
|
+
child.on('error', () => (errored = true));
|
|
110
|
+
child.on('close', (code) => resolve({ ok: !errored && code === 0, stderr, stdout }));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
module.exports = InstallCommand;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const caddy_1 = require("../../lib/caddy");
|
|
13
|
+
const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
|
|
14
|
+
const dev_project_1 = require("../../lib/dev-project");
|
|
15
|
+
/**
|
|
16
|
+
* Register an existing project with `lt dev` and apply idempotent
|
|
17
|
+
* env-aware patches.
|
|
18
|
+
*
|
|
19
|
+
* Idempotent — re-running with no changes is a no-op. Safe to invoke
|
|
20
|
+
* automatically after `lt fullstack init` or by developers manually.
|
|
21
|
+
*
|
|
22
|
+
* Steps (delegated to `lib/dev-migrate-helper.ts#runMigrate`):
|
|
23
|
+
* 1. Resolve workspace layout (api/app dirs, root)
|
|
24
|
+
* 2. Build identity (slug + subdomains)
|
|
25
|
+
* 3. Patch hardcoded ports → env-aware fallbacks (config.env.ts,
|
|
26
|
+
* nuxt.config.ts, playwright.config.ts)
|
|
27
|
+
* 4. Inject CLAUDE.md URL block (root + each subproject)
|
|
28
|
+
* 5. Persist project to ~/.lenneTech/projects.json
|
|
29
|
+
* 6. Add `.lt-dev/` to .gitignore
|
|
30
|
+
*
|
|
31
|
+
* Closes with a hint to run `lt dev install` if Caddy is missing.
|
|
32
|
+
*/
|
|
33
|
+
const MigrateCommand = {
|
|
34
|
+
alias: ['m'],
|
|
35
|
+
description: 'Migrate an existing project to lt dev (idempotent)',
|
|
36
|
+
hidden: false,
|
|
37
|
+
name: 'migrate',
|
|
38
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
39
|
+
const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
|
|
40
|
+
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
41
|
+
if (!layout.apiDir && !layout.appDir) {
|
|
42
|
+
error('No API (src/config.env.ts) or App (nuxt.config.ts) project detected at this path.');
|
|
43
|
+
if (!parameters.options.fromGluegunMenu)
|
|
44
|
+
process.exit(1);
|
|
45
|
+
return 'dev migrate: not a project';
|
|
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)'));
|
|
68
|
+
}
|
|
69
|
+
// CLAUDE.md
|
|
70
|
+
result.claudePatches.filter((r) => r.patched).forEach((r) => success(`updated CLAUDE.md URL block: ${r.file}`));
|
|
71
|
+
// Registry
|
|
72
|
+
if (result.registryUpdated) {
|
|
73
|
+
success(`registered in ${process.env.LT_DEV_REGISTRY_PATH || '~/.lenneTech/projects.json'}`);
|
|
74
|
+
}
|
|
75
|
+
// .gitignore
|
|
76
|
+
if (result.addedGitignoreEntry)
|
|
77
|
+
success('added `.lt-dev/` to .gitignore');
|
|
78
|
+
if (result.alreadyMigrated) {
|
|
79
|
+
info(colors.dim(' Project was already fully migrated — nothing changed.'));
|
|
80
|
+
}
|
|
81
|
+
info('');
|
|
82
|
+
success('Migration complete.');
|
|
83
|
+
// Hint: Caddy installed?
|
|
84
|
+
const caddyOk = yield (0, caddy_1.caddyAvailable)();
|
|
85
|
+
if (!caddyOk) {
|
|
86
|
+
warning('Caddy is not installed yet. Run `lt dev install` first, then `lt dev up`.');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
info('Start the project with `lt dev up`.');
|
|
90
|
+
}
|
|
91
|
+
if (!parameters.options.fromGluegunMenu)
|
|
92
|
+
process.exit();
|
|
93
|
+
return `dev migrate: ${result.identity.slug}`;
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
module.exports = MigrateCommand;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const caddy_1 = require("../../lib/caddy");
|
|
13
|
+
const dev_identity_1 = require("../../lib/dev-identity");
|
|
14
|
+
const dev_process_1 = require("../../lib/dev-process");
|
|
15
|
+
const dev_project_1 = require("../../lib/dev-project");
|
|
16
|
+
const dev_state_1 = require("../../lib/dev-state");
|
|
17
|
+
/**
|
|
18
|
+
* Show what is running.
|
|
19
|
+
*
|
|
20
|
+
* Default: status for the current project (PIDs + URLs + DB).
|
|
21
|
+
* `--all`: list every project in the central registry with health checks.
|
|
22
|
+
*/
|
|
23
|
+
const StatusCommand = {
|
|
24
|
+
alias: ['s'],
|
|
25
|
+
description: 'Show lt dev status',
|
|
26
|
+
hidden: false,
|
|
27
|
+
name: 'status',
|
|
28
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
const { filesystem, parameters, print: { colors, info, warning }, } = toolbox;
|
|
31
|
+
const all = Boolean(parameters.options.all);
|
|
32
|
+
const reg = (0, dev_state_1.loadRegistry)();
|
|
33
|
+
if (all) {
|
|
34
|
+
info('');
|
|
35
|
+
info(colors.bold('All registered lt dev projects'));
|
|
36
|
+
info(colors.dim('─'.repeat(60)));
|
|
37
|
+
const slugs = Object.keys(reg.projects).sort();
|
|
38
|
+
if (slugs.length === 0) {
|
|
39
|
+
warning('No projects registered. Run `lt dev migrate` in a project.');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
for (const slug of slugs) {
|
|
43
|
+
const e = reg.projects[slug];
|
|
44
|
+
const session = (0, dev_state_1.loadSession)(e.path);
|
|
45
|
+
const apiAlive = (session === null || session === void 0 ? void 0 : session.pids.api) ? (0, dev_state_1.isPidAlive)(session.pids.api) : false;
|
|
46
|
+
const appAlive = (session === null || session === void 0 ? void 0 : session.pids.app) ? (0, dev_state_1.isPidAlive)(session.pids.app) : false;
|
|
47
|
+
const status = apiAlive || appAlive ? colors.green('●') : colors.dim('○');
|
|
48
|
+
info(` ${status} ${slug.padEnd(30)} ${colors.dim(e.path)}`);
|
|
49
|
+
for (const [sub, host] of Object.entries(e.subdomains))
|
|
50
|
+
info(` ${sub.padEnd(6)} https://${host}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
info('');
|
|
54
|
+
if (!parameters.options.fromGluegunMenu)
|
|
55
|
+
process.exit();
|
|
56
|
+
return 'dev status: all';
|
|
57
|
+
}
|
|
58
|
+
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
59
|
+
const identity = (0, dev_identity_1.buildIdentity)(layout.root);
|
|
60
|
+
const entry = reg.projects[identity.slug];
|
|
61
|
+
info('');
|
|
62
|
+
info(colors.bold(`lt dev status: ${identity.slug}`));
|
|
63
|
+
info(colors.dim('─'.repeat(60)));
|
|
64
|
+
if (!entry) {
|
|
65
|
+
warning('Not registered. Run `lt dev migrate` first.');
|
|
66
|
+
// Show what migrate would do (legacy code present?) so the user
|
|
67
|
+
// can judge urgency before running it.
|
|
68
|
+
const legacyFiles = [];
|
|
69
|
+
if (layout.apiDir) {
|
|
70
|
+
const f = (0, dev_project_1.apiNeedsPortPatch)(layout.apiDir);
|
|
71
|
+
if (f)
|
|
72
|
+
legacyFiles.push(f);
|
|
73
|
+
}
|
|
74
|
+
if (layout.appDir)
|
|
75
|
+
legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
|
|
76
|
+
if (legacyFiles.length > 0) {
|
|
77
|
+
info(colors.dim(' Legacy hardcoded ports detected — `lt dev migrate` will patch:'));
|
|
78
|
+
legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
info(colors.dim(' Code is already env-aware; `lt dev migrate` will only register + patch CLAUDE.md.'));
|
|
82
|
+
}
|
|
83
|
+
info('');
|
|
84
|
+
if (!parameters.options.fromGluegunMenu)
|
|
85
|
+
process.exit();
|
|
86
|
+
return 'dev status: not registered';
|
|
87
|
+
}
|
|
88
|
+
for (const [sub, host] of Object.entries(entry.subdomains)) {
|
|
89
|
+
const port = sub === 'api' ? entry.internalPorts.api : entry.internalPorts.app;
|
|
90
|
+
info(` ${sub.padEnd(6)} https://${host}${port ? colors.dim(` → 127.0.0.1:${port}`) : ''}`);
|
|
91
|
+
}
|
|
92
|
+
if (entry.dbName)
|
|
93
|
+
info(` db mongodb://127.0.0.1/${entry.dbName}`);
|
|
94
|
+
// Patch status — quick view whether legacy ports are still in source.
|
|
95
|
+
const legacyFiles = [];
|
|
96
|
+
if (layout.apiDir) {
|
|
97
|
+
const f = (0, dev_project_1.apiNeedsPortPatch)(layout.apiDir);
|
|
98
|
+
if (f)
|
|
99
|
+
legacyFiles.push(f);
|
|
100
|
+
}
|
|
101
|
+
if (layout.appDir)
|
|
102
|
+
legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
|
|
103
|
+
if (legacyFiles.length > 0) {
|
|
104
|
+
info('');
|
|
105
|
+
warning(' Legacy hardcoded ports still present:');
|
|
106
|
+
legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
|
|
107
|
+
info(colors.dim(' Run `lt dev migrate` to patch them; otherwise Caddy may proxy into the void.'));
|
|
108
|
+
}
|
|
109
|
+
// Caddy status — quick view whether the daemon is reachable.
|
|
110
|
+
{
|
|
111
|
+
const caddyOk = yield (0, caddy_1.caddyAvailable)();
|
|
112
|
+
const daemonOk = caddyOk ? yield (0, caddy_1.caddyDaemonRunning)() : false;
|
|
113
|
+
info('');
|
|
114
|
+
if (!caddyOk) {
|
|
115
|
+
warning(' Caddy not installed — run `lt dev install` first.');
|
|
116
|
+
}
|
|
117
|
+
else if (!daemonOk) {
|
|
118
|
+
warning(' Caddy daemon not running — `brew services start caddy`.');
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
info(colors.dim(' Caddy: ready'));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
info('');
|
|
125
|
+
const session = (0, dev_state_1.loadSession)(layout.root);
|
|
126
|
+
if (!session) {
|
|
127
|
+
info(colors.dim(' no `lt dev up` session active'));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const apiAlive = session.pids.api ? (0, dev_state_1.isPidAlive)(session.pids.api) : false;
|
|
131
|
+
const appAlive = session.pids.app ? (0, dev_state_1.isPidAlive)(session.pids.app) : false;
|
|
132
|
+
info(` api: ${apiAlive ? colors.green('running') : colors.red('dead')} (pid ${(_a = session.pids.api) !== null && _a !== void 0 ? _a : '-'})`);
|
|
133
|
+
info(` app: ${appAlive ? colors.green('running') : colors.red('dead')} (pid ${(_b = session.pids.app) !== null && _b !== void 0 ? _b : '-'})`);
|
|
134
|
+
info(colors.dim(` started: ${session.startedAt}`));
|
|
135
|
+
// Live port snapshot
|
|
136
|
+
const ports = [entry.internalPorts.api, entry.internalPorts.app].filter((p) => typeof p === 'number');
|
|
137
|
+
if (ports.length > 0) {
|
|
138
|
+
info('');
|
|
139
|
+
info(colors.bold(' Live upstream state'));
|
|
140
|
+
const snap = yield (0, dev_process_1.listenSnapshot)(ports);
|
|
141
|
+
for (const p of ports) {
|
|
142
|
+
const r = snap.get(p);
|
|
143
|
+
info(` ${p}: ${r ? colors.green(`bound to ${r.command} (pid ${r.pid})`) : colors.dim('free')}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
info('');
|
|
148
|
+
if (!parameters.options.fromGluegunMenu)
|
|
149
|
+
process.exit();
|
|
150
|
+
return `dev status: ${identity.slug}`;
|
|
151
|
+
}),
|
|
152
|
+
};
|
|
153
|
+
module.exports = StatusCommand;
|