@lasso-ai/cli 1.0.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.
Files changed (47) hide show
  1. package/ARCHITECTURE.md +342 -0
  2. package/CHANGELOG.md +102 -0
  3. package/CODE_OF_CONDUCT.md +129 -0
  4. package/CONTRIBUTING.md +122 -0
  5. package/LICENSE +7 -0
  6. package/README.md +548 -0
  7. package/SECURITY.md +65 -0
  8. package/SUPPORT.md +31 -0
  9. package/dist/cli/agent.d.ts +61 -0
  10. package/dist/cli/agent.js +418 -0
  11. package/dist/cli/auth.d.ts +38 -0
  12. package/dist/cli/auth.js +162 -0
  13. package/dist/cli/bridge.d.ts +160 -0
  14. package/dist/cli/bridge.js +450 -0
  15. package/dist/cli/host/certificates.d.ts +12 -0
  16. package/dist/cli/host/certificates.js +52 -0
  17. package/dist/cli/host/client.d.ts +49 -0
  18. package/dist/cli/host/client.js +141 -0
  19. package/dist/cli/host/daemon.d.ts +50 -0
  20. package/dist/cli/host/daemon.js +368 -0
  21. package/dist/cli/host/dns.d.ts +80 -0
  22. package/dist/cli/host/dns.js +255 -0
  23. package/dist/cli/host/install.d.ts +32 -0
  24. package/dist/cli/host/install.js +177 -0
  25. package/dist/cli/host/next-host-entry.d.ts +1 -0
  26. package/dist/cli/host/next-host-entry.js +82 -0
  27. package/dist/cli/host/paths.d.ts +15 -0
  28. package/dist/cli/host/paths.js +55 -0
  29. package/dist/cli/host/registry.d.ts +31 -0
  30. package/dist/cli/host/registry.js +103 -0
  31. package/dist/cli/host/runtime.d.ts +23 -0
  32. package/dist/cli/host/runtime.js +134 -0
  33. package/dist/cli/host/vite-host-entry.d.ts +1 -0
  34. package/dist/cli/host/vite-host-entry.js +59 -0
  35. package/dist/cli/index.d.ts +2 -0
  36. package/dist/cli/index.js +322 -0
  37. package/dist/cli/project.d.ts +56 -0
  38. package/dist/cli/project.js +298 -0
  39. package/dist/cli/server/next.d.ts +6 -0
  40. package/dist/cli/server/next.js +76 -0
  41. package/dist/cli/server/vite.d.ts +1 -0
  42. package/dist/cli/server/vite.js +51 -0
  43. package/dist/cli/utils/framework.d.ts +2 -0
  44. package/dist/cli/utils/framework.js +26 -0
  45. package/dist/overlay.js +20365 -0
  46. package/docs/PUBLISHING.md +63 -0
  47. package/package.json +106 -0
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hostAlive = hostAlive;
7
+ exports.readDaemonPid = readDaemonPid;
8
+ exports.isProcessAlive = isProcessAlive;
9
+ exports.registerWithHost = registerWithHost;
10
+ exports.unregisterFromHost = unregisterFromHost;
11
+ exports.listHostProjects = listHostProjects;
12
+ exports.stopHostProject = stopHostProject;
13
+ exports.restartHost = restartHost;
14
+ const node_fs_1 = __importDefault(require("node:fs"));
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const auth_1 = require("../auth");
17
+ const registry_1 = require("./registry");
18
+ const dns_1 = require("./dns");
19
+ const paths_1 = require("./paths");
20
+ async function hostAlive(port) {
21
+ try {
22
+ const response = await (0, auth_1.fetchWithTimeout)(`http://127.0.0.1:${port}/_host/health`, {}, 2500);
23
+ if (!response.ok)
24
+ return null;
25
+ return (await response.json());
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function readDaemonPid() {
32
+ try {
33
+ const pid = Number(node_fs_1.default.readFileSync(paths_1.PID_FILE, "utf8").trim());
34
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ function isProcessAlive(pid) {
41
+ try {
42
+ process.kill(pid, 0);
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ async function registerWithHost(domain, directory, projectId, port) {
50
+ const health = await hostAlive(port);
51
+ if (health) {
52
+ try {
53
+ const response = await (0, auth_1.fetchWithTimeout)(`http://127.0.0.1:${port}/_host/register`, {
54
+ method: "POST",
55
+ headers: { "content-type": "application/json" },
56
+ body: JSON.stringify({ domain, directory, projectId }),
57
+ });
58
+ const body = (await response.json());
59
+ if (!response.ok || !body.ok)
60
+ return { ok: false, domain, directory, error: body.error || "The host rejected the registration." };
61
+ return { ok: true, domain, directory: node_path_1.default.resolve(directory), replaced: Boolean(body.replaced), viaHost: true };
62
+ }
63
+ catch {
64
+ // fall through to a direct registry write
65
+ }
66
+ }
67
+ try {
68
+ const current = (0, registry_1.loadRegistry)();
69
+ const next = (0, registry_1.registerProject)({ domain, directory, projectId, registry: current });
70
+ (0, registry_1.saveRegistry)(next);
71
+ await (0, dns_1.syncResolverAfterRegister)();
72
+ return { ok: true, domain, directory: node_path_1.default.resolve(directory), replaced: Boolean(current[domain] && current[domain].directory !== node_path_1.default.resolve(directory)) };
73
+ }
74
+ catch (error) {
75
+ return { ok: false, domain, directory, error: error instanceof Error ? error.message : "Could not write the Lasso Host registry." };
76
+ }
77
+ }
78
+ async function unregisterFromHost(domain, port) {
79
+ const health = await hostAlive(port);
80
+ if (health) {
81
+ const response = await (0, auth_1.fetchWithTimeout)(`http://127.0.0.1:${port}/_host/unregister`, {
82
+ method: "POST",
83
+ headers: { "content-type": "application/json" },
84
+ body: JSON.stringify({ domain }),
85
+ });
86
+ const body = (await response.json());
87
+ if (!response.ok || !body.ok)
88
+ return { ok: false, error: body.error || "The host rejected the unregister." };
89
+ return { ok: true, removed: Boolean(body.removed) };
90
+ }
91
+ const had = domain in (0, registry_1.loadRegistry)();
92
+ (0, registry_1.saveRegistry)((0, registry_1.unregisterDomain)((0, registry_1.loadRegistry)(), domain));
93
+ await (0, dns_1.syncResolverAfterRegister)();
94
+ return { ok: true, removed: had };
95
+ }
96
+ async function listHostProjects(port) {
97
+ const health = await hostAlive(port);
98
+ if (health?.projects) {
99
+ const projects = health.projects;
100
+ return {
101
+ health,
102
+ projects: projects.map((p) => ({
103
+ projectId: String(p.projectId || ""),
104
+ directory: String(p.directory || ""),
105
+ registeredAt: "",
106
+ domain: String(p.domain || ""),
107
+ status: p.status === "running" ? "running" : "stopped",
108
+ port: typeof p.port === "number" ? p.port : null,
109
+ pid: typeof p.pid === "number" ? p.pid : null,
110
+ })),
111
+ };
112
+ }
113
+ const registry = (0, registry_1.loadRegistry)();
114
+ return {
115
+ health,
116
+ projects: Object.entries(registry).map(([domain, project]) => ({ ...project, domain, status: "stopped", port: null, pid: null })),
117
+ };
118
+ }
119
+ async function stopHostProject(domain, port) {
120
+ const health = await hostAlive(port);
121
+ if (!health)
122
+ return { ok: false, error: "Lasso Host is not running." };
123
+ const response = await (0, auth_1.fetchWithTimeout)(`http://127.0.0.1:${port}/_host/stop`, {
124
+ method: "POST",
125
+ headers: { "content-type": "application/json" },
126
+ body: JSON.stringify({ domain }),
127
+ });
128
+ const body = (await response.json());
129
+ if (!response.ok || !body.ok)
130
+ return { ok: false, error: body.error || "The host rejected the stop request." };
131
+ return { ok: true };
132
+ }
133
+ async function restartHost(port) {
134
+ const health = await hostAlive(port);
135
+ if (!health)
136
+ return { ok: false, error: "Lasso Host is not running." };
137
+ const response = await (0, auth_1.fetchWithTimeout)(`http://127.0.0.1:${port}/_host/restart`, { method: "POST" });
138
+ if (!response.ok)
139
+ return { ok: false, error: "The host rejected the restart request." };
140
+ return { ok: true };
141
+ }
@@ -0,0 +1,50 @@
1
+ export declare const SERVICE_NAME = "lasso-host";
2
+ export interface HostRuntimeOptions {
3
+ proxyPort: number;
4
+ httpsPort: number;
5
+ dnsPort: number;
6
+ version?: string;
7
+ }
8
+ export declare class LassoHost {
9
+ private readonly opts;
10
+ private readonly server;
11
+ private readonly secureServer;
12
+ private dnsHandle;
13
+ private readonly resolver;
14
+ private readonly registry;
15
+ private readonly running;
16
+ private readonly crashLog;
17
+ private readonly proxies;
18
+ constructor(opts: HostRuntimeOptions);
19
+ private log;
20
+ projectView(domain: string): {
21
+ domain: string;
22
+ directory: string;
23
+ projectId: string;
24
+ status: string;
25
+ port: number | null;
26
+ pid: number | null;
27
+ } | null;
28
+ private projectList;
29
+ private commitRegistry;
30
+ private proxyFor;
31
+ private ensureRunning;
32
+ private routes;
33
+ private health;
34
+ private handleRequest;
35
+ private proxyToProject;
36
+ private handleUpgrade;
37
+ start(): Promise<{
38
+ error?: string;
39
+ }>;
40
+ stop(): Promise<void>;
41
+ resolverStatus(): Promise<{
42
+ installed: boolean;
43
+ detail: string;
44
+ }>;
45
+ }
46
+ export declare function getHost(): LassoHost | null;
47
+ export declare function startHost(opts: HostRuntimeOptions): Promise<{
48
+ error?: string;
49
+ }>;
50
+ export declare function waitForShutdown(): void;
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LassoHost = exports.SERVICE_NAME = void 0;
7
+ exports.getHost = getHost;
8
+ exports.startHost = startHost;
9
+ exports.waitForShutdown = waitForShutdown;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_http_1 = __importDefault(require("node:http"));
12
+ const node_https_1 = __importDefault(require("node:https"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const http_proxy_1 = __importDefault(require("http-proxy"));
15
+ const registry_1 = require("./registry");
16
+ const runtime_1 = require("./runtime");
17
+ const dns_1 = require("./dns");
18
+ const paths_1 = require("./paths");
19
+ const certificates_1 = require("./certificates");
20
+ exports.SERVICE_NAME = "lasso-host";
21
+ function writeJson(res, status, body) {
22
+ res.writeHead(status, { "content-type": "application/json" });
23
+ res.end(JSON.stringify(body));
24
+ }
25
+ function readBody(req) {
26
+ return new Promise((resolve) => {
27
+ const chunks = [];
28
+ req.on("data", (chunk) => chunks.push(chunk));
29
+ req.on("end", () => {
30
+ try {
31
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
32
+ }
33
+ catch {
34
+ resolve({});
35
+ }
36
+ });
37
+ });
38
+ }
39
+ function logline(message) {
40
+ try {
41
+ node_fs_1.default.appendFileSync(paths_1.LOG_FILE, `[${new Date().toISOString()}] ${message}\n`);
42
+ }
43
+ catch {
44
+ // best-effort
45
+ }
46
+ }
47
+ class LassoHost {
48
+ opts;
49
+ server;
50
+ secureServer;
51
+ dnsHandle = null;
52
+ resolver;
53
+ registry;
54
+ running = new Map();
55
+ crashLog = new Map();
56
+ proxies = new Map();
57
+ constructor(opts) {
58
+ this.opts = opts;
59
+ this.server = node_http_1.default.createServer((req, res) => this.handleRequest(req, res));
60
+ this.server.on("upgrade", (req, socket, head) => this.handleUpgrade(req, socket, head));
61
+ this.secureServer = node_https_1.default.createServer({}, (req, res) => this.handleRequest(req, res));
62
+ this.secureServer.on("upgrade", (req, socket, head) => this.handleUpgrade(req, socket, head));
63
+ this.resolver = (0, dns_1.createDomainResolver)();
64
+ this.registry = (0, registry_1.loadRegistry)();
65
+ this.log(`host starting (version ${opts.version || "unknown"})`);
66
+ }
67
+ log(message) {
68
+ logline(message);
69
+ }
70
+ // ---- helpers ------------------------------------------------------
71
+ projectView(domain) {
72
+ const project = this.registry[domain];
73
+ if (!project)
74
+ return null;
75
+ const runtime = this.running.get(domain);
76
+ return {
77
+ domain,
78
+ directory: project.directory,
79
+ projectId: project.projectId,
80
+ status: runtime?.ready ? "running" : "stopped",
81
+ port: runtime?.port ?? null,
82
+ pid: runtime?.pid ?? null,
83
+ };
84
+ }
85
+ projectList() {
86
+ return Object.keys(this.registry).map((domain) => this.projectView(domain));
87
+ }
88
+ commitRegistry(registry) {
89
+ for (const key of Object.keys(this.registry))
90
+ delete this.registry[key];
91
+ Object.assign(this.registry, registry);
92
+ (0, registry_1.saveRegistry)(this.registry);
93
+ }
94
+ proxyFor(port) {
95
+ let proxy = this.proxies.get(port);
96
+ if (!proxy) {
97
+ proxy = http_proxy_1.default.createProxyServer({ target: `http://127.0.0.1:${port}`, ws: true });
98
+ proxy.on("error", () => {
99
+ // forwarded per-request below
100
+ });
101
+ this.proxies.set(port, proxy);
102
+ }
103
+ return proxy;
104
+ }
105
+ async ensureRunning(domain) {
106
+ const existing = this.running.get(domain);
107
+ if (existing)
108
+ return existing;
109
+ const project = this.registry[domain];
110
+ if (!project)
111
+ return null;
112
+ const crashes = (this.crashLog.get(domain) || []).filter((t) => Date.now() - t < 5 * 60_000);
113
+ if (crashes.length >= 3) {
114
+ throw new Error(`${domain} kept crashing on startup; not auto-restarting. Fix the project or run "lasso daemon restart".`);
115
+ }
116
+ this.crashLog.set(domain, crashes);
117
+ const result = await (0, runtime_1.startProjectRuntime)(project.directory);
118
+ if (!result.ok || !result.runtime) {
119
+ this.crashLog.set(domain, [...crashes, Date.now()]);
120
+ throw new Error(result.error || `Could not start ${domain}.`);
121
+ }
122
+ const runtime = result.runtime;
123
+ this.running.set(domain, runtime);
124
+ runtime.child.on("exit", (code, signal) => {
125
+ if (this.running.get(domain) === runtime) {
126
+ this.running.delete(domain);
127
+ this.log(`project ${domain} exited (code=${code} signal=${signal})`);
128
+ }
129
+ });
130
+ return runtime;
131
+ }
132
+ // ---- control surface ---------------------------------------------
133
+ routes() {
134
+ return [
135
+ { route: "GET /_host/health", handler: (_req, res) => this.health(res) },
136
+ { route: "GET /_host/registry", handler: (_req, res) => writeJson(res, 200, { ok: true, projects: this.projectList() }) },
137
+ {
138
+ route: "POST /_host/register",
139
+ handler: async (req, res) => {
140
+ const body = await readBody(req);
141
+ const domain = String(body.domain || "").trim().toLowerCase();
142
+ const directory = String(body.directory || "").trim();
143
+ const projectId = String(body.projectId || "").trim();
144
+ if (!(0, registry_1.validateDomain)(domain))
145
+ return writeJson(res, 400, { ok: false, error: `"${domain}" is not a valid .lasso domain.` });
146
+ if (!directory || !node_fs_1.default.existsSync(directory) || !node_fs_1.default.statSync(directory).isDirectory()) {
147
+ return writeJson(res, 400, { ok: false, error: "The project directory does not exist or is not a directory." });
148
+ }
149
+ for (const [existingDomain, project] of Object.entries(this.registry)) {
150
+ if (node_path_1.default.resolve(project.directory) === node_path_1.default.resolve(directory) && existingDomain !== domain) {
151
+ this.running.get(existingDomain)?.child.kill("SIGTERM");
152
+ this.running.delete(existingDomain);
153
+ }
154
+ }
155
+ const next = (0, registry_1.registerProject)({ domain, directory, projectId, registry: this.registry });
156
+ this.commitRegistry(next);
157
+ await (0, dns_1.syncResolverAfterRegister)();
158
+ this.log(`registered ${domain} → ${directory}`);
159
+ writeJson(res, 200, { ok: true, domain, directory: node_path_1.default.resolve(directory), projects: Object.keys(next).length });
160
+ },
161
+ },
162
+ {
163
+ route: "POST /_host/unregister",
164
+ handler: async (req, res) => {
165
+ const body = await readBody(req);
166
+ const domain = String(body.domain || "").trim().toLowerCase();
167
+ if (!domain)
168
+ return writeJson(res, 400, { ok: false, error: "A domain is required." });
169
+ const had = domain in this.registry;
170
+ this.running.get(domain)?.child.kill("SIGTERM");
171
+ this.running.delete(domain);
172
+ this.commitRegistry((0, registry_1.unregisterDomain)(this.registry, domain));
173
+ await (0, dns_1.syncResolverAfterRegister)();
174
+ this.log(`unregistered ${domain}`);
175
+ writeJson(res, 200, { ok: true, domain, removed: had });
176
+ },
177
+ },
178
+ {
179
+ route: "POST /_host/stop",
180
+ handler: async (req, res) => {
181
+ const body = await readBody(req);
182
+ const domain = String(body.domain || "").trim().toLowerCase();
183
+ const runtime = this.running.get(domain);
184
+ if (!runtime)
185
+ return writeJson(res, 200, { ok: true, domain, status: "stopped" });
186
+ runtime.child.kill("SIGTERM");
187
+ this.running.delete(domain);
188
+ this.log(`stopped ${domain}`);
189
+ writeJson(res, 200, { ok: true, domain, status: "stopped" });
190
+ },
191
+ },
192
+ {
193
+ route: "POST /_host/restart",
194
+ handler: async (_req, res) => {
195
+ const stopped = [];
196
+ for (const [domain, runtime] of this.running) {
197
+ runtime.child.kill("SIGTERM");
198
+ this.running.delete(domain);
199
+ stopped.push(domain);
200
+ this.log(`stopped ${domain} for restart`);
201
+ }
202
+ writeJson(res, 200, { ok: true, stopped });
203
+ },
204
+ },
205
+ ];
206
+ }
207
+ health(res) {
208
+ writeJson(res, 200, {
209
+ ok: true,
210
+ service: exports.SERVICE_NAME,
211
+ version: this.opts.version || "unknown",
212
+ pid: process.pid,
213
+ dnsPort: this.dnsHandle?.port ?? null,
214
+ proxyPort: this.opts.proxyPort,
215
+ httpsPort: this.opts.httpsPort,
216
+ https: (0, certificates_1.hostCertificateStatus)().present,
217
+ running: this.running.size,
218
+ registered: Object.keys(this.registry).length,
219
+ projects: this.projectList(),
220
+ });
221
+ }
222
+ // ---- request handling --------------------------------------------
223
+ handleRequest(req, res) {
224
+ const url = req.url || "/";
225
+ const route = this.routes().find((r) => r.route === `${req.method} ${url}`);
226
+ if (route) {
227
+ void Promise.resolve(route.handler(req, res, {})).catch((error) => {
228
+ this.log(line(error));
229
+ writeJson(res, 500, { ok: false, error: line(error) });
230
+ });
231
+ return;
232
+ }
233
+ const isControl = url.startsWith("/_host/");
234
+ if (isControl) {
235
+ writeJson(res, 404, { ok: false, error: "Unknown Lasso Host control endpoint." });
236
+ return;
237
+ }
238
+ const hostname = (0, paths_1.domainOf)(req.headers.host || "");
239
+ if (!(0, paths_1.isLassoDomain)(hostname)) {
240
+ writeJson(res, 404, { ok: false, error: "Lasso Host only proxies *.lasso domains.", host: req.headers.host });
241
+ return;
242
+ }
243
+ void this.proxyToProject(hostname, req, res);
244
+ }
245
+ async proxyToProject(hostname, req, res) {
246
+ let runtime;
247
+ try {
248
+ const started = await this.ensureRunning(hostname);
249
+ if (!started) {
250
+ writeJson(res, 404, { ok: false, domain: hostname, error: `No Lasso project is registered for ${hostname}. Run "lasso init" in the project first.` });
251
+ return;
252
+ }
253
+ runtime = started;
254
+ }
255
+ catch (error) {
256
+ const message = line(error);
257
+ this.log(`request for ${hostname} failed: ${message}`);
258
+ writeJson(res, 503, { ok: false, domain: hostname, error: message });
259
+ return;
260
+ }
261
+ this.proxyFor(runtime.port).web(req, res, {}, (error) => {
262
+ this.log(`proxy error for ${hostname}: ${line(error)}`);
263
+ if (!res.headersSent)
264
+ writeJson(res, 502, { ok: false, error: "The project's dev server is not responding." });
265
+ else
266
+ res.destroy();
267
+ });
268
+ }
269
+ handleUpgrade(req, socket, head) {
270
+ const hostname = (0, paths_1.domainOf)(req.headers.host || "");
271
+ if (!(0, paths_1.isLassoDomain)(hostname)) {
272
+ socket.write("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
273
+ socket.destroy();
274
+ return;
275
+ }
276
+ void (async () => {
277
+ let runtime;
278
+ try {
279
+ const started = await this.ensureRunning(hostname);
280
+ if (!started)
281
+ throw new Error("not registered");
282
+ runtime = started;
283
+ }
284
+ catch {
285
+ socket.write("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
286
+ socket.destroy();
287
+ return;
288
+ }
289
+ this.proxyFor(runtime.port).ws(req, socket, head, {}, (error) => {
290
+ this.log(`ws proxy error for ${hostname}: ${line(error)}`);
291
+ socket.destroy();
292
+ });
293
+ })();
294
+ }
295
+ // ---- lifecycle ---------------------------------------------------
296
+ async start() {
297
+ try {
298
+ this.dnsHandle = await (0, dns_1.startDnsServer)(this.opts.dnsPort);
299
+ this.log(`dns server listening on 127.0.0.1:${this.opts.dnsPort}`);
300
+ }
301
+ catch (error) {
302
+ this.dnsHandle = null;
303
+ this.log(`dns server failed to start on ${this.opts.dnsPort}: ${line(error)}`);
304
+ }
305
+ const certificate = (0, certificates_1.ensureHostCertificate)();
306
+ if (!certificate.ok)
307
+ this.log(`https certificate unavailable: ${certificate.error}`);
308
+ else {
309
+ this.secureServer.setSecureContext({ key: node_fs_1.default.readFileSync(certificates_1.TLS_KEY_FILE), cert: node_fs_1.default.readFileSync(certificates_1.TLS_CERT_FILE) });
310
+ this.log(`https certificate ready${certificate.trusted ? " (trusted by mkcert)" : " (local trust may be required)"}`);
311
+ }
312
+ return await new Promise((resolve) => {
313
+ this.server.once("error", (error) => {
314
+ resolve({ error: error.code === "EADDRINUSE" ? `Lasso Host is already running on port ${this.opts.proxyPort}.` : line(error) });
315
+ });
316
+ this.server.listen(this.opts.proxyPort, "127.0.0.1", () => {
317
+ node_fs_1.default.writeFileSync(paths_1.PID_FILE, String(process.pid), { encoding: "utf8" });
318
+ this.log(`proxy listening on 127.0.0.1:${this.opts.proxyPort}`);
319
+ if (!certificate.ok)
320
+ return resolve({});
321
+ this.secureServer.once("error", (error) => { this.log(`https proxy failed: ${line(error)}`); resolve({}); });
322
+ this.secureServer.listen(this.opts.httpsPort, "127.0.0.1", () => {
323
+ this.log(`https proxy listening on 127.0.0.1:${this.opts.httpsPort}`);
324
+ resolve({});
325
+ });
326
+ });
327
+ });
328
+ }
329
+ async stop() {
330
+ for (const [, runtime] of this.running)
331
+ runtime.child.kill("SIGTERM");
332
+ this.running.clear();
333
+ for (const proxy of this.proxies.values())
334
+ proxy.close();
335
+ this.proxies.clear();
336
+ this.server.close();
337
+ this.secureServer.close();
338
+ this.dnsHandle?.close();
339
+ try {
340
+ node_fs_1.default.unlinkSync(paths_1.PID_FILE);
341
+ }
342
+ catch {
343
+ // already gone
344
+ }
345
+ }
346
+ resolverStatus() {
347
+ return this.resolver.status();
348
+ }
349
+ }
350
+ exports.LassoHost = LassoHost;
351
+ function line(error) {
352
+ return error instanceof Error ? error.message : String(error);
353
+ }
354
+ let host = null;
355
+ function getHost() {
356
+ return host;
357
+ }
358
+ function startHost(opts) {
359
+ host = new LassoHost(opts);
360
+ return host.start();
361
+ }
362
+ function waitForShutdown() {
363
+ const shutdown = () => {
364
+ void host?.stop().then(() => process.exit(0));
365
+ };
366
+ process.on("SIGTERM", shutdown);
367
+ process.on("SIGINT", shutdown);
368
+ }
@@ -0,0 +1,80 @@
1
+ import { type HostRegistry } from "./registry";
2
+ export declare function buildDnsResponse(query: Buffer): Buffer | null;
3
+ export interface DnsServerHandle {
4
+ close: () => void;
5
+ port: number;
6
+ }
7
+ export declare function startDnsServer(port: number): Promise<DnsServerHandle>;
8
+ export interface DomainResolver {
9
+ platform: string;
10
+ status: () => Promise<{
11
+ installed: boolean;
12
+ detail: string;
13
+ }>;
14
+ install: (dnsPort: number) => Promise<{
15
+ ok: boolean;
16
+ requiresSudo?: boolean;
17
+ message: string;
18
+ }>;
19
+ uninstall: (dnsPort: number) => Promise<{
20
+ ok: boolean;
21
+ message: string;
22
+ }>;
23
+ }
24
+ export declare class MacOSResolver implements DomainResolver {
25
+ readonly platform = "darwin";
26
+ status(): Promise<{
27
+ installed: boolean;
28
+ detail: string;
29
+ }>;
30
+ install(dnsPort: number): Promise<{
31
+ ok: boolean;
32
+ message: string;
33
+ requiresSudo?: undefined;
34
+ } | {
35
+ ok: boolean;
36
+ requiresSudo: boolean;
37
+ message: string;
38
+ }>;
39
+ uninstall(): Promise<{
40
+ ok: boolean;
41
+ message: string;
42
+ }>;
43
+ }
44
+ export declare class LinuxResolver implements DomainResolver {
45
+ readonly platform = "linux";
46
+ private readonly systemdFile;
47
+ status(): Promise<{
48
+ installed: boolean;
49
+ detail: string;
50
+ }>;
51
+ install(dnsPort: number): Promise<{
52
+ ok: boolean;
53
+ requiresSudo: boolean;
54
+ message: string;
55
+ }>;
56
+ uninstall(): Promise<{
57
+ ok: boolean;
58
+ message: string;
59
+ }>;
60
+ }
61
+ export declare class WindowsResolver implements DomainResolver {
62
+ readonly platform = "win32";
63
+ status(): Promise<{
64
+ installed: boolean;
65
+ detail: string;
66
+ }>;
67
+ readHostsBlock(): Promise<string[]>;
68
+ decodeHost(entry: string): string;
69
+ sync(registry: HostRegistry): Promise<boolean>;
70
+ install(): Promise<{
71
+ ok: boolean;
72
+ message: string;
73
+ }>;
74
+ uninstall(): Promise<{
75
+ ok: boolean;
76
+ message: string;
77
+ }>;
78
+ }
79
+ export declare function createDomainResolver(platform?: NodeJS.Platform): DomainResolver;
80
+ export declare function syncResolverAfterRegister(): Promise<boolean>;