@dbx-tools/cli 0.9.54 → 0.9.60
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 +548 -58
- package/bin/dbx-graphiti.ts +5 -0
- package/bin/dbx-lakebase-proxy.ts +4 -0
- package/bin/dbx-model-gateway.ts +5 -0
- package/index.ts +35 -1
- package/lib/bin/dbx-graphiti.d.ts +2 -0
- package/lib/bin/dbx-graphiti.js +4 -0
- package/lib/bin/dbx-lakebase-proxy.d.ts +2 -0
- package/lib/bin/dbx-lakebase-proxy.js +4 -0
- package/lib/bin/dbx-model-gateway.d.ts +2 -0
- package/lib/bin/dbx-model-gateway.js +4 -0
- package/lib/index.d.ts +35 -1
- package/lib/index.js +26 -2
- package/lib/src/appkit/cli.d.ts +24 -0
- package/lib/src/appkit/cli.js +54 -0
- package/lib/src/appkit/env-export.d.ts +40 -0
- package/lib/src/appkit/env-export.js +99 -0
- package/lib/src/auth/cli.d.ts +20 -0
- package/lib/src/auth/cli.js +118 -0
- package/lib/src/auth/options.d.ts +41 -0
- package/lib/src/auth/options.js +64 -0
- package/lib/src/cli.d.ts +2 -0
- package/lib/src/cli.js +13 -7
- package/lib/src/graphiti/cli.d.ts +18 -0
- package/lib/src/graphiti/cli.js +43 -0
- package/lib/src/graphiti/options.d.ts +10 -0
- package/lib/src/graphiti/options.js +10 -0
- package/lib/src/lakebase-proxy/cancellation.d.ts +22 -0
- package/lib/src/lakebase-proxy/cancellation.js +100 -0
- package/lib/src/lakebase-proxy/cli.d.ts +13 -0
- package/lib/src/lakebase-proxy/cli.js +54 -0
- package/lib/src/lakebase-proxy/options.d.ts +23 -0
- package/lib/src/lakebase-proxy/options.js +41 -0
- package/lib/src/lakebase-proxy/protocol.d.ts +37 -0
- package/lib/src/lakebase-proxy/protocol.js +124 -0
- package/lib/src/lakebase-proxy/proxy.d.ts +22 -0
- package/lib/src/lakebase-proxy/proxy.js +200 -0
- package/lib/src/model-gateway/cli.d.ts +19 -0
- package/lib/src/model-gateway/cli.js +63 -0
- package/lib/src/model-gateway/server.d.ts +13 -0
- package/lib/src/model-gateway/server.js +53 -0
- package/lib/src/tunnel/app.d.ts +20 -0
- package/lib/src/tunnel/app.js +52 -0
- package/lib/src/tunnel/cli.d.ts +27 -0
- package/lib/src/tunnel/cli.js +190 -0
- package/lib/src/tunnel/options.d.ts +72 -0
- package/lib/src/tunnel/options.js +138 -0
- package/lib/src/tunnel/proxy.d.ts +51 -0
- package/lib/src/tunnel/proxy.js +143 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +124 -9
- package/src/appkit/cli.ts +72 -0
- package/src/appkit/env-export.ts +124 -0
- package/src/auth/cli.ts +165 -0
- package/src/auth/options.ts +79 -0
- package/src/cli.ts +22 -6
- package/src/graphiti/cli.ts +75 -0
- package/src/graphiti/options.ts +20 -0
- package/src/lakebase-proxy/cancellation.ts +116 -0
- package/src/lakebase-proxy/cli.ts +80 -0
- package/src/lakebase-proxy/options.ts +56 -0
- package/src/lakebase-proxy/protocol.ts +170 -0
- package/src/lakebase-proxy/proxy.ts +256 -0
- package/src/model-gateway/cli.ts +89 -0
- package/src/model-gateway/server.ts +62 -0
- package/src/tunnel/app.ts +58 -0
- package/src/tunnel/cli.ts +218 -0
- package/src/tunnel/options.ts +169 -0
- package/src/tunnel/proxy.ts +194 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reverse proxy that makes the wrapper path possible.
|
|
3
|
+
*
|
|
4
|
+
* The wrapper claims the PUBLIC port and the wrapped app runs as a child process
|
|
5
|
+
* on a private loopback port, so - unlike the in-process plugin - there is no
|
|
6
|
+
* middleware chain to insert the gate into. This proxy is that insertion point: it
|
|
7
|
+
* answers the login routes itself, applies the gate to everything else, and
|
|
8
|
+
* forwards what survives to the child.
|
|
9
|
+
*
|
|
10
|
+
* The gating DECISION is not reimplemented here. `@dbx-tools/tunnel`'s
|
|
11
|
+
* `gate.gateRequest` makes it - the same function the Express middleware calls -
|
|
12
|
+
* and this module only differs in how the outcome is written: a proxied request
|
|
13
|
+
* instead of `next()`, a `writeHead` instead of `res.json`. That is deliberate:
|
|
14
|
+
* two independent implementations of "which requests are gated and which headers
|
|
15
|
+
* are stripped" is the one way this package could become a security bug.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { createServer } from "node:http";
|
|
20
|
+
import { log } from "@dbx-tools/shared-core";
|
|
21
|
+
import { gate as tunnelGate, headers as tunnelHeaders, loginPage as tunnelLoginPage, } from "@dbx-tools/tunnel";
|
|
22
|
+
import ProxyModule from "http-proxy-3";
|
|
23
|
+
const logger = log.logger("tunnel:proxy");
|
|
24
|
+
function sendJson(response, status, body, setCookie) {
|
|
25
|
+
response.writeHead(status, {
|
|
26
|
+
"content-type": "application/json",
|
|
27
|
+
...(setCookie ? { "set-cookie": setCookie } : {}),
|
|
28
|
+
});
|
|
29
|
+
response.end(JSON.stringify(body));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Hand every Better Auth and compatibility route to the shared gate runtime.
|
|
33
|
+
*/
|
|
34
|
+
async function handleAuthRoute(request, response, path, gate, brandName) {
|
|
35
|
+
if (!tunnelGate.isAuthPath(path))
|
|
36
|
+
return false;
|
|
37
|
+
if (tunnelGate.wantsHostedLogin(request)) {
|
|
38
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
39
|
+
response.end(tunnelLoginPage.loginPageHtml({
|
|
40
|
+
brandName,
|
|
41
|
+
returnTo: tunnelGate.loginReturnTo(request),
|
|
42
|
+
}));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
const result = await gate.handler(tunnelGate.webRequest(request));
|
|
46
|
+
await tunnelGate.sendWebResponse(response, result);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Start the proxy and resolve once it is listening.
|
|
51
|
+
*
|
|
52
|
+
* `publicDomain` is intentionally NOT passed to `gateRequest`: on this path every
|
|
53
|
+
* request arrived on the public port, so it IS tunnel traffic by construction -
|
|
54
|
+
* whereas the in-process gate shares a port with the platform front door and has
|
|
55
|
+
* to tell them apart by `Host`. Passing the request's own host keeps the shared
|
|
56
|
+
* decision function's contract satisfied without weakening it.
|
|
57
|
+
*/
|
|
58
|
+
export async function startProxy(options) {
|
|
59
|
+
const proxy = ProxyModule.createProxyServer({
|
|
60
|
+
target: `http://127.0.0.1:${options.appPort}`,
|
|
61
|
+
ws: true,
|
|
62
|
+
xfwd: true,
|
|
63
|
+
});
|
|
64
|
+
const headerPolicy = tunnelHeaders.toHeaderPolicy(options.forwardHeaders);
|
|
65
|
+
const gate = options.gate;
|
|
66
|
+
const decide = (request) => gate
|
|
67
|
+
? tunnelGate.gateRequest(request, {
|
|
68
|
+
gate,
|
|
69
|
+
publicDomain: (request.headers.host ?? "").split(":")[0] || "localhost",
|
|
70
|
+
headerPolicy,
|
|
71
|
+
gatePaths: options.gatePaths,
|
|
72
|
+
})
|
|
73
|
+
: Promise.resolve("pass");
|
|
74
|
+
const onRequest = (request, response) => {
|
|
75
|
+
void (async () => {
|
|
76
|
+
const path = (request.url ?? "/").split("?")[0] ?? "/";
|
|
77
|
+
const brandName = options.brandName ?? "this app";
|
|
78
|
+
if (gate && (await handleAuthRoute(request, response, path, gate, brandName)))
|
|
79
|
+
return;
|
|
80
|
+
if ((await decide(request)) === "deny") {
|
|
81
|
+
// A denied browser navigation gets the hosted login page; anything else
|
|
82
|
+
// (an XHR, a /ws upgrade probe, a non-HTML fetch) gets the 401 JSON.
|
|
83
|
+
if (gate && tunnelGate.wantsLoginPage(request)) {
|
|
84
|
+
const html = tunnelLoginPage.loginPageHtml({
|
|
85
|
+
brandName,
|
|
86
|
+
returnTo: tunnelGate.requestReturnTo(request),
|
|
87
|
+
});
|
|
88
|
+
response.writeHead(401, { "content-type": "text/html; charset=utf-8" });
|
|
89
|
+
response.end(html);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
sendJson(response, 401, tunnelGate.unauthorizedBody(request));
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
proxy.web(request, response);
|
|
97
|
+
})().catch((error) => {
|
|
98
|
+
logger.error("proxy request failed", { error });
|
|
99
|
+
if (!response.headersSent)
|
|
100
|
+
sendJson(response, 502, { error: "bad gateway" });
|
|
101
|
+
else
|
|
102
|
+
response.end();
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
// A websocket upgrade cannot be answered with a 401 body, so a denied one is
|
|
106
|
+
// destroyed - the client sees the handshake fail, which is what a browser's
|
|
107
|
+
// WebSocket error handler expects.
|
|
108
|
+
const onUpgrade = (request, socket, head) => {
|
|
109
|
+
const upgradeSocket = socket;
|
|
110
|
+
upgradeSocket.destroySoon ??= () => upgradeSocket.end();
|
|
111
|
+
const path = (request.url ?? "/").split("?", 1)[0] ?? "/";
|
|
112
|
+
if (tunnelGate.isAuthPath(path)) {
|
|
113
|
+
upgradeSocket.destroy();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
void decide(request)
|
|
117
|
+
.then((action) => {
|
|
118
|
+
if (action === "deny")
|
|
119
|
+
upgradeSocket.destroy();
|
|
120
|
+
else
|
|
121
|
+
proxy.ws(request, upgradeSocket, head);
|
|
122
|
+
})
|
|
123
|
+
.catch(() => upgradeSocket.destroy());
|
|
124
|
+
};
|
|
125
|
+
// One net.Server binds one address, so listen each host on its own server that
|
|
126
|
+
// shares the same handlers. Binding specific interface IPs (rather than the
|
|
127
|
+
// default 0.0.0.0) is what lets the gate front an overlay/LAN address while
|
|
128
|
+
// loopback reaches the upstream ungated.
|
|
129
|
+
const hosts = options.bindHosts?.length ? options.bindHosts : ["0.0.0.0"];
|
|
130
|
+
await Promise.all(hosts.map((host) => new Promise((resolve) => {
|
|
131
|
+
const server = createServer(onRequest);
|
|
132
|
+
server.on("upgrade", onUpgrade);
|
|
133
|
+
server.listen(options.publicPort, host, () => {
|
|
134
|
+
logger.info("proxy listening", {
|
|
135
|
+
host,
|
|
136
|
+
publicPort: options.publicPort,
|
|
137
|
+
appPort: options.appPort,
|
|
138
|
+
});
|
|
139
|
+
resolve();
|
|
140
|
+
});
|
|
141
|
+
})));
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvdHVubmVsL3Byb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUVILE9BQU8sRUFBRSxZQUFZLEVBQTZDLE1BQU0sV0FBVyxDQUFDO0FBRXBGLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUM3QyxPQUFPLEVBQ0wsSUFBSSxJQUFJLFVBQVUsRUFDbEIsT0FBTyxJQUFJLGFBQWEsRUFDeEIsU0FBUyxJQUFJLGVBQWUsR0FFN0IsTUFBTSxtQkFBbUIsQ0FBQztBQUMzQixPQUFPLFdBQVcsTUFBTSxjQUFjLENBQUM7QUFFdkMsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsQ0FBQztBQTJCMUMsU0FBUyxRQUFRLENBQ2YsUUFBd0IsRUFDeEIsTUFBYyxFQUNkLElBQWEsRUFDYixTQUFrQjtJQUVsQixRQUFRLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRTtRQUN6QixjQUFjLEVBQUUsa0JBQWtCO1FBQ2xDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7S0FDbEQsQ0FBQyxDQUFDO0lBQ0gsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDckMsQ0FBQztBQUVEOztHQUVHO0FBQ0gsS0FBSyxVQUFVLGVBQWUsQ0FDNUIsT0FBd0IsRUFDeEIsUUFBd0IsRUFDeEIsSUFBWSxFQUNaLElBQWlCLEVBQ2pCLFNBQWlCO0lBRWpCLElBQUksQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQy9DLElBQUksVUFBVSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FB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b3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoZSByZXZlcnNlIHByb3h5IHRoYXQgbWFrZXMgdGhlIHdyYXBwZXIgcGF0aCBwb3NzaWJsZS5cbiAqXG4gKiBUaGUgd3JhcHBlciBjbGFpbXMgdGhlIFBVQkxJQyBwb3J0IGFuZCB0aGUgd3JhcHBlZCBhcHAgcnVucyBhcyBhIGNoaWxkIHByb2Nlc3NcbiAqIG9uIGEgcHJpdmF0ZSBsb29wYmFjayBwb3J0LCBzbyAtIHVubGlrZSB0aGUgaW4tcHJvY2VzcyBwbHVnaW4gLSB0aGVyZSBpcyBub1xuICogbWlkZGxld2FyZSBjaGFpbiB0byBpbnNlcnQgdGhlIGdhdGUgaW50by4gVGhpcyBwcm94eSBpcyB0aGF0IGluc2VydGlvbiBwb2ludDogaXRcbiAqIGFuc3dlcnMgdGhlIGxvZ2luIHJvdXRlcyBpdHNlbGYsIGFwcGxpZXMgdGhlIGdhdGUgdG8gZXZlcnl0aGluZyBlbHNlLCBhbmRcbiAqIGZvcndhcmRzIHdoYXQgc3Vydml2ZXMgdG8gdGhlIGNoaWxkLlxuICpcbiAqIFRoZSBnYXRpbmcgREVDSVNJT04gaXMgbm90IHJlaW1wbGVtZW50ZWQgaGVyZS4gYEBkYngtdG9vbHMvdHVubmVsYCdzXG4gKiBgZ2F0ZS5nYXRlUmVxdWVzdGAgbWFrZXMgaXQgLSB0aGUgc2FtZSBmdW5jdGlvbiB0aGUgRXhwcmVzcyBtaWRkbGV3YXJlIGNhbGxzIC1cbiAqIGFuZCB0aGlzIG1vZHVsZSBvbmx5IGRpZmZlcnMgaW4gaG93IHRoZSBvdXRjb21lIGlzIHdyaXR0ZW46IGEgcHJveGllZCByZXF1ZXN0XG4gKiBpbnN0ZWFkIG9mIGBuZXh0KClgLCBhIGB3cml0ZUhlYWRgIGluc3RlYWQgb2YgYHJlcy5qc29uYC4gVGhhdCBpcyBkZWxpYmVyYXRlOlxuICogdHdvIGluZGVwZW5kZW50IGltcGxlbWVudGF0aW9ucyBvZiBcIndoaWNoIHJlcXVlc3RzIGFyZSBnYXRlZCBhbmQgd2hpY2ggaGVhZGVyc1xuICogYXJlIHN0cmlwcGVkXCIgaXMgdGhlIG9uZSB3YXkgdGhpcyBwYWNrYWdlIGNvdWxkIGJlY29tZSBhIHNlY3VyaXR5IGJ1Zy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlU2VydmVyLCB0eXBlIEluY29taW5nTWVzc2FnZSwgdHlwZSBTZXJ2ZXJSZXNwb25zZSB9IGZyb20gXCJub2RlOmh0dHBcIjtcbmltcG9ydCB0eXBlIHsgU29ja2V0IH0gZnJvbSBcIm5vZGU6bmV0XCI7XG5pbXBvcnQgeyBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHtcbiAgZ2F0ZSBhcyB0dW5uZWxHYXRlLFxuICBoZWFkZXJzIGFzIHR1bm5lbEhlYWRlcnMsXG4gIGxvZ2luUGFnZSBhcyB0dW5uZWxMb2dpblBhZ2UsXG4gIHR5cGUgQXV0aEdhdGVBcGksXG59IGZyb20gXCJAZGJ4LXRvb2xzL3R1bm5lbFwiO1xuaW1wb3J0IFByb3h5TW9kdWxlIGZyb20gXCJodHRwLXByb3h5LTNcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcInR1bm5lbDpwcm94eVwiKTtcblxudHlwZSBVcGdyYWRlU29ja2V0ID0gU29ja2V0ICYgeyBkZXN0cm95U29vbj86ICgpID0+IHZvaWQgfTtcblxuLyoqIExpc3RlbmVyLCB1cHN0cmVhbSBhcHBsaWNhdGlvbiwgYW5kIG9wdGlvbmFsIGF1dGhlbnRpY2F0aW9uIHNldHRpbmdzIGZvciB0aGUgd3JhcHBlciBwcm94eS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUHJveHlPcHRpb25zIHtcbiAgLyoqIFRoZSBwb3J0IHRoaXMgcHJveHkgbGlzdGVucyBvbiAtIHRoZSBwb3J0IHBvcnRyIGFuZCB0aGUgcGxhdGZvcm0gcm91dGUgdG8uICovXG4gIHB1YmxpY1BvcnQ6IG51bWJlcjtcbiAgLyoqIFRoZSBwcml2YXRlIGxvb3BiYWNrIHBvcnQgdGhlIHdyYXBwZWQgYXBwIGxpc3RlbnMgb24uICovXG4gIGFwcFBvcnQ6IG51bWJlcjtcbiAgLyoqIFRoZSBnYXRlIGhhbmRsZXJzLiBPbWl0dGVkIChhbiBgLS1pbnNlY3VyZWAgcnVuKSBmb3J3YXJkcyBldmVyeXRoaW5nLiAqL1xuICBnYXRlPzogQXV0aEdhdGVBcGk7XG4gIC8qKiBFeHRyYSBgeC1gIGhlYWRlcnMgdHVubmVsIHRyYWZmaWMgbWF5IGZvcndhcmQuICovXG4gIGZvcndhcmRIZWFkZXJzPzogcmVhZG9ubHkgc3RyaW5nW107XG4gIC8qKiBQYXRoIHByZWZpeGVzIHRvIGdhdGUgYmV5b25kIGAvYXBpL2AgKGUuZy4gYC93c2AgZm9yIGEgV2ViU29ja2V0IGFwcCkuICovXG4gIGdhdGVQYXRocz86IHJlYWRvbmx5IHN0cmluZ1tdO1xuICAvKiogQnJhbmQgbmFtZSBmb3IgdGhlIGhvc3RlZCBsb2dpbiBwYWdlIHNob3duIG9uIGEgZGVuaWVkIGJyb3dzZXIgbmF2aWdhdGlvbi4gKi9cbiAgYnJhbmROYW1lPzogc3RyaW5nO1xuICAvKipcbiAgICogQWRkcmVzc2VzIHRvIGxpc3RlbiBvbi4gRGVmYXVsdHMgdG8gYSBzaW5nbGUgYDAuMC4wLjBgIChldmVyeSBpbnRlcmZhY2UpLFxuICAgKiB0aGUgcG9ydHIvcGxhdGZvcm0gY2FzZS4gUGFzcyBzcGVjaWZpYyBpbnRlcmZhY2UgSVBzIHRvIGV4cG9zZSB0aGUgZ2F0ZSBvblxuICAgKiBvbmx5IHRob3NlIC0gZS5nLiBhbiBvdmVybGF5L0xBTiBhZGRyZXNzIHdoaWxlIGxvb3BiYWNrIHJlYWNoZXMgdGhlIHVwc3RyZWFtXG4gICAqIGRpcmVjdGx5IGFuZCB1bmdhdGVkLlxuICAgKi9cbiAgYmluZEhvc3RzPzogcmVhZG9ubHkgc3RyaW5nW107XG59XG5cbmZ1bmN0aW9uIHNlbmRKc29uKFxuICByZXNwb25zZTogU2VydmVyUmVzcG9uc2UsXG4gIHN0YXR1czogbnVtYmVyLFxuICBib2R5OiB1bmtub3duLFxuICBzZXRDb29raWU/OiBzdHJpbmcsXG4pOiB2b2lkIHtcbiAgcmVzcG9uc2Uud3JpdGVIZWFkKHN0YXR1cywge1xuICAgIFwiY29udGVudC10eXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiLFxuICAgIC4uLihzZXRDb29raWUgPyB7IFwic2V0LWNvb2tpZVwiOiBzZXRDb29raWUgfSA6IHt9KSxcbiAgfSk7XG4gIHJlc3BvbnNlLmVuZChKU09OLnN0cmluZ2lmeShib2R5KSk7XG59XG5cbi8qKlxuICogSGFuZCBldmVyeSBCZXR0ZXIgQXV0aCBhbmQgY29tcGF0aWJpbGl0eSByb3V0ZSB0byB0aGUgc2hhcmVkIGdhdGUgcnVudGltZS5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaGFuZGxlQXV0aFJvdXRlKFxuICByZXF1ZXN0OiBJbmNvbWluZ01lc3NhZ2UsXG4gIHJlc3BvbnNlOiBTZXJ2ZXJSZXNwb25zZSxcbiAgcGF0aDogc3RyaW5nLFxuICBnYXRlOiBBdXRoR2F0ZUFwaSxcbiAgYnJhbmROYW1lOiBzdHJpbmcsXG4pOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgaWYgKCF0dW5uZWxHYXRlLmlzQXV0aFBhdGgocGF0aCkpIHJldHVybiBmYWxzZTtcbiAgaWYgKHR1bm5lbEdhdGUud2FudHNIb3N0ZWRMb2dpbihyZXF1ZXN0KSkge1xuICAgIHJlc3BvbnNlLndyaXRlSGVhZCgyMDAsIHsgXCJjb250ZW50LXR5cGVcIjogXCJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLThcIiB9KTtcbiAgICByZXNwb25zZS5lbmQoXG4gICAgICB0dW5uZWxMb2dpblBhZ2UubG9naW5QYWdlSHRtbCh7XG4gICAgICAgIGJyYW5kTmFtZSxcbiAgICAgICAgcmV0dXJuVG86IHR1bm5lbEdhdGUubG9naW5SZXR1cm5UbyhyZXF1ZXN0KSxcbiAgICAgIH0pLFxuICAgICk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZ2F0ZS5oYW5kbGVyKHR1bm5lbEdhdGUud2ViUmVxdWVzdChyZXF1ZXN0KSk7XG4gIGF3YWl0IHR1bm5lbEdhdGUuc2VuZFdlYlJlc3BvbnNlKHJlc3BvbnNlLCByZXN1bHQpO1xuICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqXG4gKiBTdGFydCB0aGUgcHJveHkgYW5kIHJlc29sdmUgb25jZSBpdCBpcyBsaXN0ZW5pbmcuXG4gKlxuICogYHB1YmxpY0RvbWFpbmAgaXMgaW50ZW50aW9uYWxseSBOT1QgcGFzc2VkIHRvIGBnYXRlUmVxdWVzdGA6IG9uIHRoaXMgcGF0aCBldmVyeVxuICogcmVxdWVzdCBhcnJpdmVkIG9uIHRoZSBwdWJsaWMgcG9ydCwgc28gaXQgSVMgdHVubmVsIHRyYWZmaWMgYnkgY29uc3RydWN0aW9uIC1cbiAqIHdoZXJlYXMgdGhlIGluLXByb2Nlc3MgZ2F0ZSBzaGFyZXMgYSBwb3J0IHdpdGggdGhlIHBsYXRmb3JtIGZyb250IGRvb3IgYW5kIGhhc1xuICogdG8gdGVsbCB0aGVtIGFwYXJ0IGJ5IGBIb3N0YC4gUGFzc2luZyB0aGUgcmVxdWVzdCdzIG93biBob3N0IGtlZXBzIHRoZSBzaGFyZWRcbiAqIGRlY2lzaW9uIGZ1bmN0aW9uJ3MgY29udHJhY3Qgc2F0aXNmaWVkIHdpdGhvdXQgd2Vha2VuaW5nIGl0LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3RhcnRQcm94eShvcHRpb25zOiBQcm94eU9wdGlvbnMpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgcHJveHkgPSBQcm94eU1vZHVsZS5jcmVhdGVQcm94eVNlcnZlcih7XG4gICAgdGFyZ2V0OiBgaHR0cDovLzEyNy4wLjAuMToke29wdGlvbnMuYXBwUG9ydH1gLFxuICAgIHdzOiB0cnVlLFxuICAgIHhmd2Q6IHRydWUsXG4gIH0pO1xuICBjb25zdCBoZWFkZXJQb2xpY3kgPSB0dW5uZWxIZWFkZXJzLnRvSGVhZGVyUG9saWN5KG9wdGlvbnMuZm9yd2FyZEhlYWRlcnMpO1xuICBjb25zdCBnYXRlID0gb3B0aW9ucy5nYXRlO1xuXG4gIGNvbnN0IGRlY2lkZSA9IChyZXF1ZXN0OiBJbmNvbWluZ01lc3NhZ2UpOiBQcm9taXNlPHR1bm5lbEdhdGUuR2F0ZUFjdGlvbj4gPT5cbiAgICBnYXRlXG4gICAgICA/IHR1bm5lbEdhdGUuZ2F0ZVJlcXVlc3QocmVxdWVzdCwge1xuICAgICAgICAgIGdhdGUsXG4gICAgICAgICAgcHVibGljRG9tYWluOiAocmVxdWVzdC5oZWFkZXJzLmhvc3QgPz8gXCJcIikuc3BsaXQoXCI6XCIpWzBdIHx8IFwibG9jYWxob3N0XCIsXG4gICAgICAgICAgaGVhZGVyUG9saWN5LFxuICAgICAgICAgIGdhdGVQYXRoczogb3B0aW9ucy5nYXRlUGF0aHMsXG4gICAgICAgIH0pXG4gICAgICA6IFByb21pc2UucmVzb2x2ZTx0dW5uZWxHYXRlLkdhdGVBY3Rpb24+KFwicGFzc1wiKTtcblxuICBjb25zdCBvblJlcXVlc3QgPSAocmVxdWVzdDogSW5jb21pbmdNZXNzYWdlLCByZXNwb25zZTogU2VydmVyUmVzcG9uc2UpOiB2b2lkID0+IHtcbiAgICB2b2lkIChhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBwYXRoID0gKHJlcXVlc3QudXJsID8/IFwiL1wiKS5zcGxpdChcIj9cIilbMF0gPz8gXCIvXCI7XG4gICAgICBjb25zdCBicmFuZE5hbWUgPSBvcHRpb25zLmJyYW5kTmFtZSA/PyBcInRoaXMgYXBwXCI7XG4gICAgICBpZiAoZ2F0ZSAmJiAoYXdhaXQgaGFuZGxlQXV0aFJvdXRlKHJlcXVlc3QsIHJlc3BvbnNlLCBwYXRoLCBnYXRlLCBicmFuZE5hbWUpKSkgcmV0dXJuO1xuICAgICAgaWYgKChhd2FpdCBkZWNpZGUocmVxdWVzdCkpID09PSBcImRlbnlcIikge1xuICAgICAgICAvLyBBIGRlbmllZCBicm93c2VyIG5hdmlnYXRpb24gZ2V0cyB0aGUgaG9zdGVkIGxvZ2luIHBhZ2U7IGFueXRoaW5nIGVsc2VcbiAgICAgICAgLy8gKGFuIFhIUiwgYSAvd3MgdXBncmFkZSBwcm9iZSwgYSBub24tSFRNTCBmZXRjaCkgZ2V0cyB0aGUgNDAxIEpTT04uXG4gICAgICAgIGlmIChnYXRlICYmIHR1bm5lbEdhdGUud2FudHNMb2dpblBhZ2UocmVxdWVzdCkpIHtcbiAgICAgICAgICBjb25zdCBodG1sID0gdHVubmVsTG9naW5QYWdlLmxvZ2luUGFnZUh0bWwoe1xuICAgICAgICAgICAgYnJhbmROYW1lLFxuICAgICAgICAgICAgcmV0dXJuVG86IHR1bm5lbEdhdGUucmVxdWVzdFJldHVyblRvKHJlcXVlc3QpLFxuICAgICAgICAgIH0pO1xuICAgICAgICAgIHJlc3BvbnNlLndyaXRlSGVhZCg0MDEsIHsgXCJjb250ZW50LXR5cGVcIjogXCJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLThcIiB9KTtcbiAgICAgICAgICByZXNwb25zZS5lbmQoaHRtbCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgc2VuZEpzb24ocmVzcG9uc2UsIDQwMSwgdHVubmVsR2F0ZS51bmF1dGhvcml6ZWRCb2R5KHJlcXVlc3QpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBwcm94eS53ZWIocmVxdWVzdCwgcmVzcG9uc2UpO1xuICAgIH0pKCkuY2F0Y2goKGVycm9yOiB1bmtub3duKSA9PiB7XG4gICAgICBsb2dnZXIuZXJyb3IoXCJwcm94eSByZXF1ZXN0IGZhaWxlZFwiLCB7IGVycm9yIH0pO1xuICAgICAgaWYgKCFyZXNwb25zZS5oZWFkZXJzU2VudCkgc2VuZEpzb24ocmVzcG9uc2UsIDUwMiwgeyBlcnJvcjogXCJiYWQgZ2F0ZXdheVwiIH0pO1xuICAgICAgZWxzZSByZXNwb25zZS5lbmQoKTtcbiAgICB9KTtcbiAgfTtcblxuICAvLyBBIHdlYnNvY2tldCB1cGdyYWRlIGNhbm5vdCBiZSBhbnN3ZXJlZCB3aXRoIGEgNDAxIGJvZHksIHNvIGEgZGVuaWVkIG9uZSBpc1xuICAvLyBkZXN0cm95ZWQgLSB0aGUgY2xpZW50IHNlZXMgdGhlIGhhbmRzaGFrZSBmYWlsLCB3aGljaCBpcyB3aGF0IGEgYnJvd3NlcidzXG4gIC8vIFdlYlNvY2tldCBlcnJvciBoYW5kbGVyIGV4cGVjdHMuXG4gIGNvbnN0IG9uVXBncmFkZSA9IChyZXF1ZXN0OiBJbmNvbWluZ01lc3NhZ2UsIHNvY2tldDogU29ja2V0LCBoZWFkOiBCdWZmZXIpOiB2b2lkID0+IHtcbiAgICBjb25zdCB1cGdyYWRlU29ja2V0ID0gc29ja2V0IGFzIFVwZ3JhZGVTb2NrZXQ7XG4gICAgdXBncmFkZVNvY2tldC5kZXN0cm95U29vbiA/Pz0gKCkgPT4gdXBncmFkZVNvY2tldC5lbmQoKTtcbiAgICBjb25zdCBwYXRoID0gKHJlcXVlc3QudXJsID8/IFwiL1wiKS5zcGxpdChcIj9cIiwgMSlbMF0gPz8gXCIvXCI7XG4gICAgaWYgKHR1bm5lbEdhdGUuaXNBdXRoUGF0aChwYXRoKSkge1xuICAgICAgdXBncmFkZVNvY2tldC5kZXN0cm95KCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHZvaWQgZGVjaWRlKHJlcXVlc3QpXG4gICAgICAudGhlbigoYWN0aW9uKSA9PiB7XG4gICAgICAgIGlmIChhY3Rpb24gPT09IFwiZGVueVwiKSB1cGdyYWRlU29ja2V0LmRlc3Ryb3koKTtcbiAgICAgICAgZWxzZSBwcm94eS53cyhyZXF1ZXN0LCB1cGdyYWRlU29ja2V0LCBoZWFkKTtcbiAgICAgIH0pXG4gICAgICAuY2F0Y2goKCkgPT4gdXBncmFkZVNvY2tldC5kZXN0cm95KCkpO1xuICB9O1xuXG4gIC8vIE9uZSBuZXQuU2VydmVyIGJpbmRzIG9uZSBhZGRyZXNzLCBzbyBsaXN0ZW4gZWFjaCBob3N0IG9uIGl0cyBvd24gc2VydmVyIHRoYXRcbiAgLy8gc2hhcmVzIHRoZSBzYW1lIGhhbmRsZXJzLiBCaW5kaW5nIHNwZWNpZmljIGludGVyZmFjZSBJUHMgKHJhdGhlciB0aGFuIHRoZVxuICAvLyBkZWZhdWx0IDAuMC4wLjApIGlzIHdoYXQgbGV0cyB0aGUgZ2F0ZSBmcm9udCBhbiBvdmVybGF5L0xBTiBhZGRyZXNzIHdoaWxlXG4gIC8vIGxvb3BiYWNrIHJlYWNoZXMgdGhlIHVwc3RyZWFtIHVuZ2F0ZWQuXG4gIGNvbnN0IGhvc3RzID0gb3B0aW9ucy5iaW5kSG9zdHM/Lmxlbmd0aCA/IG9wdGlvbnMuYmluZEhvc3RzIDogW1wiMC4wLjAuMFwiXTtcbiAgYXdhaXQgUHJvbWlzZS5hbGwoXG4gICAgaG9zdHMubWFwKFxuICAgICAgKGhvc3QpID0+XG4gICAgICAgIG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlKSA9PiB7XG4gICAgICAgICAgY29uc3Qgc2VydmVyID0gY3JlYXRlU2VydmVyKG9uUmVxdWVzdCk7XG4gICAgICAgICAgc2VydmVyLm9uKFwidXBncmFkZVwiLCBvblVwZ3JhZGUpO1xuICAgICAgICAgIHNlcnZlci5saXN0ZW4ob3B0aW9ucy5wdWJsaWNQb3J0LCBob3N0LCAoKSA9PiB7XG4gICAgICAgICAgICBsb2dnZXIuaW5mbyhcInByb3h5IGxpc3RlbmluZ1wiLCB7XG4gICAgICAgICAgICAgIGhvc3QsXG4gICAgICAgICAgICAgIHB1YmxpY1BvcnQ6IG9wdGlvbnMucHVibGljUG9ydCxcbiAgICAgICAgICAgICAgYXBwUG9ydDogb3B0aW9ucy5hcHBQb3J0LFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH0pLFxuICAgICksXG4gICk7XG59XG4iXX0=
|
package/lib/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/bootstrap.ts","../src/bun.ts","../src/cli.ts","../src/root.ts","../index.ts","../bin/dbx-tools.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../src/bootstrap.ts","../src/bun.ts","../src/cli.ts","../src/root.ts","../src/appkit/cli.ts","../src/appkit/env-export.ts","../src/auth/cli.ts","../src/auth/options.ts","../src/graphiti/cli.ts","../src/graphiti/options.ts","../src/lakebase-proxy/cancellation.ts","../src/lakebase-proxy/cli.ts","../src/lakebase-proxy/options.ts","../src/lakebase-proxy/protocol.ts","../src/lakebase-proxy/proxy.ts","../src/model-gateway/cli.ts","../src/model-gateway/server.ts","../src/tunnel/app.ts","../src/tunnel/cli.ts","../src/tunnel/options.ts","../src/tunnel/proxy.ts","../index.ts","../bin/dbx-graphiti.ts","../bin/dbx-lakebase-proxy.ts","../bin/dbx-model-gateway.ts","../bin/dbx-tools.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
10
|
"dbx": "./lib/bin/dbx-tools.js",
|
|
11
|
+
"dbx-graphiti": "./lib/bin/dbx-graphiti.js",
|
|
12
|
+
"dbx-lakebase-proxy": "./lib/bin/dbx-lakebase-proxy.js",
|
|
13
|
+
"dbx-model-gateway": "./lib/bin/dbx-model-gateway.js",
|
|
11
14
|
"dbx-tools": "./lib/bin/dbx-tools.js"
|
|
12
15
|
},
|
|
13
16
|
"scripts": {
|
|
@@ -24,19 +27,32 @@
|
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@types/node": "^24.6.0",
|
|
30
|
+
"@types/pg": "^8",
|
|
27
31
|
"typescript": "^5.9.3"
|
|
28
32
|
},
|
|
29
33
|
"dependencies": {
|
|
30
34
|
"@clack/prompts": "^1.7.0",
|
|
31
|
-
"@
|
|
32
|
-
"@dbx-tools/
|
|
33
|
-
"@dbx-tools/
|
|
34
|
-
"@dbx-tools/
|
|
35
|
-
"@dbx-tools/
|
|
36
|
-
"@dbx-tools/cli-
|
|
37
|
-
"@dbx-tools/
|
|
38
|
-
"@dbx-tools/
|
|
35
|
+
"@databricks/appkit": "0.81.0",
|
|
36
|
+
"@dbx-tools/appkit": "^0.9.60",
|
|
37
|
+
"@dbx-tools/appkit-model-gateway": "^0.9.60",
|
|
38
|
+
"@dbx-tools/auth": "^0.9.60",
|
|
39
|
+
"@dbx-tools/auth-gate": "^0.9.60",
|
|
40
|
+
"@dbx-tools/cli-args": "^0.9.60",
|
|
41
|
+
"@dbx-tools/cli-service": "^0.9.60",
|
|
42
|
+
"@dbx-tools/core": "^0.9.60",
|
|
43
|
+
"@dbx-tools/databricks": "^0.9.60",
|
|
44
|
+
"@dbx-tools/email": "^0.9.60",
|
|
45
|
+
"@dbx-tools/graphiti": "^0.9.60",
|
|
46
|
+
"@dbx-tools/lakebase": "^0.9.60",
|
|
47
|
+
"@dbx-tools/postgres": "^0.9.60",
|
|
48
|
+
"@dbx-tools/shared-auth": "^0.9.60",
|
|
49
|
+
"@dbx-tools/shared-core": "^0.9.60",
|
|
50
|
+
"@dbx-tools/shared-email": "^0.9.60",
|
|
51
|
+
"@dbx-tools/shared-model-gateway": "^0.9.60",
|
|
52
|
+
"@dbx-tools/tunnel": "^0.9.60",
|
|
39
53
|
"commander": "^15.0.0",
|
|
54
|
+
"http-proxy-3": "^1.23.1",
|
|
55
|
+
"pg": "^8.22.0",
|
|
40
56
|
"zod": "4.3.6"
|
|
41
57
|
},
|
|
42
58
|
"main": "./lib/index.js",
|
|
@@ -47,6 +63,9 @@
|
|
|
47
63
|
"types": "./lib/index.d.ts",
|
|
48
64
|
"bin": {
|
|
49
65
|
"dbx": "./lib/bin/dbx-tools.js",
|
|
66
|
+
"dbx-graphiti": "./lib/bin/dbx-graphiti.js",
|
|
67
|
+
"dbx-lakebase-proxy": "./lib/bin/dbx-lakebase-proxy.js",
|
|
68
|
+
"dbx-model-gateway": "./lib/bin/dbx-model-gateway.js",
|
|
50
69
|
"dbx-tools": "./lib/bin/dbx-tools.js"
|
|
51
70
|
},
|
|
52
71
|
"exports": {
|
|
@@ -70,10 +89,58 @@
|
|
|
70
89
|
"types": "./lib/src/root.d.ts",
|
|
71
90
|
"default": "./lib/src/root.js"
|
|
72
91
|
},
|
|
92
|
+
"./appkit": {
|
|
93
|
+
"types": "./lib/src/appkit/cli.d.ts",
|
|
94
|
+
"default": "./lib/src/appkit/cli.js"
|
|
95
|
+
},
|
|
96
|
+
"./appkit/env-export": {
|
|
97
|
+
"types": "./lib/src/appkit/env-export.d.ts",
|
|
98
|
+
"default": "./lib/src/appkit/env-export.js"
|
|
99
|
+
},
|
|
100
|
+
"./auth": {
|
|
101
|
+
"types": "./lib/src/auth/cli.d.ts",
|
|
102
|
+
"default": "./lib/src/auth/cli.js"
|
|
103
|
+
},
|
|
104
|
+
"./auth/options": {
|
|
105
|
+
"types": "./lib/src/auth/options.d.ts",
|
|
106
|
+
"default": "./lib/src/auth/options.js"
|
|
107
|
+
},
|
|
108
|
+
"./graphiti": {
|
|
109
|
+
"types": "./lib/src/graphiti/cli.d.ts",
|
|
110
|
+
"default": "./lib/src/graphiti/cli.js"
|
|
111
|
+
},
|
|
112
|
+
"./graphiti/options": {
|
|
113
|
+
"types": "./lib/src/graphiti/options.d.ts",
|
|
114
|
+
"default": "./lib/src/graphiti/options.js"
|
|
115
|
+
},
|
|
116
|
+
"./lakebase-proxy": {
|
|
117
|
+
"types": "./lib/src/lakebase-proxy/cli.d.ts",
|
|
118
|
+
"default": "./lib/src/lakebase-proxy/cli.js"
|
|
119
|
+
},
|
|
120
|
+
"./lakebase-proxy/options": {
|
|
121
|
+
"types": "./lib/src/lakebase-proxy/options.d.ts",
|
|
122
|
+
"default": "./lib/src/lakebase-proxy/options.js"
|
|
123
|
+
},
|
|
124
|
+
"./model-gateway": {
|
|
125
|
+
"types": "./lib/src/model-gateway/cli.d.ts",
|
|
126
|
+
"default": "./lib/src/model-gateway/cli.js"
|
|
127
|
+
},
|
|
128
|
+
"./model-gateway/server": {
|
|
129
|
+
"types": "./lib/src/model-gateway/server.d.ts",
|
|
130
|
+
"default": "./lib/src/model-gateway/server.js"
|
|
131
|
+
},
|
|
132
|
+
"./tunnel": {
|
|
133
|
+
"types": "./lib/src/tunnel/cli.d.ts",
|
|
134
|
+
"default": "./lib/src/tunnel/cli.js"
|
|
135
|
+
},
|
|
136
|
+
"./tunnel/options": {
|
|
137
|
+
"types": "./lib/src/tunnel/options.d.ts",
|
|
138
|
+
"default": "./lib/src/tunnel/options.js"
|
|
139
|
+
},
|
|
73
140
|
"./package.json": "./package.json"
|
|
74
141
|
}
|
|
75
142
|
},
|
|
76
|
-
"version": "0.9.
|
|
143
|
+
"version": "0.9.60",
|
|
77
144
|
"types": "./lib/index.d.ts",
|
|
78
145
|
"dbxToolsConfig": {
|
|
79
146
|
"tags": [
|
|
@@ -102,6 +169,54 @@
|
|
|
102
169
|
"types": "./lib/src/root.d.ts",
|
|
103
170
|
"default": "./lib/src/root.js"
|
|
104
171
|
},
|
|
172
|
+
"./appkit": {
|
|
173
|
+
"types": "./lib/src/appkit/cli.d.ts",
|
|
174
|
+
"default": "./lib/src/appkit/cli.js"
|
|
175
|
+
},
|
|
176
|
+
"./appkit/env-export": {
|
|
177
|
+
"types": "./lib/src/appkit/env-export.d.ts",
|
|
178
|
+
"default": "./lib/src/appkit/env-export.js"
|
|
179
|
+
},
|
|
180
|
+
"./auth": {
|
|
181
|
+
"types": "./lib/src/auth/cli.d.ts",
|
|
182
|
+
"default": "./lib/src/auth/cli.js"
|
|
183
|
+
},
|
|
184
|
+
"./auth/options": {
|
|
185
|
+
"types": "./lib/src/auth/options.d.ts",
|
|
186
|
+
"default": "./lib/src/auth/options.js"
|
|
187
|
+
},
|
|
188
|
+
"./graphiti": {
|
|
189
|
+
"types": "./lib/src/graphiti/cli.d.ts",
|
|
190
|
+
"default": "./lib/src/graphiti/cli.js"
|
|
191
|
+
},
|
|
192
|
+
"./graphiti/options": {
|
|
193
|
+
"types": "./lib/src/graphiti/options.d.ts",
|
|
194
|
+
"default": "./lib/src/graphiti/options.js"
|
|
195
|
+
},
|
|
196
|
+
"./lakebase-proxy": {
|
|
197
|
+
"types": "./lib/src/lakebase-proxy/cli.d.ts",
|
|
198
|
+
"default": "./lib/src/lakebase-proxy/cli.js"
|
|
199
|
+
},
|
|
200
|
+
"./lakebase-proxy/options": {
|
|
201
|
+
"types": "./lib/src/lakebase-proxy/options.d.ts",
|
|
202
|
+
"default": "./lib/src/lakebase-proxy/options.js"
|
|
203
|
+
},
|
|
204
|
+
"./model-gateway": {
|
|
205
|
+
"types": "./lib/src/model-gateway/cli.d.ts",
|
|
206
|
+
"default": "./lib/src/model-gateway/cli.js"
|
|
207
|
+
},
|
|
208
|
+
"./model-gateway/server": {
|
|
209
|
+
"types": "./lib/src/model-gateway/server.d.ts",
|
|
210
|
+
"default": "./lib/src/model-gateway/server.js"
|
|
211
|
+
},
|
|
212
|
+
"./tunnel": {
|
|
213
|
+
"types": "./lib/src/tunnel/cli.d.ts",
|
|
214
|
+
"default": "./lib/src/tunnel/cli.js"
|
|
215
|
+
},
|
|
216
|
+
"./tunnel/options": {
|
|
217
|
+
"types": "./lib/src/tunnel/options.d.ts",
|
|
218
|
+
"default": "./lib/src/tunnel/options.js"
|
|
219
|
+
},
|
|
105
220
|
"./package.json": "./package.json"
|
|
106
221
|
},
|
|
107
222
|
"files": [
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dbx appkit` commander program.
|
|
3
|
+
*
|
|
4
|
+
* `env` runs AppKit auto-config and prints the env vars it added or changed as
|
|
5
|
+
* eval-able `export` / `set` lines (or JSON) - e.g. `eval "$(dbx appkit env)"`
|
|
6
|
+
* to load a resolved Lakebase connection into your shell.
|
|
7
|
+
*
|
|
8
|
+
* This package ships no bin of its own: {@link buildProgram} is mounted as the
|
|
9
|
+
* `appkit` subcommand of the single `dbx` CLI (`@dbx-tools/cli`), which imports
|
|
10
|
+
* this module lazily so `dbx dev` never pays for the AppKit load.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { appkit } from "@dbx-tools/appkit";
|
|
16
|
+
import { addArgs, parseArgs } from "@dbx-tools/cli-args";
|
|
17
|
+
import { log } from "@dbx-tools/shared-core";
|
|
18
|
+
import { Command, CommanderError } from "commander";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
diffEnv,
|
|
22
|
+
EnvCommandOptionsSchema,
|
|
23
|
+
formatEnvExport,
|
|
24
|
+
snapshotEnv,
|
|
25
|
+
type EnvCommandOptions,
|
|
26
|
+
} from "./env-export.ts";
|
|
27
|
+
|
|
28
|
+
const logger = log.logger("appkit-env");
|
|
29
|
+
|
|
30
|
+
/** Run auto-config and write the resulting env delta to stdout. */
|
|
31
|
+
async function writeEnvExport(opts: EnvCommandOptions): Promise<void> {
|
|
32
|
+
if (opts.quiet) {
|
|
33
|
+
process.env.LOG_LEVEL = "error";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
logger.debug("Snapshotting env vars");
|
|
37
|
+
const before = snapshotEnv();
|
|
38
|
+
await appkit.autoConfigure({ autoConfigure: "env" });
|
|
39
|
+
const changes = diffEnv(before, snapshotEnv());
|
|
40
|
+
|
|
41
|
+
process.stdout.write(formatEnvExport(changes, opts.format));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the `env` command on its own, so a host CLI can mount it under a
|
|
46
|
+
* different parent than {@link buildProgram}'s.
|
|
47
|
+
*/
|
|
48
|
+
export function buildEnvCommand(name = "env"): Command {
|
|
49
|
+
const command = addArgs(
|
|
50
|
+
new Command(name).description("Run AppKit auto-config and print new/changed env vars."),
|
|
51
|
+
EnvCommandOptionsSchema,
|
|
52
|
+
);
|
|
53
|
+
return command.action(async () => {
|
|
54
|
+
await writeEnvExport(parseArgs(command, EnvCommandOptionsSchema));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Build the `dbx appkit` commander program (no side effects until parsed). */
|
|
59
|
+
export function buildProgram(name = "dbx appkit"): Command {
|
|
60
|
+
return new Command()
|
|
61
|
+
.name(name)
|
|
62
|
+
.description("AppKit helpers: resolve the environment an AppKit app would start with.")
|
|
63
|
+
.addCommand(buildEnvCommand())
|
|
64
|
+
.showHelpAfterError();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Parse `argv` and run the matching command. Throws {@link CommanderError} on flag errors. */
|
|
68
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
69
|
+
await buildProgram().parseAsync(argv);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { CommanderError };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot `process.env`, diff after auto-config, and format deltas as
|
|
3
|
+
* eval-able shell `export` / Windows `set` lines or JSON.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
export const EnvExportFormatSchema = z
|
|
11
|
+
.preprocess(
|
|
12
|
+
(value) => {
|
|
13
|
+
if (typeof value !== "string") return value;
|
|
14
|
+
const normalized = value.trim().toLowerCase();
|
|
15
|
+
if (["export", "shell", "nix", "bash"].includes(normalized)) return "export";
|
|
16
|
+
if (["windows", "win", "cmd"].includes(normalized)) return "windows";
|
|
17
|
+
return normalized;
|
|
18
|
+
},
|
|
19
|
+
z.enum(["export", "windows", "json"]),
|
|
20
|
+
)
|
|
21
|
+
.describe("Environment export output format.");
|
|
22
|
+
|
|
23
|
+
export type EnvExportFormat = z.output<typeof EnvExportFormatSchema>;
|
|
24
|
+
|
|
25
|
+
/** Shallow copy of the current process environment. */
|
|
26
|
+
export function snapshotEnv(env: NodeJS.ProcessEnv = process.env): Record<string, string> {
|
|
27
|
+
const out: Record<string, string> = {};
|
|
28
|
+
for (const [key, value] of Object.entries(env)) {
|
|
29
|
+
if (value !== undefined) {
|
|
30
|
+
out[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Keys whose values were added, changed, or cleared between snapshots. A key
|
|
38
|
+
* that went from a non-blank value to blank/removed is emitted with an empty
|
|
39
|
+
* value so it can be unset. Keys that were already blank/absent and stayed that
|
|
40
|
+
* way are omitted.
|
|
41
|
+
*/
|
|
42
|
+
export function diffEnv(
|
|
43
|
+
before: Record<string, string>,
|
|
44
|
+
after: Record<string, string>,
|
|
45
|
+
): Record<string, string> {
|
|
46
|
+
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
47
|
+
const out: Record<string, string> = {};
|
|
48
|
+
for (const key of [...keys].sort()) {
|
|
49
|
+
// Treat a removed key as blank so a non-blank -> blank/removed transition is a delta.
|
|
50
|
+
const prev = before[key] ?? "";
|
|
51
|
+
const next = after[key] ?? "";
|
|
52
|
+
if (next !== prev) {
|
|
53
|
+
out[key] = next;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Select Windows `set` output on Windows and POSIX `export` output elsewhere. */
|
|
60
|
+
export function defaultEnvExportFormat(
|
|
61
|
+
platform: NodeJS.Platform = process.platform,
|
|
62
|
+
): EnvExportFormat {
|
|
63
|
+
return platform === "win32" ? "windows" : "export";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const EnvCommandOptionsSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
format: EnvExportFormatSchema.default(defaultEnvExportFormat()).describe(
|
|
69
|
+
"Output format: export, windows, or json.",
|
|
70
|
+
),
|
|
71
|
+
quiet: z.boolean().default(false).describe("Suppress auto-config log output."),
|
|
72
|
+
})
|
|
73
|
+
.strict()
|
|
74
|
+
.describe("Options for exporting the AppKit auto-config environment.");
|
|
75
|
+
|
|
76
|
+
export type EnvCommandOptions = z.output<typeof EnvCommandOptionsSchema>;
|
|
77
|
+
|
|
78
|
+
function escapeExportValue(value: string): string {
|
|
79
|
+
return value
|
|
80
|
+
.replace(/\\/g, "\\\\")
|
|
81
|
+
.replace(/"/g, '\\"')
|
|
82
|
+
.replace(/\$/g, "\\$")
|
|
83
|
+
.replace(/`/g, "\\`");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function escapeWindowsSetValue(value: string): string {
|
|
87
|
+
if (/[\s"&|<>^]/.test(value)) {
|
|
88
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Format env entries for the requested output style. An empty map prints
|
|
95
|
+
* nothing (no trailing newline).
|
|
96
|
+
*/
|
|
97
|
+
export function formatEnvExport(env: Record<string, string>, format: EnvExportFormat): string {
|
|
98
|
+
const entries = Object.entries(env).sort(([a], [b]) => a.localeCompare(b));
|
|
99
|
+
if (entries.length === 0) {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
switch (format) {
|
|
104
|
+
case "json":
|
|
105
|
+
return `${JSON.stringify(Object.fromEntries(entries), null, 2)}\n`;
|
|
106
|
+
case "windows":
|
|
107
|
+
return (
|
|
108
|
+
entries.map(([key, value]) => `set ${key}=${escapeWindowsSetValue(value)}`).join("\n") +
|
|
109
|
+
"\n"
|
|
110
|
+
);
|
|
111
|
+
case "export":
|
|
112
|
+
return (
|
|
113
|
+
entries.map(([key, value]) => `export ${key}="${escapeExportValue(value)}"`).join("\n") +
|
|
114
|
+
"\n"
|
|
115
|
+
);
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unknown env export format: ${format satisfies never}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Normalize CLI format aliases to a supported export format. */
|
|
122
|
+
export function parseEnvExportFormat(value: string): EnvExportFormat {
|
|
123
|
+
return EnvExportFormatSchema.parse(value);
|
|
124
|
+
}
|