@lenne.tech/cli 1.21.0 → 1.23.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.
@@ -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 — run `lt dev install` to (re)start the lt-dev service.');
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;
@@ -0,0 +1,191 @@
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 fs_1 = require("fs");
14
+ const caddy_1 = require("../../lib/caddy");
15
+ const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
16
+ const dev_identity_1 = require("../../lib/dev-identity");
17
+ const dev_project_1 = require("../../lib/dev-project");
18
+ const dev_state_1 = require("../../lib/dev-state");
19
+ /**
20
+ * One-shot E2E convenience wrapper.
21
+ *
22
+ * Ensures the project is up under `lt dev`, then runs the test command
23
+ * with the ENV bridge loaded, and optionally tears the session down at
24
+ * the end. Useful for TDD loops, CI reproduction, and "just test it"
25
+ * workflows where the developer doesn't want to remember three commands.
26
+ *
27
+ * Behaviour:
28
+ * 1. If no `lt dev up` session is alive: run `lt dev up` first.
29
+ * 2. Wait for the App URL to respond (best-effort, ~30s timeout).
30
+ * 3. Inherit the env from `<root>/.lt-dev/.env` (so VS Code IDE-style
31
+ * runners get the same setup) and spawn the test command in the
32
+ * App project.
33
+ * 4. With `--teardown` (default false): run `lt dev down` after.
34
+ *
35
+ * Usage:
36
+ * lt dev test # ensure up + run pnpm test:e2e in app
37
+ * lt dev test --api # run API tests instead (pnpm test in api)
38
+ * lt dev test --teardown # plus stop session after
39
+ * lt dev test --debug # PWDEBUG=1 + headed mode
40
+ * lt dev test -- <args> # forward args to the test runner
41
+ * lt dev test -- --ui crm-login.spec.ts
42
+ */
43
+ const TestCommand = {
44
+ alias: ['t'],
45
+ description: 'Ensure up + run E2E tests with lt dev env',
46
+ hidden: false,
47
+ name: 'test',
48
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
49
+ var _a;
50
+ const { filesystem, parameters, print: { colors, error, info, success }, } = toolbox;
51
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
52
+ if (!layout.apiDir && !layout.appDir) {
53
+ error('No API or App project detected at this path.');
54
+ if (!parameters.options.fromGluegunMenu)
55
+ process.exit(1);
56
+ return 'dev test: not a project';
57
+ }
58
+ const apiMode = Boolean(parameters.options.api);
59
+ const teardown = Boolean(parameters.options.teardown);
60
+ const debug = Boolean(parameters.options.debug);
61
+ // Forwarded args after `--` (gluegun puts them in parameters.array).
62
+ const forwarded = parameters.array || [];
63
+ const targetDir = apiMode ? layout.apiDir : layout.appDir;
64
+ if (!targetDir) {
65
+ error(`Cannot run ${apiMode ? 'API' : 'App'} tests — no ${apiMode ? 'apiDir' : 'appDir'} in layout.`);
66
+ if (!parameters.options.fromGluegunMenu)
67
+ process.exit(1);
68
+ return 'dev test: target missing';
69
+ }
70
+ const identity = (0, dev_identity_1.buildIdentity)(layout.root);
71
+ // Pre-flight: Caddy + ensure up.
72
+ if (!apiMode) {
73
+ if (!(yield (0, caddy_1.caddyAvailable)())) {
74
+ error('caddy is not installed. Run `lt dev install` first.');
75
+ if (!parameters.options.fromGluegunMenu)
76
+ process.exit(1);
77
+ return 'dev test: caddy missing';
78
+ }
79
+ if (!(yield (0, caddy_1.caddyDaemonRunning)())) {
80
+ error('caddy daemon not running. Run `lt dev install` first.');
81
+ if (!parameters.options.fromGluegunMenu)
82
+ process.exit(1);
83
+ return 'dev test: caddy daemon down';
84
+ }
85
+ const reg = (0, dev_state_1.loadRegistry)();
86
+ const entry = reg.projects[identity.slug];
87
+ const session = (0, dev_state_1.loadSession)(layout.root);
88
+ const sessionAlive = session !== null &&
89
+ ((session.pids.api && (0, dev_state_1.isPidAlive)(session.pids.api)) || (session.pids.app && (0, dev_state_1.isPidAlive)(session.pids.app)));
90
+ if (!entry || !sessionAlive) {
91
+ info(colors.dim('Project not running — invoking `lt dev up` first ...'));
92
+ const upResult = yield runChild('lt', ['dev', 'up'], { cwd: layout.root, env: process.env, inherit: true });
93
+ if (upResult !== 0) {
94
+ error(`lt dev up failed (exit ${upResult}).`);
95
+ if (!parameters.options.fromGluegunMenu)
96
+ process.exit(upResult !== null && upResult !== void 0 ? upResult : 1);
97
+ return 'dev test: up failed';
98
+ }
99
+ // Wait for App URL to respond (best-effort).
100
+ const appHost = (_a = identity.subdomains.app) === null || _a === void 0 ? void 0 : _a.hostname;
101
+ if (appHost) {
102
+ info(colors.dim(`Waiting for https://${appHost} to respond ...`));
103
+ yield waitForUrl(`https://${appHost}`, 30000);
104
+ }
105
+ }
106
+ }
107
+ // Build env: process.env + bridge file (bridge wins for keys it defines).
108
+ const env = Object.assign(Object.assign({}, process.env), readBridgeEnv(layout.root));
109
+ if (debug) {
110
+ env.PWDEBUG = '1';
111
+ env.HEADED = '1';
112
+ }
113
+ // Pick the runner.
114
+ const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
115
+ const args = apiMode ? ['run', 'test:e2e', ...forwarded] : ['run', 'test:e2e', ...forwarded];
116
+ info('');
117
+ info(colors.bold(`Running ${apiMode ? 'API' : 'App'} E2E tests for "${identity.slug}"`));
118
+ info(colors.dim(` ${pnpmBin} ${args.join(' ')}`));
119
+ info(colors.dim(` cwd: ${targetDir}`));
120
+ info('');
121
+ const exitCode = yield runChild(pnpmBin, args, { cwd: targetDir, env, inherit: true });
122
+ if (teardown) {
123
+ info('');
124
+ info(colors.dim('Tearing down lt dev session ...'));
125
+ yield runChild('lt', ['dev', 'down'], { cwd: layout.root, env: process.env, inherit: true });
126
+ }
127
+ if (exitCode === 0)
128
+ success('Tests passed.');
129
+ else
130
+ error(`Tests failed (exit ${exitCode}).`);
131
+ if (!parameters.options.fromGluegunMenu)
132
+ process.exit(exitCode !== null && exitCode !== void 0 ? exitCode : 1);
133
+ return `dev test: exit=${exitCode}`;
134
+ }),
135
+ };
136
+ /**
137
+ * Read the .lt-dev/.env bridge file as a key/value map.
138
+ * Returns an empty object if the file is missing.
139
+ */
140
+ function readBridgeEnv(root) {
141
+ const file = (0, dev_env_bridge_1.envBridgePath)(root);
142
+ if (!(0, fs_1.existsSync)(file))
143
+ return {};
144
+ const out = {};
145
+ for (const line of (0, fs_1.readFileSync)(file, 'utf8').split(/\r?\n/)) {
146
+ const m = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/);
147
+ if (m)
148
+ out[m[1]] = m[2];
149
+ }
150
+ return out;
151
+ }
152
+ /** Spawn a child synchronously (waits for exit), inheriting stdio when requested. */
153
+ function runChild(cmd, args, opts) {
154
+ return new Promise((resolve) => {
155
+ const child = (0, child_process_1.spawn)(cmd, args, {
156
+ cwd: opts.cwd,
157
+ env: opts.env,
158
+ stdio: opts.inherit ? 'inherit' : 'pipe',
159
+ });
160
+ child.on('error', () => resolve(1));
161
+ child.on('close', (code) => resolve(code));
162
+ });
163
+ }
164
+ /** Poll a URL until it responds or timeout elapses. */
165
+ function waitForUrl(url, timeoutMs) {
166
+ const start = Date.now();
167
+ return new Promise((resolve) => {
168
+ const tick = () => {
169
+ var _a;
170
+ const child = (0, child_process_1.spawn)('curl', ['-sk', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', '2', url], {
171
+ stdio: ['ignore', 'pipe', 'ignore'],
172
+ });
173
+ let status = '';
174
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (status += String(b)));
175
+ child.on('close', () => {
176
+ if (/^[1-5]\d\d$/.test(status.trim()))
177
+ return resolve(true);
178
+ if (Date.now() - start > timeoutMs)
179
+ return resolve(false);
180
+ setTimeout(tick, 500);
181
+ });
182
+ child.on('error', () => {
183
+ if (Date.now() - start > timeoutMs)
184
+ return resolve(false);
185
+ setTimeout(tick, 500);
186
+ });
187
+ };
188
+ tick();
189
+ });
190
+ }
191
+ module.exports = TestCommand;
@@ -0,0 +1,142 @@
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 cloudflared_1 = require("../../lib/cloudflared");
14
+ const dev_identity_1 = require("../../lib/dev-identity");
15
+ const dev_project_1 = require("../../lib/dev-project");
16
+ const dev_state_1 = require("../../lib/dev-state");
17
+ /**
18
+ * Expose a running `lt dev up` project to the public internet via a
19
+ * Cloudflare Quick Tunnel.
20
+ *
21
+ * Quick tunnel = no Cloudflare account, ephemeral `*.trycloudflare.com`
22
+ * URL, runs in the foreground until Ctrl-C. Designed for ad-hoc work:
23
+ * - mobile / tablet preview from outside the LAN
24
+ * - sharing a feature with a teammate during review
25
+ * - landing webhooks from external services
26
+ *
27
+ * Caveats (printed at runtime):
28
+ * - Auth cookies set on `<slug>.localhost` are NOT valid on the
29
+ * `*.trycloudflare.com` domain — log in again on the public URL.
30
+ * - The default tunnel exposes ONLY the App subdomain (the API stays
31
+ * on `*.localhost`). Use `--api` to tunnel the API instead, or
32
+ * start a second `lt dev tunnel --api` in parallel.
33
+ * - Better-Auth's `trustedOrigins` won't include the random tunnel
34
+ * URL — login flows that validate the origin will reject the
35
+ * request unless you add the URL explicitly to your API config.
36
+ *
37
+ * Not yet supported (deliberate, separate command if needed later):
38
+ * - named tunnels with persistent URL (`cloudflared tunnel create`)
39
+ * - parallel multi-host tunnels in one process
40
+ * - background/detached mode (use a separate shell for now)
41
+ */
42
+ const TunnelCommand = {
43
+ alias: ['tun'],
44
+ description: 'Cloudflare quick-tunnel to a running app',
45
+ hidden: false,
46
+ name: 'tunnel',
47
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
48
+ var _a, _b;
49
+ const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
50
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
51
+ if (!layout.apiDir && !layout.appDir) {
52
+ error('No API or App project detected at this path. Run `lt dev migrate` first.');
53
+ if (!parameters.options.fromGluegunMenu)
54
+ process.exit(1);
55
+ return 'dev tunnel: not a project';
56
+ }
57
+ const apiMode = Boolean(parameters.options.api);
58
+ const identity = (0, dev_identity_1.buildIdentity)(layout.root);
59
+ const targetSub = apiMode ? identity.subdomains.api : identity.subdomains.app;
60
+ if (!targetSub) {
61
+ error(`No ${apiMode ? 'API' : 'App'} subdomain configured for "${identity.slug}".`);
62
+ if (!parameters.options.fromGluegunMenu)
63
+ process.exit(1);
64
+ return 'dev tunnel: no target';
65
+ }
66
+ // Pre-flight: cloudflared installed
67
+ const available = yield (0, cloudflared_1.cloudflaredAvailable)();
68
+ if (!available.installed) {
69
+ error('cloudflared is not installed.');
70
+ info(` → ${colors.cyan('brew install cloudflared')} (macOS)`);
71
+ info(` → ${colors.cyan('https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/')} (Linux/Windows)`);
72
+ if (!parameters.options.fromGluegunMenu)
73
+ process.exit(1);
74
+ return 'dev tunnel: cloudflared missing';
75
+ }
76
+ // Pre-flight: Caddy must be up — without it cloudflared would forward
77
+ // to a dead upstream and the public URL would 502.
78
+ if (!(yield (0, caddy_1.caddyDaemonRunning)())) {
79
+ error('Caddy daemon is not running — run `lt dev install` first.');
80
+ if (!parameters.options.fromGluegunMenu)
81
+ process.exit(1);
82
+ return 'dev tunnel: caddy down';
83
+ }
84
+ const registry = (0, dev_state_1.loadRegistry)();
85
+ if (!registry.projects[identity.slug]) {
86
+ warning(`Project "${identity.slug}" is not in the lt-dev registry. Run \`lt dev up\` first.`);
87
+ info(' (Continuing anyway — Caddy may have a stale block.)');
88
+ }
89
+ const upstreamUrl = `https://${targetSub.hostname}`;
90
+ info('');
91
+ info(colors.bold(`lt dev tunnel — Cloudflare Quick Tunnel`));
92
+ info(colors.dim('─'.repeat(60)));
93
+ info(` Upstream: ${colors.cyan(upstreamUrl)}`);
94
+ info(colors.dim(` cloudflared ${available.version || 'unknown'}`));
95
+ info('');
96
+ info(colors.dim('Starting tunnel — this typically takes 5-10 seconds ...'));
97
+ const tunnel = (0, cloudflared_1.spawnQuickTunnel)({ hostHeader: targetSub.hostname, upstreamUrl });
98
+ // Wire stderr through so users see cloudflared progress / errors.
99
+ (_a = tunnel.child.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => {
100
+ process.stderr.write(String(chunk));
101
+ });
102
+ let publicUrl;
103
+ try {
104
+ publicUrl = yield tunnel.publicUrl;
105
+ }
106
+ catch (err) {
107
+ error(`Tunnel failed to start: ${err.message}`);
108
+ if (!parameters.options.fromGluegunMenu)
109
+ process.exit(1);
110
+ return 'dev tunnel: failed';
111
+ }
112
+ info('');
113
+ success(`Public URL: ${colors.cyan(publicUrl)}`);
114
+ info('');
115
+ info(colors.bold('Heads up:'));
116
+ info(' • Auth cookies set on the localhost domain are NOT valid on the tunnel URL.');
117
+ info(` • Better-Auth's trustedOrigins must include ${colors.cyan(publicUrl)} for login to succeed.`);
118
+ if (!apiMode) {
119
+ info(` • This tunnel exposes ONLY the App. The API stays on \`${((_b = identity.subdomains.api) === null || _b === void 0 ? void 0 : _b.hostname) || '—'}\`.`);
120
+ info(' For full external usage, start a second `lt dev tunnel --api` in another shell.');
121
+ }
122
+ info('');
123
+ info(colors.dim('Stop with Ctrl-C.'));
124
+ // Keep the foreground alive until the child exits (Ctrl-C → SIGINT
125
+ // is forwarded to the child by the terminal, child exits, we exit).
126
+ const exitCode = yield new Promise((resolve) => {
127
+ tunnel.child.on('close', resolve);
128
+ const forward = (sig) => {
129
+ if (!tunnel.child.killed)
130
+ tunnel.child.kill(sig);
131
+ };
132
+ process.on('SIGINT', forward);
133
+ process.on('SIGTERM', forward);
134
+ });
135
+ info('');
136
+ info(colors.dim(`cloudflared exited (code ${exitCode !== null && exitCode !== void 0 ? exitCode : 'unknown'}).`));
137
+ if (!parameters.options.fromGluegunMenu)
138
+ process.exit(exitCode !== null && exitCode !== void 0 ? exitCode : 0);
139
+ return `dev tunnel: exit=${exitCode}`;
140
+ }),
141
+ };
142
+ module.exports = TunnelCommand;
@@ -0,0 +1,92 @@
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 fs_1 = require("fs");
13
+ const path_1 = require("path");
14
+ const caddy_1 = require("../../lib/caddy");
15
+ const dev_service_1 = require("../../lib/dev-service");
16
+ /**
17
+ * Symmetric counterpart to `lt dev install`.
18
+ *
19
+ * Removes the LaunchAgent / systemd-user unit, stops the running Caddy
20
+ * daemon, and optionally purges all lt-dev state (Caddyfile, logs).
21
+ *
22
+ * What it does NOT touch:
23
+ * - the caddy binary itself (`brew uninstall caddy` remains the
24
+ * user's choice — we don't presume they want to drop the tool)
25
+ * - per-project state under `<project>/.lt-dev/` (use `lt dev down`)
26
+ * - the trusted CA in the system keychain (use
27
+ * `sudo -E HOME="$HOME" caddy untrust` if desired)
28
+ *
29
+ * Flags:
30
+ * --purge — also remove ~/.lenneTech/Caddyfile and caddy logs
31
+ * --noConfirm — skip the purge confirmation prompt
32
+ */
33
+ const UninstallCommand = {
34
+ alias: ['un'],
35
+ description: 'Remove lt dev Caddy service',
36
+ hidden: false,
37
+ name: 'uninstall',
38
+ run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
39
+ const { parameters, print: { colors, error, info, success, warning }, prompt: { confirm }, } = toolbox;
40
+ info('');
41
+ info(colors.bold('lt dev uninstall — remove the lt-dev Caddy service'));
42
+ info(colors.dim('─'.repeat(60)));
43
+ const plat = (0, dev_service_1.platformSupported)();
44
+ if (plat === 'unsupported') {
45
+ info('No managed service to remove on this platform.');
46
+ if (!parameters.options.fromGluegunMenu)
47
+ process.exit(0);
48
+ return 'dev uninstall: nothing to do';
49
+ }
50
+ const paths = (0, dev_service_1.getServicePaths)();
51
+ const result = yield (0, dev_service_1.uninstallService)();
52
+ if (!result.ok) {
53
+ error(result.message);
54
+ if (!parameters.options.fromGluegunMenu)
55
+ process.exit(1);
56
+ return 'dev uninstall: failed';
57
+ }
58
+ if (result.removed.length === 0) {
59
+ info(colors.dim('Service was not installed.'));
60
+ }
61
+ else {
62
+ success(`Removed: ${result.removed.join(', ')}`);
63
+ }
64
+ // Optional purge of related state files.
65
+ const purge = Boolean(parameters.options.purge) ||
66
+ (!parameters.options.noConfirm && (yield confirm('Also remove the Caddyfile and lt-dev caddy logs?', false)));
67
+ if (purge) {
68
+ const toRemove = [caddy_1.paths.caddyfile, paths.logFile, paths.errFile, (0, path_1.join)(paths.logFile, '..', 'ports.json')];
69
+ for (const file of toRemove) {
70
+ if ((0, fs_1.existsSync)(file)) {
71
+ try {
72
+ (0, fs_1.unlinkSync)(file);
73
+ success(`Removed ${file}`);
74
+ }
75
+ catch (e) {
76
+ warning(`Failed to remove ${file}: ${e.message}`);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ else {
82
+ info(colors.dim(`Kept: ${caddy_1.paths.caddyfile}`));
83
+ info(colors.dim(`Kept: ${paths.logFile}`));
84
+ }
85
+ info('');
86
+ info(`To reinstall: ${colors.cyan('lt dev install')}. To also remove caddy itself: ${colors.cyan('brew uninstall caddy')}.`);
87
+ if (!parameters.options.fromGluegunMenu)
88
+ process.exit(0);
89
+ return 'dev uninstall: ok';
90
+ }),
91
+ };
92
+ module.exports = UninstallCommand;