theokit 0.1.0-alpha.0 → 0.1.0-alpha.11
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/dist/adapters/web-shim.d.ts +67 -0
- package/dist/adapters/web-shim.js +129 -0
- package/dist/adapters/web-shim.js.map +1 -0
- package/dist/adapters/ws-shim.d.ts +55 -0
- package/dist/adapters/ws-shim.js +135 -0
- package/dist/adapters/ws-shim.js.map +1 -0
- package/dist/add-2ZFFGGE4.js +165 -0
- package/dist/add-2ZFFGGE4.js.map +1 -0
- package/dist/agent-events-lSIS9uYs.d.ts +76 -0
- package/dist/aws-lambda-PONVF3UX.js +173 -0
- package/dist/aws-lambda-PONVF3UX.js.map +1 -0
- package/dist/broadcast-4SX7R5XE.js +14 -0
- package/dist/broadcast-4SX7R5XE.js.map +1 -0
- package/dist/broadcast-QOQGUNNK.js +16 -0
- package/dist/broadcast-QOQGUNNK.js.map +1 -0
- package/dist/build-IQQYCA3B.js +571 -0
- package/dist/build-IQQYCA3B.js.map +1 -0
- package/dist/bun-DITMZMKL.js +140 -0
- package/dist/bun-DITMZMKL.js.map +1 -0
- package/dist/check-PZR67OY5.js +126 -0
- package/dist/check-PZR67OY5.js.map +1 -0
- package/dist/chunk-3Q36SEDK.js +285 -0
- package/dist/chunk-3Q36SEDK.js.map +1 -0
- package/dist/chunk-4QTKSLTO.js +385 -0
- package/dist/chunk-4QTKSLTO.js.map +1 -0
- package/dist/chunk-5ODOE6EF.js +129 -0
- package/dist/chunk-5ODOE6EF.js.map +1 -0
- package/dist/chunk-7BIM27LV.js +126 -0
- package/dist/chunk-7BIM27LV.js.map +1 -0
- package/dist/chunk-C52GSJPF.js +523 -0
- package/dist/chunk-C52GSJPF.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-FH3DYBAN.js +39 -0
- package/dist/chunk-FH3DYBAN.js.map +1 -0
- package/dist/chunk-FRBYJANT.js +2133 -0
- package/dist/chunk-FRBYJANT.js.map +1 -0
- package/dist/chunk-GQSTRXXB.js +168 -0
- package/dist/chunk-GQSTRXXB.js.map +1 -0
- package/dist/chunk-GY5Q27BJ.js +107 -0
- package/dist/chunk-GY5Q27BJ.js.map +1 -0
- package/dist/chunk-JZ5WEG3S.js +71 -0
- package/dist/chunk-JZ5WEG3S.js.map +1 -0
- package/dist/chunk-KT3MWNZG.js +18 -0
- package/dist/chunk-KT3MWNZG.js.map +1 -0
- package/dist/chunk-MCP6Y5BA.js +161 -0
- package/dist/chunk-MCP6Y5BA.js.map +1 -0
- package/dist/chunk-O4BLVPML.js +173 -0
- package/dist/chunk-O4BLVPML.js.map +1 -0
- package/dist/chunk-O7335ZGH.js +84 -0
- package/dist/chunk-O7335ZGH.js.map +1 -0
- package/dist/chunk-OEB3OIYP.js +2121 -0
- package/dist/chunk-OEB3OIYP.js.map +1 -0
- package/dist/chunk-PB4GR7EP.js +51 -0
- package/dist/chunk-PB4GR7EP.js.map +1 -0
- package/dist/chunk-S3JBIFKS.js +202 -0
- package/dist/chunk-S3JBIFKS.js.map +1 -0
- package/dist/chunk-VRHXOKRP.js +601 -0
- package/dist/chunk-VRHXOKRP.js.map +1 -0
- package/dist/chunk-WQCPKA2L.js +1535 -0
- package/dist/chunk-WQCPKA2L.js.map +1 -0
- package/dist/chunk-WZ7CPVQB.js +636 -0
- package/dist/chunk-WZ7CPVQB.js.map +1 -0
- package/dist/chunk-X2VVCJ4V.js +28 -0
- package/dist/chunk-X2VVCJ4V.js.map +1 -0
- package/dist/{chunk-KPU44T6G.js → chunk-XMS5VRIK.js} +3 -5
- package/dist/chunk-XMS5VRIK.js.map +1 -0
- package/dist/chunk-Y5KE6YFP.js +65 -0
- package/dist/chunk-Y5KE6YFP.js.map +1 -0
- package/dist/chunk-YLBSSEHX.js +50 -0
- package/dist/chunk-YLBSSEHX.js.map +1 -0
- package/dist/chunk-ZK5JCE5J.js +2038 -0
- package/dist/chunk-ZK5JCE5J.js.map +1 -0
- package/dist/cli/index.js +42 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +116 -1
- package/dist/client/index.js +328 -21
- package/dist/client/index.js.map +1 -1
- package/dist/cloudflare-RBANU3SE.js +123 -0
- package/dist/cloudflare-RBANU3SE.js.map +1 -0
- package/dist/configure-agent-registry-6YGTJYBC.js +33 -0
- package/dist/configure-agent-registry-6YGTJYBC.js.map +1 -0
- package/dist/deno-deploy-J7LI6HNL.js +108 -0
- package/dist/deno-deploy-J7LI6HNL.js.map +1 -0
- package/dist/dev-VN6WS3CM.js +84 -0
- package/dist/dev-VN6WS3CM.js.map +1 -0
- package/dist/devtools/entry.d.ts +5 -0
- package/dist/devtools/entry.js +1892 -0
- package/dist/devtools/entry.js.map +1 -0
- package/dist/dist-UZV7J3YI.js +18637 -0
- package/dist/dist-UZV7J3YI.js.map +1 -0
- package/dist/{docker-M253W54T.js → docker-EZDA5FT7.js} +3 -1
- package/dist/docker-EZDA5FT7.js.map +1 -0
- package/dist/{generate-AA7ZE42F.js → generate-SIO4JSKJ.js} +48 -22
- package/dist/generate-SIO4JSKJ.js.map +1 -0
- package/dist/index-li83Swxa.d.ts +506 -0
- package/dist/index.d.ts +1373 -6
- package/dist/index.js +18 -89
- package/dist/index.js.map +1 -1
- package/dist/info-B3UOXFUF.js +104 -0
- package/dist/info-B3UOXFUF.js.map +1 -0
- package/dist/lib-NST3PNZX.js +781 -0
- package/dist/lib-NST3PNZX.js.map +1 -0
- package/dist/netlify-IFY6ZGFV.js +151 -0
- package/dist/netlify-IFY6ZGFV.js.map +1 -0
- package/dist/node-KWZI3CKV.js +11 -0
- package/dist/node-KWZI3CKV.js.map +1 -0
- package/dist/proper-lockfile-MVKMQGFK.js +1592 -0
- package/dist/proper-lockfile-MVKMQGFK.js.map +1 -0
- package/dist/rate-limit-store-BEJnhWdw.d.ts +72 -0
- package/dist/react-query/index.d.ts +33 -0
- package/dist/react-query/index.js +11 -0
- package/dist/react-query/index.js.map +1 -0
- package/dist/registry-BB3UYAOG.js +25 -0
- package/dist/registry-BB3UYAOG.js.map +1 -0
- package/dist/{routes-YP357VAC.js → routes-CAGFZKUK.js} +10 -6
- package/dist/routes-CAGFZKUK.js.map +1 -0
- package/dist/scan-C2BOKLSY.js +10 -0
- package/dist/scan-C2BOKLSY.js.map +1 -0
- package/dist/schema-Cxq2qqwh.d.ts +232 -0
- package/dist/schema-MVB2FVK6.js +29 -0
- package/dist/schema-MVB2FVK6.js.map +1 -0
- package/dist/server/auth/index.d.ts +374 -0
- package/dist/server/auth/index.js +54 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/cost/index.d.ts +172 -0
- package/dist/server/cost/index.js +12 -0
- package/dist/server/cost/index.js.map +1 -0
- package/dist/server/cron/index.d.ts +208 -0
- package/dist/server/cron/index.js +36 -0
- package/dist/server/cron/index.js.map +1 -0
- package/dist/server/index.d.ts +2091 -28
- package/dist/server/index.js +2168 -98
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +2 -0
- package/dist/server/jobs/index.js +38 -0
- package/dist/server/jobs/index.js.map +1 -0
- package/dist/services-typed-client-XWYYBWGW.js +46 -0
- package/dist/services-typed-client-XWYYBWGW.js.map +1 -0
- package/dist/services-typed-client-ZX5JUHH3.js +48 -0
- package/dist/services-typed-client-ZX5JUHH3.js.map +1 -0
- package/dist/sqlite-vec-54KB4RD3.js +41 -0
- package/dist/sqlite-vec-54KB4RD3.js.map +1 -0
- package/dist/start-CSLJQXV3.js +609 -0
- package/dist/start-CSLJQXV3.js.map +1 -0
- package/dist/static-BH6JQNM2.js +229 -0
- package/dist/static-BH6JQNM2.js.map +1 -0
- package/dist/storage-manager-D5KBXXKB.js +184 -0
- package/dist/storage-manager-D5KBXXKB.js.map +1 -0
- package/dist/storage-types-DtIVejC1.d.ts +96 -0
- package/dist/theo-cloud-3K4DGB3S.js +25 -0
- package/dist/theo-cloud-3K4DGB3S.js.map +1 -0
- package/dist/upgrade-readiness-OHL36FQL.js +249 -0
- package/dist/upgrade-readiness-OHL36FQL.js.map +1 -0
- package/dist/vercel-RHADE5IP.js +134 -0
- package/dist/vercel-RHADE5IP.js.map +1 -0
- package/dist/vite-plugin/index.d.ts +79 -2
- package/dist/vite-plugin/index.js +20 -4
- package/dist/vite-plugin-WA3G45YY.js +27 -0
- package/dist/vite-plugin-WA3G45YY.js.map +1 -0
- package/package.json +65 -4
- package/dist/build-NSKFOAFX.js +0 -49
- package/dist/build-NSKFOAFX.js.map +0 -1
- package/dist/chunk-ASGEGAWL.js +0 -480
- package/dist/chunk-ASGEGAWL.js.map +0 -1
- package/dist/chunk-ATSTRYYT.js +0 -894
- package/dist/chunk-ATSTRYYT.js.map +0 -1
- package/dist/chunk-KPU44T6G.js.map +0 -1
- package/dist/chunk-MMZZBPMX.js +0 -335
- package/dist/chunk-MMZZBPMX.js.map +0 -1
- package/dist/chunk-N5YH2UDG.js +0 -85
- package/dist/chunk-N5YH2UDG.js.map +0 -1
- package/dist/chunk-SAVVU5LG.js +0 -66
- package/dist/chunk-SAVVU5LG.js.map +0 -1
- package/dist/chunk-TXMUCDJT.js +0 -43
- package/dist/chunk-TXMUCDJT.js.map +0 -1
- package/dist/chunk-U3OJFWK3.js +0 -162
- package/dist/chunk-U3OJFWK3.js.map +0 -1
- package/dist/cloudflare-CVGN7FOU.js +0 -88
- package/dist/cloudflare-CVGN7FOU.js.map +0 -1
- package/dist/dev-7UJK3M2O.js +0 -47
- package/dist/dev-7UJK3M2O.js.map +0 -1
- package/dist/docker-M253W54T.js.map +0 -1
- package/dist/generate-AA7ZE42F.js.map +0 -1
- package/dist/rate-limit-C6hHXIj1.d.ts +0 -13
- package/dist/routes-YP357VAC.js.map +0 -1
- package/dist/start-BIS3RCFQ.js +0 -243
- package/dist/start-BIS3RCFQ.js.map +0 -1
- package/dist/vercel-747SR2FB.js +0 -80
- package/dist/vercel-747SR2FB.js.map +0 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-to-Node bridge used by adapters whose runtimes provide a `Request`
|
|
3
|
+
* (Web Standard) but where TheoKit's `executeRoute` expects a Node-style
|
|
4
|
+
* IncomingMessage/ServerResponse pair.
|
|
5
|
+
*
|
|
6
|
+
* Used by adapters that target Node-compatible runtimes (Bun, Netlify
|
|
7
|
+
* Functions, AWS Lambda Node). Cloudflare uses an inline version of the same
|
|
8
|
+
* pattern — long-term it should consolidate here too.
|
|
9
|
+
*
|
|
10
|
+
* NOT used by Deno Deploy: Deno's stdlib does not have `Buffer`/`node:http`
|
|
11
|
+
* by default and forcing the shim there bloats the bundle. Deno wiring is
|
|
12
|
+
* tracked separately and will likely require executeRoute to accept Web
|
|
13
|
+
* Standard Request directly.
|
|
14
|
+
*/
|
|
15
|
+
interface ShimRequest {
|
|
16
|
+
method: string;
|
|
17
|
+
url: string;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
socket: {
|
|
20
|
+
remoteAddress: string;
|
|
21
|
+
};
|
|
22
|
+
on: (event: 'data' | 'end' | 'error', cb: (chunk?: unknown) => void) => unknown;
|
|
23
|
+
}
|
|
24
|
+
interface ShimResponse {
|
|
25
|
+
statusCode: number;
|
|
26
|
+
headersSent: boolean;
|
|
27
|
+
writableEnded: boolean;
|
|
28
|
+
writeHead: (status: number, headers?: Record<string, string>) => void;
|
|
29
|
+
setHeader: (key: string, value: string) => void;
|
|
30
|
+
getHeader: (key: string) => string | undefined;
|
|
31
|
+
write: (chunk: Uint8Array | string) => boolean;
|
|
32
|
+
end: (body?: Uint8Array | string) => void;
|
|
33
|
+
}
|
|
34
|
+
interface ShimContext {
|
|
35
|
+
req: ShimRequest;
|
|
36
|
+
res: ShimResponse;
|
|
37
|
+
/** Resolves to a Web Standard Response once `res.end()` is called. */
|
|
38
|
+
toResponse: () => Promise<Response>;
|
|
39
|
+
}
|
|
40
|
+
interface CreateWebShimOptions {
|
|
41
|
+
/**
|
|
42
|
+
* CR-018 fix: how to resolve the client IP from forwarded headers.
|
|
43
|
+
*
|
|
44
|
+
* - `'platform'` (default for `cf-connecting-ip`): trust runtime-injected
|
|
45
|
+
* headers only (`cf-connecting-ip` on Cloudflare, `x-real-ip` on
|
|
46
|
+
* Netlify/Vercel when the platform writes it). Ignores
|
|
47
|
+
* `x-forwarded-for` because clients can spoof it.
|
|
48
|
+
* - `'trusted-proxy'`: read the **rightmost** entry of `x-forwarded-for`.
|
|
49
|
+
* Only safe when the request literally went through a trusted proxy
|
|
50
|
+
* that strips client-set headers and appends the real client IP last.
|
|
51
|
+
* - `'none'`: skip all forwarded-header lookups and report
|
|
52
|
+
* `'0.0.0.0'`. Force this when the adapter has no reliable way to
|
|
53
|
+
* identify the client (rate-limiters then must use a different key).
|
|
54
|
+
*
|
|
55
|
+
* Default: `'platform'`.
|
|
56
|
+
*/
|
|
57
|
+
trustedProxy?: 'platform' | 'trusted-proxy' | 'none';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build Node-style req/res objects around a Web Standard Request.
|
|
61
|
+
* `toResponse()` returns a Promise that resolves when `res.end()` has been
|
|
62
|
+
* invoked, materializing a Web Standard Response with the accumulated body
|
|
63
|
+
* + headers + status.
|
|
64
|
+
*/
|
|
65
|
+
declare function createWebShim(request: Request, options?: CreateWebShimOptions): ShimContext;
|
|
66
|
+
|
|
67
|
+
export { type CreateWebShimOptions, type ShimContext, type ShimRequest, type ShimResponse, createWebShim };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import "../chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/adapters/web-shim.ts
|
|
4
|
+
function resolveRemoteAddress(headers, policy) {
|
|
5
|
+
if (policy === "none") return "0.0.0.0";
|
|
6
|
+
const cf = headers["cf-connecting-ip"];
|
|
7
|
+
if (cf) return cf;
|
|
8
|
+
if (policy === "trusted-proxy") {
|
|
9
|
+
const xff = headers["x-forwarded-for"];
|
|
10
|
+
if (xff) {
|
|
11
|
+
const parts = xff.split(",").map((p) => p.trim()).filter(Boolean);
|
|
12
|
+
if (parts.length > 0) return parts[parts.length - 1];
|
|
13
|
+
}
|
|
14
|
+
const xri = headers["x-real-ip"];
|
|
15
|
+
if (xri) return xri;
|
|
16
|
+
}
|
|
17
|
+
return "0.0.0.0";
|
|
18
|
+
}
|
|
19
|
+
function createWebShim(request, options) {
|
|
20
|
+
const url = new URL(request.url);
|
|
21
|
+
const headers = {};
|
|
22
|
+
request.headers.forEach((value, key) => {
|
|
23
|
+
headers[key] = value;
|
|
24
|
+
});
|
|
25
|
+
const pumpState = { consumed: false };
|
|
26
|
+
const dataListeners = [];
|
|
27
|
+
const endListeners = [];
|
|
28
|
+
const errorListeners = [];
|
|
29
|
+
async function pumpBody() {
|
|
30
|
+
if (pumpState.consumed) return;
|
|
31
|
+
pumpState.consumed = true;
|
|
32
|
+
if (!request.body) {
|
|
33
|
+
for (const cb of endListeners) cb();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const reader = request.body.getReader();
|
|
38
|
+
let done = false;
|
|
39
|
+
while (!done) {
|
|
40
|
+
const chunk = await reader.read();
|
|
41
|
+
done = chunk.done;
|
|
42
|
+
if (!done && chunk.value) {
|
|
43
|
+
for (const cb of dataListeners) cb(chunk.value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const cb of endListeners) cb();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
for (const cb of errorListeners) cb(err);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const trustedProxy = options?.trustedProxy ?? "platform";
|
|
52
|
+
const req = {
|
|
53
|
+
method: request.method,
|
|
54
|
+
url: url.pathname + url.search,
|
|
55
|
+
headers,
|
|
56
|
+
socket: {
|
|
57
|
+
remoteAddress: resolveRemoteAddress(headers, trustedProxy)
|
|
58
|
+
},
|
|
59
|
+
on(event, cb) {
|
|
60
|
+
if (event === "data") dataListeners.push(cb);
|
|
61
|
+
if (event === "end") endListeners.push(cb);
|
|
62
|
+
if (event === "error") errorListeners.push(cb);
|
|
63
|
+
if (event === "end") void pumpBody();
|
|
64
|
+
return req;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const responseChunks = [];
|
|
68
|
+
const responseHeaders = {};
|
|
69
|
+
let responseStatus = 200;
|
|
70
|
+
let resolveResponse;
|
|
71
|
+
const responsePromise = new Promise((resolve) => {
|
|
72
|
+
resolveResponse = resolve;
|
|
73
|
+
});
|
|
74
|
+
function chunkToBytes(chunk) {
|
|
75
|
+
if (typeof chunk === "string") return new TextEncoder().encode(chunk);
|
|
76
|
+
return chunk;
|
|
77
|
+
}
|
|
78
|
+
const res = {
|
|
79
|
+
statusCode: 200,
|
|
80
|
+
headersSent: false,
|
|
81
|
+
writableEnded: false,
|
|
82
|
+
writeHead(status, headers2) {
|
|
83
|
+
responseStatus = status;
|
|
84
|
+
this.statusCode = status;
|
|
85
|
+
if (headers2) {
|
|
86
|
+
for (const [k, v] of Object.entries(headers2)) {
|
|
87
|
+
responseHeaders[k.toLowerCase()] = v;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this.headersSent = true;
|
|
91
|
+
},
|
|
92
|
+
setHeader(key, value) {
|
|
93
|
+
responseHeaders[key.toLowerCase()] = value;
|
|
94
|
+
},
|
|
95
|
+
getHeader(key) {
|
|
96
|
+
return responseHeaders[key.toLowerCase()];
|
|
97
|
+
},
|
|
98
|
+
write(chunk) {
|
|
99
|
+
responseChunks.push(chunkToBytes(chunk));
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
end(body) {
|
|
103
|
+
if (body !== void 0) responseChunks.push(chunkToBytes(body));
|
|
104
|
+
this.writableEnded = true;
|
|
105
|
+
const totalLength = responseChunks.reduce((n, c) => n + c.length, 0);
|
|
106
|
+
const concat = new Uint8Array(totalLength);
|
|
107
|
+
let offset = 0;
|
|
108
|
+
for (const c of responseChunks) {
|
|
109
|
+
concat.set(c, offset);
|
|
110
|
+
offset += c.length;
|
|
111
|
+
}
|
|
112
|
+
resolveResponse(
|
|
113
|
+
new Response(concat, {
|
|
114
|
+
status: responseStatus,
|
|
115
|
+
headers: responseHeaders
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
req,
|
|
122
|
+
res,
|
|
123
|
+
toResponse: () => responsePromise
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
createWebShim
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=web-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/web-shim.ts"],"sourcesContent":["/**\n * Web-to-Node bridge used by adapters whose runtimes provide a `Request`\n * (Web Standard) but where TheoKit's `executeRoute` expects a Node-style\n * IncomingMessage/ServerResponse pair.\n *\n * Used by adapters that target Node-compatible runtimes (Bun, Netlify\n * Functions, AWS Lambda Node). Cloudflare uses an inline version of the same\n * pattern — long-term it should consolidate here too.\n *\n * NOT used by Deno Deploy: Deno's stdlib does not have `Buffer`/`node:http`\n * by default and forcing the shim there bloats the bundle. Deno wiring is\n * tracked separately and will likely require executeRoute to accept Web\n * Standard Request directly.\n */\n\nexport interface ShimRequest {\n method: string\n url: string\n headers: Record<string, string>\n socket: { remoteAddress: string }\n on: (event: 'data' | 'end' | 'error', cb: (chunk?: unknown) => void) => unknown\n}\n\nexport interface ShimResponse {\n statusCode: number\n headersSent: boolean\n writableEnded: boolean\n writeHead: (status: number, headers?: Record<string, string>) => void\n setHeader: (key: string, value: string) => void\n getHeader: (key: string) => string | undefined\n write: (chunk: Uint8Array | string) => boolean\n end: (body?: Uint8Array | string) => void\n}\n\nexport interface ShimContext {\n req: ShimRequest\n res: ShimResponse\n /** Resolves to a Web Standard Response once `res.end()` is called. */\n toResponse: () => Promise<Response>\n}\n\nexport interface CreateWebShimOptions {\n /**\n * CR-018 fix: how to resolve the client IP from forwarded headers.\n *\n * - `'platform'` (default for `cf-connecting-ip`): trust runtime-injected\n * headers only (`cf-connecting-ip` on Cloudflare, `x-real-ip` on\n * Netlify/Vercel when the platform writes it). Ignores\n * `x-forwarded-for` because clients can spoof it.\n * - `'trusted-proxy'`: read the **rightmost** entry of `x-forwarded-for`.\n * Only safe when the request literally went through a trusted proxy\n * that strips client-set headers and appends the real client IP last.\n * - `'none'`: skip all forwarded-header lookups and report\n * `'0.0.0.0'`. Force this when the adapter has no reliable way to\n * identify the client (rate-limiters then must use a different key).\n *\n * Default: `'platform'`.\n */\n trustedProxy?: 'platform' | 'trusted-proxy' | 'none'\n}\n\nfunction resolveRemoteAddress(\n headers: Record<string, string>,\n policy: NonNullable<CreateWebShimOptions['trustedProxy']>,\n): string {\n if (policy === 'none') return '0.0.0.0'\n\n // Runtime-injected headers — these come from the platform itself and\n // cannot be set by the client.\n const cf = headers['cf-connecting-ip']\n if (cf) return cf\n\n if (policy === 'trusted-proxy') {\n // Take the RIGHTMOST entry of x-forwarded-for. The rightmost is the\n // hop nearest the application, which a trusted proxy appended; entries\n // to the left may have been forged by the client.\n const xff = headers['x-forwarded-for']\n if (xff) {\n const parts = xff\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean)\n if (parts.length > 0) return parts[parts.length - 1]\n }\n // x-real-ip is typically platform-set (NGINX/Netlify/Vercel).\n const xri = headers['x-real-ip']\n if (xri) return xri\n }\n\n return '0.0.0.0'\n}\n\n/**\n * Build Node-style req/res objects around a Web Standard Request.\n * `toResponse()` returns a Promise that resolves when `res.end()` has been\n * invoked, materializing a Web Standard Response with the accumulated body\n * + headers + status.\n */\nexport function createWebShim(request: Request, options?: CreateWebShimOptions): ShimContext {\n const url = new URL(request.url)\n const headers: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n const pumpState = { consumed: false }\n\n // Consume the request body once, then dispatch data/end events to whatever\n // listener attaches later (executeRoute's body parser).\n const dataListeners: ((chunk: Uint8Array) => void)[] = []\n const endListeners: (() => void)[] = []\n const errorListeners: ((err: unknown) => void)[] = []\n\n async function pumpBody(): Promise<void> {\n if (pumpState.consumed) return\n pumpState.consumed = true\n if (!request.body) {\n for (const cb of endListeners) cb()\n return\n }\n try {\n const reader = request.body.getReader()\n let done = false\n while (!done) {\n const chunk = await reader.read()\n done = chunk.done\n if (!done && chunk.value) {\n for (const cb of dataListeners) cb(chunk.value)\n }\n }\n for (const cb of endListeners) cb()\n } catch (err) {\n for (const cb of errorListeners) cb(err)\n }\n }\n\n const trustedProxy = options?.trustedProxy ?? 'platform'\n\n const req: ShimRequest = {\n method: request.method,\n url: url.pathname + url.search,\n headers,\n socket: {\n remoteAddress: resolveRemoteAddress(headers, trustedProxy),\n },\n on(event, cb) {\n if (event === 'data') dataListeners.push(cb)\n if (event === 'end') endListeners.push(cb)\n if (event === 'error') errorListeners.push(cb)\n // Lazily start pumping when end is registered (executeRoute always\n // listens for end before doing anything with the body).\n if (event === 'end') void pumpBody()\n return req\n },\n }\n\n const responseChunks: Uint8Array[] = []\n const responseHeaders: Record<string, string> = {}\n let responseStatus = 200\n let resolveResponse!: (r: Response) => void\n const responsePromise = new Promise<Response>((resolve) => {\n resolveResponse = resolve\n })\n\n function chunkToBytes(chunk: Uint8Array | string): Uint8Array {\n if (typeof chunk === 'string') return new TextEncoder().encode(chunk)\n return chunk\n }\n\n const res: ShimResponse = {\n statusCode: 200,\n headersSent: false,\n writableEnded: false,\n writeHead(status, headers) {\n responseStatus = status\n this.statusCode = status\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n responseHeaders[k.toLowerCase()] = v\n }\n }\n this.headersSent = true\n },\n setHeader(key, value) {\n responseHeaders[key.toLowerCase()] = value\n },\n getHeader(key) {\n return responseHeaders[key.toLowerCase()]\n },\n write(chunk) {\n responseChunks.push(chunkToBytes(chunk))\n return true\n },\n end(body) {\n if (body !== undefined) responseChunks.push(chunkToBytes(body))\n this.writableEnded = true\n const totalLength = responseChunks.reduce((n, c) => n + c.length, 0)\n const concat = new Uint8Array(totalLength)\n let offset = 0\n for (const c of responseChunks) {\n concat.set(c, offset)\n offset += c.length\n }\n resolveResponse(\n new Response(concat, {\n status: responseStatus,\n headers: responseHeaders,\n }),\n )\n },\n }\n\n return {\n req,\n res,\n toResponse: () => responsePromise,\n }\n}\n"],"mappings":"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","names":["headers"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T3.1 — WebSocket cross-runtime bridges.
|
|
3
|
+
*
|
|
4
|
+
* Exports 4 bridges (Node `ws` package, Bun.serve, Deno.upgradeWebSocket,
|
|
5
|
+
* Cloudflare WebSocketPair) that adapt each runtime's native WebSocket API
|
|
6
|
+
* to a common `WebSocketLike` interface that user handlers (`defineWebSocket`)
|
|
7
|
+
* can consume uniformly.
|
|
8
|
+
*/
|
|
9
|
+
interface WebSocketLike {
|
|
10
|
+
send(data: string | Uint8Array | ArrayBuffer): void;
|
|
11
|
+
close(code?: number, reason?: string): void;
|
|
12
|
+
on(event: 'message' | 'close' | 'error', cb: (data?: unknown) => void): void;
|
|
13
|
+
}
|
|
14
|
+
interface WsHandler {
|
|
15
|
+
onOpen?: (ws: WebSocketLike) => void;
|
|
16
|
+
onMessage?: (ws: WebSocketLike, data: unknown) => void;
|
|
17
|
+
onClose?: (ws: WebSocketLike, code?: number, reason?: string) => void;
|
|
18
|
+
onError?: (ws: WebSocketLike, err: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
interface NodeWsLike {
|
|
21
|
+
on(event: string, cb: (arg?: unknown) => void): unknown;
|
|
22
|
+
send(data: unknown): void;
|
|
23
|
+
close(code?: number, reason?: string): void;
|
|
24
|
+
}
|
|
25
|
+
declare function createNodeWsBridge(handler: WsHandler): {
|
|
26
|
+
attach(nativeWs: NodeWsLike): WebSocketLike;
|
|
27
|
+
};
|
|
28
|
+
interface BunWsContext {
|
|
29
|
+
send(data: unknown): void;
|
|
30
|
+
close(code?: number, reason?: string): void;
|
|
31
|
+
}
|
|
32
|
+
interface BunWebSocketConfig {
|
|
33
|
+
open: (ws: BunWsContext) => void;
|
|
34
|
+
message: (ws: BunWsContext, message: unknown) => void;
|
|
35
|
+
close: (ws: BunWsContext, code?: number, reason?: string) => void;
|
|
36
|
+
}
|
|
37
|
+
declare function createBunWsBridge(handler: WsHandler): BunWebSocketConfig;
|
|
38
|
+
interface DenoLike {
|
|
39
|
+
upgradeWebSocket(req: Request): {
|
|
40
|
+
response: Response;
|
|
41
|
+
socket: {
|
|
42
|
+
send(data: unknown): void;
|
|
43
|
+
close(code?: number, reason?: string): void;
|
|
44
|
+
addEventListener(event: string, cb: (e: unknown) => void): void;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
declare function createDenoWsBridge(handler: WsHandler, denoNs: DenoLike): {
|
|
49
|
+
handle(request: Request): Response;
|
|
50
|
+
};
|
|
51
|
+
declare function createCloudflareWsBridge(handler: WsHandler): {
|
|
52
|
+
handle(request: Request): Response;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { type BunWebSocketConfig, type WebSocketLike, type WsHandler, createBunWsBridge, createCloudflareWsBridge, createDenoWsBridge, createNodeWsBridge };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import "../chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/adapters/ws-shim.ts
|
|
4
|
+
function createNodeWsBridge(handler) {
|
|
5
|
+
return {
|
|
6
|
+
attach(nativeWs) {
|
|
7
|
+
const wsLike = {
|
|
8
|
+
send: (data) => {
|
|
9
|
+
nativeWs.send(data);
|
|
10
|
+
},
|
|
11
|
+
close: (code, reason) => {
|
|
12
|
+
nativeWs.close(code, reason);
|
|
13
|
+
},
|
|
14
|
+
on: (event, cb) => {
|
|
15
|
+
nativeWs.on(event, cb);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
nativeWs.on("open", () => handler.onOpen?.(wsLike));
|
|
19
|
+
nativeWs.on("message", (data) => handler.onMessage?.(wsLike, data));
|
|
20
|
+
nativeWs.on("close", (codeMaybe) => {
|
|
21
|
+
const code = typeof codeMaybe === "number" ? codeMaybe : void 0;
|
|
22
|
+
handler.onClose?.(wsLike, code);
|
|
23
|
+
});
|
|
24
|
+
nativeWs.on("error", (err) => handler.onError?.(wsLike, err));
|
|
25
|
+
return wsLike;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function bunCtxToWsLike(bunWs) {
|
|
30
|
+
return {
|
|
31
|
+
send: (data) => {
|
|
32
|
+
bunWs.send(data);
|
|
33
|
+
},
|
|
34
|
+
close: (code, reason) => {
|
|
35
|
+
bunWs.close(code, reason);
|
|
36
|
+
},
|
|
37
|
+
on: () => {
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function createBunWsBridge(handler) {
|
|
42
|
+
return {
|
|
43
|
+
open(bunWs) {
|
|
44
|
+
handler.onOpen?.(bunCtxToWsLike(bunWs));
|
|
45
|
+
},
|
|
46
|
+
message(bunWs, msg) {
|
|
47
|
+
handler.onMessage?.(bunCtxToWsLike(bunWs), msg);
|
|
48
|
+
},
|
|
49
|
+
close(bunWs, code, reason) {
|
|
50
|
+
handler.onClose?.(bunCtxToWsLike(bunWs), code, reason);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function createDenoWsBridge(handler, denoNs) {
|
|
55
|
+
return {
|
|
56
|
+
handle(request) {
|
|
57
|
+
const { response, socket } = denoNs.upgradeWebSocket(request);
|
|
58
|
+
const wsLike = {
|
|
59
|
+
send: (data) => {
|
|
60
|
+
socket.send(data);
|
|
61
|
+
},
|
|
62
|
+
close: (code, reason) => {
|
|
63
|
+
socket.close(code, reason);
|
|
64
|
+
},
|
|
65
|
+
on: () => {
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
socket.addEventListener("open", () => handler.onOpen?.(wsLike));
|
|
69
|
+
socket.addEventListener("message", (e) => {
|
|
70
|
+
const data = e.data;
|
|
71
|
+
handler.onMessage?.(wsLike, data);
|
|
72
|
+
});
|
|
73
|
+
socket.addEventListener("close", (e) => {
|
|
74
|
+
const ev = e;
|
|
75
|
+
handler.onClose?.(wsLike, ev.code, ev.reason);
|
|
76
|
+
});
|
|
77
|
+
socket.addEventListener("error", (err) => handler.onError?.(wsLike, err));
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createCloudflareWsBridge(handler) {
|
|
83
|
+
return {
|
|
84
|
+
handle(_request) {
|
|
85
|
+
const g = globalThis;
|
|
86
|
+
if (!g.WebSocketPair) {
|
|
87
|
+
throw new Error("WebSocketPair is not available \u2014 Cloudflare Workers runtime expected.");
|
|
88
|
+
}
|
|
89
|
+
const pair = new g.WebSocketPair();
|
|
90
|
+
const client = pair[0];
|
|
91
|
+
const server = pair[1];
|
|
92
|
+
server.accept();
|
|
93
|
+
const wsLike = {
|
|
94
|
+
send: (data) => {
|
|
95
|
+
server.send(data);
|
|
96
|
+
},
|
|
97
|
+
close: (code, reason) => {
|
|
98
|
+
server.close(code, reason);
|
|
99
|
+
},
|
|
100
|
+
// Cloudflare's WebSocketPair exposes events via addEventListener;
|
|
101
|
+
// the WebSocketLike `on` contract is for Node-style ws. We leave
|
|
102
|
+
// it as a no-op so user handlers that expect Node-style `on(...)`
|
|
103
|
+
// do not crash — but the events flow through addEventListener
|
|
104
|
+
// below, not through this method.
|
|
105
|
+
on: () => {
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
server.addEventListener("open", () => handler.onOpen?.(wsLike));
|
|
109
|
+
server.addEventListener("message", (e) => {
|
|
110
|
+
const data = e.data;
|
|
111
|
+
handler.onMessage?.(wsLike, data);
|
|
112
|
+
});
|
|
113
|
+
server.addEventListener("close", (e) => {
|
|
114
|
+
const ev = e;
|
|
115
|
+
handler.onClose?.(wsLike, ev.code, ev.reason);
|
|
116
|
+
});
|
|
117
|
+
server.addEventListener("error", (err) => handler.onError?.(wsLike, err));
|
|
118
|
+
try {
|
|
119
|
+
return new Response(null, {
|
|
120
|
+
status: 101,
|
|
121
|
+
webSocket: client
|
|
122
|
+
});
|
|
123
|
+
} catch {
|
|
124
|
+
return { status: 101, webSocket: client };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
createBunWsBridge,
|
|
131
|
+
createCloudflareWsBridge,
|
|
132
|
+
createDenoWsBridge,
|
|
133
|
+
createNodeWsBridge
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=ws-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/ws-shim.ts"],"sourcesContent":["/**\n * T3.1 — WebSocket cross-runtime bridges.\n *\n * Exports 4 bridges (Node `ws` package, Bun.serve, Deno.upgradeWebSocket,\n * Cloudflare WebSocketPair) that adapt each runtime's native WebSocket API\n * to a common `WebSocketLike` interface that user handlers (`defineWebSocket`)\n * can consume uniformly.\n */\n\nexport interface WebSocketLike {\n send(data: string | Uint8Array | ArrayBuffer): void\n close(code?: number, reason?: string): void\n on(event: 'message' | 'close' | 'error', cb: (data?: unknown) => void): void\n}\n\nexport interface WsHandler {\n onOpen?: (ws: WebSocketLike) => void\n onMessage?: (ws: WebSocketLike, data: unknown) => void\n onClose?: (ws: WebSocketLike, code?: number, reason?: string) => void\n onError?: (ws: WebSocketLike, err: unknown) => void\n}\n\n// ============================================================\n// Node bridge — wraps `ws` package WebSocket\n// ============================================================\n\ninterface NodeWsLike {\n on(event: string, cb: (arg?: unknown) => void): unknown\n send(data: unknown): void\n close(code?: number, reason?: string): void\n}\n\nexport function createNodeWsBridge(handler: WsHandler): {\n attach(nativeWs: NodeWsLike): WebSocketLike\n} {\n return {\n attach(nativeWs: NodeWsLike): WebSocketLike {\n const wsLike: WebSocketLike = {\n send: (data) => {\n nativeWs.send(data)\n },\n close: (code, reason) => {\n nativeWs.close(code, reason)\n },\n on: (event, cb) => {\n nativeWs.on(event, cb)\n },\n }\n nativeWs.on('open', () => handler.onOpen?.(wsLike))\n nativeWs.on('message', (data) => handler.onMessage?.(wsLike, data))\n nativeWs.on('close', (codeMaybe) => {\n const code = typeof codeMaybe === 'number' ? codeMaybe : undefined\n handler.onClose?.(wsLike, code)\n })\n nativeWs.on('error', (err) => handler.onError?.(wsLike, err))\n return wsLike\n },\n }\n}\n\n// ============================================================\n// Bun bridge — returns config for Bun.serve({ websocket })\n// ============================================================\n\ninterface BunWsContext {\n send(data: unknown): void\n close(code?: number, reason?: string): void\n}\n\nexport interface BunWebSocketConfig {\n open: (ws: BunWsContext) => void\n message: (ws: BunWsContext, message: unknown) => void\n close: (ws: BunWsContext, code?: number, reason?: string) => void\n}\n\nfunction bunCtxToWsLike(bunWs: BunWsContext): WebSocketLike {\n return {\n send: (data) => {\n bunWs.send(data)\n },\n close: (code, reason) => {\n bunWs.close(code, reason)\n },\n on: () => {\n // Bun pushes events via the config handlers — no per-instance on()\n },\n }\n}\n\nexport function createBunWsBridge(handler: WsHandler): BunWebSocketConfig {\n return {\n open(bunWs) {\n handler.onOpen?.(bunCtxToWsLike(bunWs))\n },\n message(bunWs, msg) {\n handler.onMessage?.(bunCtxToWsLike(bunWs), msg)\n },\n close(bunWs, code, reason) {\n handler.onClose?.(bunCtxToWsLike(bunWs), code, reason)\n },\n }\n}\n\n// ============================================================\n// Deno bridge — uses Deno.upgradeWebSocket(request)\n// ============================================================\n\ninterface DenoLike {\n upgradeWebSocket(req: Request): {\n response: Response\n socket: {\n send(data: unknown): void\n close(code?: number, reason?: string): void\n addEventListener(event: string, cb: (e: unknown) => void): void\n }\n }\n}\n\nexport function createDenoWsBridge(\n handler: WsHandler,\n denoNs: DenoLike,\n): { handle(request: Request): Response } {\n return {\n handle(request: Request): Response {\n const { response, socket } = denoNs.upgradeWebSocket(request)\n const wsLike: WebSocketLike = {\n send: (data) => {\n socket.send(data)\n },\n close: (code, reason) => {\n socket.close(code, reason)\n },\n on: () => {\n // Deno uses addEventListener — adapter dispatches below\n },\n }\n socket.addEventListener('open', () => handler.onOpen?.(wsLike))\n socket.addEventListener('message', (e) => {\n const data = (e as { data?: unknown }).data\n handler.onMessage?.(wsLike, data)\n })\n socket.addEventListener('close', (e) => {\n const ev = e as { code?: number; reason?: string }\n handler.onClose?.(wsLike, ev.code, ev.reason)\n })\n socket.addEventListener('error', (err) => handler.onError?.(wsLike, err))\n return response\n },\n }\n}\n\n// ============================================================\n// Cloudflare bridge — uses globalThis.WebSocketPair\n// ============================================================\n\ninterface CfServerWs {\n accept(): void\n addEventListener(event: string, cb: (e: unknown) => void): void\n send(data: unknown): void\n close(code?: number, reason?: string): void\n}\n\nexport function createCloudflareWsBridge(handler: WsHandler): {\n handle(request: Request): Response\n} {\n return {\n handle(_request: Request): Response {\n const g = globalThis as {\n WebSocketPair?: new () => { 0: unknown; 1: CfServerWs }\n }\n if (!g.WebSocketPair) {\n throw new Error('WebSocketPair is not available — Cloudflare Workers runtime expected.')\n }\n const pair = new g.WebSocketPair()\n const client = pair[0]\n const server = pair[1]\n server.accept()\n const wsLike: WebSocketLike = {\n send: (data) => {\n server.send(data)\n },\n close: (code, reason) => {\n server.close(code, reason)\n },\n // Cloudflare's WebSocketPair exposes events via addEventListener;\n // the WebSocketLike `on` contract is for Node-style ws. We leave\n // it as a no-op so user handlers that expect Node-style `on(...)`\n // do not crash — but the events flow through addEventListener\n // below, not through this method.\n on: (): void => {\n /* intentional no-op on Cloudflare; see comment above */\n },\n }\n server.addEventListener('open', () => handler.onOpen?.(wsLike))\n server.addEventListener('message', (e) => {\n const data = (e as { data?: unknown }).data\n handler.onMessage?.(wsLike, data)\n })\n server.addEventListener('close', (e) => {\n const ev = e as { code?: number; reason?: string }\n handler.onClose?.(wsLike, ev.code, ev.reason)\n })\n server.addEventListener('error', (err) => handler.onError?.(wsLike, err))\n // CF requires returning Response with status 101 and webSocket pair.\n // Node's Response throws on 101; fall back to a plain object that\n // satisfies the CF runtime's protocol but matches our test shape.\n try {\n return new Response(null, {\n status: 101,\n webSocket: client,\n } as ResponseInit & { webSocket: unknown })\n } catch {\n return { status: 101, webSocket: client } as unknown as Response\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "tsx/esm";
|
|
3
|
+
import "./chunk-KT3MWNZG.js";
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/add.ts
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
var InvalidPackageNameError = class extends Error {
|
|
10
|
+
constructor(input, reason) {
|
|
11
|
+
super(`Invalid package name "${input}": ${reason}`);
|
|
12
|
+
this.name = "InvalidPackageNameError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var UnknownPackageError = class extends Error {
|
|
16
|
+
constructor(input, suggestion) {
|
|
17
|
+
super(
|
|
18
|
+
suggestion ? `Unknown package: ${input}. Did you mean: ${suggestion}?` : `Unknown package: ${input}.`
|
|
19
|
+
);
|
|
20
|
+
this.name = "UnknownPackageError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var PACKAGE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/;
|
|
24
|
+
function validatePackageInput(input) {
|
|
25
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
26
|
+
throw new InvalidPackageNameError(input, "must be a non-empty string");
|
|
27
|
+
}
|
|
28
|
+
if (!PACKAGE_NAME_REGEX.test(input)) {
|
|
29
|
+
throw new InvalidPackageNameError(input, "allowed: lowercase letters, digits, hyphens only");
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
function detectPackageManager(lockfiles) {
|
|
34
|
+
if (lockfiles["pnpm-lock.yaml"]) return "pnpm";
|
|
35
|
+
if (lockfiles["bun.lockb"]) return "bun";
|
|
36
|
+
if (lockfiles["yarn.lock"]) return "yarn";
|
|
37
|
+
if (lockfiles["package-lock.json"]) return "npm";
|
|
38
|
+
return "npm";
|
|
39
|
+
}
|
|
40
|
+
var KNOWN_PACKAGES = {
|
|
41
|
+
bun: {
|
|
42
|
+
kind: "bundled",
|
|
43
|
+
usage: "theokit build --target=bun"
|
|
44
|
+
},
|
|
45
|
+
deno: {
|
|
46
|
+
kind: "bundled",
|
|
47
|
+
usage: "theokit build --target=deno-deploy"
|
|
48
|
+
},
|
|
49
|
+
netlify: {
|
|
50
|
+
kind: "bundled",
|
|
51
|
+
usage: "theokit build --target=netlify"
|
|
52
|
+
},
|
|
53
|
+
"aws-lambda": {
|
|
54
|
+
kind: "bundled",
|
|
55
|
+
usage: "theokit build --target=aws-lambda"
|
|
56
|
+
},
|
|
57
|
+
static: {
|
|
58
|
+
kind: "bundled",
|
|
59
|
+
usage: "theokit build --target=static"
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
function levenshtein(a, b) {
|
|
63
|
+
const m = a.length;
|
|
64
|
+
const n = b.length;
|
|
65
|
+
if (m === 0) return n;
|
|
66
|
+
if (n === 0) return m;
|
|
67
|
+
const dp = Array.from(
|
|
68
|
+
{ length: m + 1 },
|
|
69
|
+
() => Array.from({ length: n + 1 }).fill(0)
|
|
70
|
+
);
|
|
71
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
72
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
73
|
+
for (let i = 1; i <= m; i++) {
|
|
74
|
+
for (let j = 1; j <= n; j++) {
|
|
75
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
76
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return dp[m][n];
|
|
80
|
+
}
|
|
81
|
+
function findSuggestion(input, candidates) {
|
|
82
|
+
let best = null;
|
|
83
|
+
for (const c of candidates) {
|
|
84
|
+
const d = levenshtein(input, c);
|
|
85
|
+
if (best === null || d < best.d) best = { name: c, d };
|
|
86
|
+
}
|
|
87
|
+
if (best && best.d <= 3) return best.name;
|
|
88
|
+
return void 0;
|
|
89
|
+
}
|
|
90
|
+
async function runAdd(deps) {
|
|
91
|
+
validatePackageInput(deps.input);
|
|
92
|
+
const registry = deps.registry ?? KNOWN_PACKAGES;
|
|
93
|
+
if (!Object.hasOwn(registry, deps.input)) {
|
|
94
|
+
const suggestion = findSuggestion(deps.input, Object.keys(registry));
|
|
95
|
+
throw new UnknownPackageError(deps.input, suggestion);
|
|
96
|
+
}
|
|
97
|
+
const entry = registry[deps.input];
|
|
98
|
+
if (entry.kind === "bundled") {
|
|
99
|
+
return {
|
|
100
|
+
packageInstalled: `${deps.input} (bundled inside theokit)`,
|
|
101
|
+
packageManager: "bundled",
|
|
102
|
+
usage: entry.usage,
|
|
103
|
+
exitCode: 0
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!entry.npm) {
|
|
107
|
+
throw new Error(`External package "${deps.input}" missing npm field in registry`);
|
|
108
|
+
}
|
|
109
|
+
const existsFn = deps.existsSync ?? ((p) => existsSync(p));
|
|
110
|
+
const lockfiles = {
|
|
111
|
+
"pnpm-lock.yaml": existsFn(resolve(deps.cwd, "pnpm-lock.yaml")),
|
|
112
|
+
"bun.lockb": existsFn(resolve(deps.cwd, "bun.lockb")),
|
|
113
|
+
"yarn.lock": existsFn(resolve(deps.cwd, "yarn.lock")),
|
|
114
|
+
"package-lock.json": existsFn(resolve(deps.cwd, "package-lock.json"))
|
|
115
|
+
};
|
|
116
|
+
const pm = detectPackageManager(lockfiles);
|
|
117
|
+
const args = pm === "npm" ? ["install", entry.npm] : ["add", entry.npm];
|
|
118
|
+
const spawnFn = deps.spawnPm ?? defaultSpawnPm;
|
|
119
|
+
const result = await spawnFn(pm, args, { cwd: deps.cwd, shell: false });
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
throw new Error(`Package manager exited with non-zero exit code ${result.code}`);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
packageInstalled: entry.npm,
|
|
125
|
+
packageManager: pm,
|
|
126
|
+
usage: entry.usage,
|
|
127
|
+
exitCode: 0
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function defaultSpawnPm(cmd, args, opts) {
|
|
131
|
+
return new Promise((resolve2) => {
|
|
132
|
+
const proc = spawn(cmd, args, { cwd: opts.cwd, shell: opts.shell, stdio: "inherit" });
|
|
133
|
+
proc.on("close", (code) => {
|
|
134
|
+
resolve2({ ok: code === 0, code: code ?? 1 });
|
|
135
|
+
});
|
|
136
|
+
proc.on("error", () => {
|
|
137
|
+
resolve2({ ok: false, code: 1 });
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async function addCommand(input) {
|
|
142
|
+
try {
|
|
143
|
+
const result = await runAdd({ input, cwd: process.cwd() });
|
|
144
|
+
console.log(`
|
|
145
|
+
\u2713 Installed ${result.packageInstalled} via ${result.packageManager}
|
|
146
|
+
`);
|
|
147
|
+
console.log(` Next: import { /* ... */ } from '${result.packageInstalled}'
|
|
148
|
+
`);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(`
|
|
151
|
+
\u2717 ${err.message}
|
|
152
|
+
`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
InvalidPackageNameError,
|
|
158
|
+
KNOWN_PACKAGES,
|
|
159
|
+
UnknownPackageError,
|
|
160
|
+
addCommand,
|
|
161
|
+
detectPackageManager,
|
|
162
|
+
runAdd,
|
|
163
|
+
validatePackageInput
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=add-2ZFFGGE4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/add.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * CLI `theo add` — detects pkg manager + writes manifests under user `cwd`.\n * Build-time tool. No HTTP input.\n */\nimport { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nexport class InvalidPackageNameError extends Error {\n constructor(input: string, reason: string) {\n super(`Invalid package name \"${input}\": ${reason}`)\n this.name = 'InvalidPackageNameError'\n }\n}\n\nexport class UnknownPackageError extends Error {\n constructor(input: string, suggestion?: string) {\n super(\n suggestion\n ? `Unknown package: ${input}. Did you mean: ${suggestion}?`\n : `Unknown package: ${input}.`,\n )\n this.name = 'UnknownPackageError'\n }\n}\n\n/**\n * EC-4 — strict whitelist of characters allowed in package input.\n * Lowercase ASCII letters, digits, hyphens. Must start with letter/digit.\n * Rejects: shell metacharacters, path traversal, scope syntax, uppercase.\n */\nconst PACKAGE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/\n\nexport function validatePackageInput(input: string): true {\n if (typeof input !== 'string' || input.length === 0) {\n throw new InvalidPackageNameError(input, 'must be a non-empty string')\n }\n if (!PACKAGE_NAME_REGEX.test(input)) {\n throw new InvalidPackageNameError(input, 'allowed: lowercase letters, digits, hyphens only')\n }\n return true\n}\n\nexport type PackageManager = 'pnpm' | 'bun' | 'yarn' | 'npm'\n\nexport function detectPackageManager(lockfiles: Record<string, boolean>): PackageManager {\n if (lockfiles['pnpm-lock.yaml']) return 'pnpm'\n if (lockfiles['bun.lockb']) return 'bun'\n if (lockfiles['yarn.lock']) return 'yarn'\n if (lockfiles['package-lock.json']) return 'npm'\n return 'npm'\n}\n\nexport interface PackageRegistryEntry {\n /** 'bundled' = adapter already shipped inside theokit; no npm install needed.\n * 'external' = adapter/plugin lives as a separate npm package. */\n kind: 'bundled' | 'external'\n /** Required when kind === 'external'. */\n npm?: string\n /** Usage hint printed to the user (e.g. `theokit build --target=bun`). */\n usage: string\n}\n\nexport const KNOWN_PACKAGES: Record<string, PackageRegistryEntry> = {\n bun: {\n kind: 'bundled',\n usage: 'theokit build --target=bun',\n },\n deno: {\n kind: 'bundled',\n usage: 'theokit build --target=deno-deploy',\n },\n netlify: {\n kind: 'bundled',\n usage: 'theokit build --target=netlify',\n },\n 'aws-lambda': {\n kind: 'bundled',\n usage: 'theokit build --target=aws-lambda',\n },\n static: {\n kind: 'bundled',\n usage: 'theokit build --target=static',\n },\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length\n const n = b.length\n if (m === 0) return n\n if (n === 0) return m\n const dp: number[][] = Array.from({ length: m + 1 }, () =>\n Array.from<number>({ length: n + 1 }).fill(0),\n )\n for (let i = 0; i <= m; i++) dp[i][0] = i\n for (let j = 0; j <= n; j++) dp[0][j] = j\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1\n dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost)\n }\n }\n return dp[m][n]\n}\n\nfunction findSuggestion(input: string, candidates: string[]): string | undefined {\n let best: { name: string; d: number } | null = null\n for (const c of candidates) {\n const d = levenshtein(input, c)\n if (best === null || d < best.d) best = { name: c, d }\n }\n if (best && best.d <= 3) return best.name\n return undefined\n}\n\nexport interface RunAddDeps {\n input: string\n cwd: string\n existsSync?: (path: string) => boolean\n spawnPm?: (\n cmd: string,\n args: string[],\n opts: { cwd: string; shell: boolean },\n ) => Promise<{ ok: boolean; code: number }>\n registry?: Record<string, PackageRegistryEntry>\n}\n\nexport interface AddResult {\n packageInstalled: string\n packageManager: PackageManager | 'bundled'\n usage: string\n exitCode: 0\n}\n\nexport async function runAdd(deps: RunAddDeps): Promise<AddResult> {\n validatePackageInput(deps.input)\n\n const registry = deps.registry ?? KNOWN_PACKAGES\n if (!Object.hasOwn(registry, deps.input)) {\n const suggestion = findSuggestion(deps.input, Object.keys(registry))\n throw new UnknownPackageError(deps.input, suggestion)\n }\n const entry = registry[deps.input]\n\n // T6.1 — bundled adapters need no install\n if (entry.kind === 'bundled') {\n return {\n packageInstalled: `${deps.input} (bundled inside theokit)`,\n packageManager: 'bundled',\n usage: entry.usage,\n exitCode: 0,\n }\n }\n\n if (!entry.npm) {\n throw new Error(`External package \"${deps.input}\" missing npm field in registry`)\n }\n\n const existsFn = deps.existsSync ?? ((p: string) => existsSync(p))\n const lockfiles = {\n 'pnpm-lock.yaml': existsFn(resolve(deps.cwd, 'pnpm-lock.yaml')),\n 'bun.lockb': existsFn(resolve(deps.cwd, 'bun.lockb')),\n 'yarn.lock': existsFn(resolve(deps.cwd, 'yarn.lock')),\n 'package-lock.json': existsFn(resolve(deps.cwd, 'package-lock.json')),\n }\n const pm = detectPackageManager(lockfiles)\n\n const args = pm === 'npm' ? ['install', entry.npm] : ['add', entry.npm]\n const spawnFn = deps.spawnPm ?? defaultSpawnPm\n const result = await spawnFn(pm, args, { cwd: deps.cwd, shell: false })\n\n if (!result.ok) {\n throw new Error(`Package manager exited with non-zero exit code ${result.code}`)\n }\n\n return {\n packageInstalled: entry.npm,\n packageManager: pm,\n usage: entry.usage,\n exitCode: 0,\n }\n}\n\nfunction defaultSpawnPm(\n cmd: string,\n args: string[],\n opts: { cwd: string; shell: boolean },\n): Promise<{ ok: boolean; code: number }> {\n return new Promise((resolve) => {\n const proc = spawn(cmd, args, { cwd: opts.cwd, shell: opts.shell, stdio: 'inherit' })\n proc.on('close', (code) => {\n resolve({ ok: code === 0, code: code ?? 1 })\n })\n proc.on('error', () => {\n resolve({ ok: false, code: 1 })\n })\n })\n}\n\nexport async function addCommand(input: string): Promise<void> {\n try {\n const result = await runAdd({ input, cwd: process.cwd() })\n console.log(`\\n ✓ Installed ${result.packageInstalled} via ${result.packageManager}\\n`)\n console.log(` Next: import { /* ... */ } from '${result.packageInstalled}'\\n`)\n } catch (err) {\n console.error(`\\n ✗ ${(err as Error).message}\\n`)\n process.exit(1)\n }\n}\n"],"mappings":"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","names":["resolve"]}
|