@b9g/platform-cloudflare 0.1.5 → 0.1.6
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/package.json +3 -3
- package/src/filesystem-assets.js +7 -2
- package/src/index.d.ts +1 -1
- package/src/index.js +69 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/platform-cloudflare",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Cloudflare Workers platform adapter for Shovel - already ServiceWorker-based!",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shovel",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"serviceworker"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@b9g/assets": "^0.1.
|
|
14
|
+
"@b9g/assets": "^0.1.12",
|
|
15
15
|
"@b9g/cache": "^0.1.4",
|
|
16
|
-
"@b9g/platform": "^0.1.
|
|
16
|
+
"@b9g/platform": "^0.1.9",
|
|
17
17
|
"@cloudflare/workers-types": "^4.20241218.0",
|
|
18
18
|
"miniflare": "^4.20251118.1"
|
|
19
19
|
},
|
package/src/filesystem-assets.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/// <reference types="./filesystem-assets.d.ts" />
|
|
2
2
|
// src/filesystem-assets.ts
|
|
3
3
|
var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
|
|
4
|
-
kind
|
|
4
|
+
kind;
|
|
5
5
|
name;
|
|
6
6
|
#assets;
|
|
7
7
|
#basePath;
|
|
8
8
|
constructor(assets, basePath = "/") {
|
|
9
|
+
this.kind = "directory";
|
|
9
10
|
this.#assets = assets;
|
|
10
11
|
this.#basePath = basePath.endsWith("/") ? basePath : basePath + "/";
|
|
11
12
|
this.name = basePath.split("/").filter(Boolean).pop() || "assets";
|
|
@@ -32,18 +33,21 @@ var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
|
|
|
32
33
|
async resolve(_possibleDescendant) {
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
36
|
+
// eslint-disable-next-line require-yield
|
|
35
37
|
async *entries() {
|
|
36
38
|
throw new DOMException(
|
|
37
39
|
"Directory listing not supported for ASSETS binding. Use an asset manifest for enumeration.",
|
|
38
40
|
"NotSupportedError"
|
|
39
41
|
);
|
|
40
42
|
}
|
|
43
|
+
// eslint-disable-next-line require-yield
|
|
41
44
|
async *keys() {
|
|
42
45
|
throw new DOMException(
|
|
43
46
|
"Directory listing not supported for ASSETS binding",
|
|
44
47
|
"NotSupportedError"
|
|
45
48
|
);
|
|
46
49
|
}
|
|
50
|
+
// eslint-disable-next-line require-yield
|
|
47
51
|
async *values() {
|
|
48
52
|
throw new DOMException(
|
|
49
53
|
"Directory listing not supported for ASSETS binding",
|
|
@@ -60,11 +64,12 @@ var CFAssetsDirectoryHandle = class _CFAssetsDirectoryHandle {
|
|
|
60
64
|
}
|
|
61
65
|
};
|
|
62
66
|
var CFAssetsFileHandle = class _CFAssetsFileHandle {
|
|
63
|
-
kind
|
|
67
|
+
kind;
|
|
64
68
|
name;
|
|
65
69
|
#assets;
|
|
66
70
|
#path;
|
|
67
71
|
constructor(assets, path, name) {
|
|
72
|
+
this.kind = "file";
|
|
68
73
|
this.#assets = assets;
|
|
69
74
|
this.#path = path;
|
|
70
75
|
this.name = name;
|
package/src/index.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare const cloudflareWorkerBanner = "// Cloudflare Worker ES Module wr
|
|
|
65
65
|
/**
|
|
66
66
|
* Generate footer code for ServiceWorker → ES Module conversion
|
|
67
67
|
*/
|
|
68
|
-
export declare const cloudflareWorkerFooter = "\n// Export ES Module for Cloudflare Workers\nexport default {\n\tasync fetch(request, env, ctx) {\n\t\ttry {\n\t\t\t// Set up ServiceWorker-like dirs API for bundled deployment\n\t\t\tif (!globalThis.self.dirs) {\n\t\t\t\t// For bundled deployment, assets are served via static middleware\n\t\t\t\t// not through the dirs API\n\t\t\t\tglobalThis.self.dirs = {\n\t\t\t\t\tasync open(directoryName) {\n\t\t\t\t\t\tif (directoryName === 'assets') {\n\t\t\t\t\t\t\t// Return a minimal interface that indicates no files available\n\t\t\t\t\t\t\t// The assets middleware will fall back to dev mode behavior\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tasync getFileHandle(fileName) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`NotFoundError: ${fileName} not found in bundled assets`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new Error(`Directory ${directoryName} not available in bundled deployment`);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t\t\n\t\t\t// Set up caches API\n\t\t\tif (!globalThis.self.caches) {\n\t\t\t\tglobalThis.self.caches = globalThis.caches;\n\t\t\t}\n\t\t\t\n\t\t\t// Ensure request.url is a string\n\t\t\tif (typeof request.url !== 'string') {\n\t\t\t\treturn new Response('Invalid request URL: ' + typeof request.url, { status: 500 });\n\t\t\t}\n\t\t\t\n\t\t\t// Create proper FetchEvent-like object\n\t\t\tlet responseReceived = null;\n\t\t\tconst event = { \n\t\t\t\trequest, \n\t\t\t\trespondWith: (response) => { responseReceived = response; }\n\t\t\t};\n\t\t\t\n\t\t\t//
|
|
68
|
+
export declare const cloudflareWorkerFooter = "\n// Export ES Module for Cloudflare Workers\nexport default {\n\tasync fetch(request, env, ctx) {\n\t\ttry {\n\t\t\t// Set up ServiceWorker-like dirs API for bundled deployment\n\t\t\tif (!globalThis.self.dirs) {\n\t\t\t\t// For bundled deployment, assets are served via static middleware\n\t\t\t\t// not through the dirs API\n\t\t\t\tglobalThis.self.dirs = {\n\t\t\t\t\tasync open(directoryName) {\n\t\t\t\t\t\tif (directoryName === 'assets') {\n\t\t\t\t\t\t\t// Return a minimal interface that indicates no files available\n\t\t\t\t\t\t\t// The assets middleware will fall back to dev mode behavior\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tasync getFileHandle(fileName) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`NotFoundError: ${fileName} not found in bundled assets`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new Error(`Directory ${directoryName} not available in bundled deployment`);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t\t\n\t\t\t// Set up caches API\n\t\t\tif (!globalThis.self.caches) {\n\t\t\t\tglobalThis.self.caches = globalThis.caches;\n\t\t\t}\n\t\t\t\n\t\t\t// Ensure request.url is a string\n\t\t\tif (typeof request.url !== 'string') {\n\t\t\t\treturn new Response('Invalid request URL: ' + typeof request.url, { status: 500 });\n\t\t\t}\n\t\t\t\n\t\t\t// Create proper FetchEvent-like object\n\t\t\tlet responseReceived = null;\n\t\t\tconst event = { \n\t\t\t\trequest, \n\t\t\t\trespondWith: (response) => { responseReceived = response; }\n\t\t\t};\n\t\t\t\n\t\t\t// Helper for error responses\n\t\t\tconst createErrorResponse = (err) => {\n\t\t\t\tconst isDev = typeof import.meta !== \"undefined\" && import.meta.env?.MODE !== \"production\";\n\t\t\t\tif (isDev) {\n\t\t\t\t\tconst escapeHtml = (str) => str.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n\t\t\t\t\treturn new Response(`<!DOCTYPE html>\n<html>\n<head>\n <title>500 Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }\n h1 { color: #c00; }\n .message { font-size: 1.2em; color: #333; }\n pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }\n </style>\n</head>\n<body>\n <h1>500 Internal Server Error</h1>\n <p class=\"message\">${escapeHtml(err.message)}</p>\n <pre>${escapeHtml(err.stack || \"No stack trace available\")}</pre>\n</body>\n</html>`, { status: 500, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n\t\t\t\t} else {\n\t\t\t\t\treturn new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain\" } });\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Dispatch to ServiceWorker fetch handlers\n\t\t\tfor (const handler of fetchHandlers) {\n\t\t\t\ttry {\n\t\t\t\t\tlogger.debug(\"Calling handler\", {url: request.url});\n\t\t\t\t\tawait handler(event);\n\t\t\t\t\tlogger.debug(\"Handler completed\", {hasResponse: !!responseReceived});\n\t\t\t\t\tif (responseReceived) {\n\t\t\t\t\t\treturn responseReceived;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(\"Handler error\", {error});\n\t\t\t\t\tlogger.error(\"Error stack\", {stack: error.stack});\n\t\t\t\t\treturn createErrorResponse(error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn new Response('No ServiceWorker handler', { status: 404 });\n\t\t} catch (topLevelError) {\n\t\t\tlogger.error(\"Top-level error\", {error: topLevelError});\n\t\t\tconst isDev = typeof import.meta !== \"undefined\" && import.meta.env?.MODE !== \"production\";\n\t\t\tif (isDev) {\n\t\t\t\tconst escapeHtml = (str) => String(str).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n\t\t\t\treturn new Response(`<!DOCTYPE html>\n<html>\n<head>\n <title>500 Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }\n h1 { color: #c00; }\n .message { font-size: 1.2em; color: #333; }\n pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }\n </style>\n</head>\n<body>\n <h1>500 Internal Server Error</h1>\n <p class=\"message\">${escapeHtml(topLevelError.message)}</p>\n <pre>${escapeHtml(topLevelError.stack || \"No stack trace available\")}</pre>\n</body>\n</html>`, { status: 500, headers: { \"Content-Type\": \"text/html; charset=utf-8\" } });\n\t\t\t} else {\n\t\t\t\treturn new Response(\"Internal Server Error\", { status: 500, headers: { \"Content-Type\": \"text/plain\" } });\n\t\t\t}\n\t\t}\n\t}\n};";
|
|
69
69
|
/**
|
|
70
70
|
* Default export for easy importing
|
|
71
71
|
*/
|
package/src/index.js
CHANGED
|
@@ -8,12 +8,15 @@ var logger = getLogger(["platform-cloudflare"]);
|
|
|
8
8
|
var CloudflarePlatform = class extends BasePlatform {
|
|
9
9
|
name;
|
|
10
10
|
#options;
|
|
11
|
-
#miniflare
|
|
12
|
-
#assetsMiniflare
|
|
11
|
+
#miniflare;
|
|
12
|
+
#assetsMiniflare;
|
|
13
13
|
// Separate instance for ASSETS binding
|
|
14
|
-
#assetsBinding
|
|
14
|
+
#assetsBinding;
|
|
15
15
|
constructor(options = {}) {
|
|
16
16
|
super(options);
|
|
17
|
+
this.#miniflare = null;
|
|
18
|
+
this.#assetsMiniflare = null;
|
|
19
|
+
this.#assetsBinding = null;
|
|
17
20
|
this.name = "cloudflare";
|
|
18
21
|
this.#options = {
|
|
19
22
|
environment: "production",
|
|
@@ -115,7 +118,17 @@ var CloudflarePlatform = class extends BasePlatform {
|
|
|
115
118
|
const instance = {
|
|
116
119
|
runtime: mf,
|
|
117
120
|
handleRequest: async (request) => {
|
|
118
|
-
|
|
121
|
+
const cfResponse = await mf.dispatchFetch(request.url, {
|
|
122
|
+
method: request.method,
|
|
123
|
+
headers: request.headers,
|
|
124
|
+
body: request.body,
|
|
125
|
+
duplex: request.body ? "half" : void 0
|
|
126
|
+
});
|
|
127
|
+
return new Response(cfResponse.body, {
|
|
128
|
+
status: cfResponse.status,
|
|
129
|
+
statusText: cfResponse.statusText,
|
|
130
|
+
headers: cfResponse.headers
|
|
131
|
+
});
|
|
119
132
|
},
|
|
120
133
|
install: () => Promise.resolve(),
|
|
121
134
|
activate: () => Promise.resolve(),
|
|
@@ -315,6 +328,33 @@ export default {
|
|
|
315
328
|
respondWith: (response) => { responseReceived = response; }
|
|
316
329
|
};
|
|
317
330
|
|
|
331
|
+
// Helper for error responses
|
|
332
|
+
const createErrorResponse = (err) => {
|
|
333
|
+
const isDev = typeof import.meta !== "undefined" && import.meta.env?.MODE !== "production";
|
|
334
|
+
if (isDev) {
|
|
335
|
+
const escapeHtml = (str) => str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
336
|
+
return new Response(\`<!DOCTYPE html>
|
|
337
|
+
<html>
|
|
338
|
+
<head>
|
|
339
|
+
<title>500 Internal Server Error</title>
|
|
340
|
+
<style>
|
|
341
|
+
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
|
342
|
+
h1 { color: #c00; }
|
|
343
|
+
.message { font-size: 1.2em; color: #333; }
|
|
344
|
+
pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }
|
|
345
|
+
</style>
|
|
346
|
+
</head>
|
|
347
|
+
<body>
|
|
348
|
+
<h1>500 Internal Server Error</h1>
|
|
349
|
+
<p class="message">\${escapeHtml(err.message)}</p>
|
|
350
|
+
<pre>\${escapeHtml(err.stack || "No stack trace available")}</pre>
|
|
351
|
+
</body>
|
|
352
|
+
</html>\`, { status: 500, headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
353
|
+
} else {
|
|
354
|
+
return new Response("Internal Server Error", { status: 500, headers: { "Content-Type": "text/plain" } });
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
318
358
|
// Dispatch to ServiceWorker fetch handlers
|
|
319
359
|
for (const handler of fetchHandlers) {
|
|
320
360
|
try {
|
|
@@ -327,31 +367,36 @@ export default {
|
|
|
327
367
|
} catch (error) {
|
|
328
368
|
logger.error("Handler error", {error});
|
|
329
369
|
logger.error("Error stack", {stack: error.stack});
|
|
330
|
-
|
|
331
|
-
return new Response(JSON.stringify({
|
|
332
|
-
error: error.message,
|
|
333
|
-
stack: error.stack,
|
|
334
|
-
name: error.name,
|
|
335
|
-
url: request.url
|
|
336
|
-
}, null, 2), {
|
|
337
|
-
status: 500,
|
|
338
|
-
headers: { 'Content-Type': 'application/json' }
|
|
339
|
-
});
|
|
370
|
+
return createErrorResponse(error);
|
|
340
371
|
}
|
|
341
372
|
}
|
|
342
|
-
|
|
373
|
+
|
|
343
374
|
return new Response('No ServiceWorker handler', { status: 404 });
|
|
344
375
|
} catch (topLevelError) {
|
|
345
376
|
logger.error("Top-level error", {error: topLevelError});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
377
|
+
const isDev = typeof import.meta !== "undefined" && import.meta.env?.MODE !== "production";
|
|
378
|
+
if (isDev) {
|
|
379
|
+
const escapeHtml = (str) => String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
380
|
+
return new Response(\`<!DOCTYPE html>
|
|
381
|
+
<html>
|
|
382
|
+
<head>
|
|
383
|
+
<title>500 Internal Server Error</title>
|
|
384
|
+
<style>
|
|
385
|
+
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
|
386
|
+
h1 { color: #c00; }
|
|
387
|
+
.message { font-size: 1.2em; color: #333; }
|
|
388
|
+
pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }
|
|
389
|
+
</style>
|
|
390
|
+
</head>
|
|
391
|
+
<body>
|
|
392
|
+
<h1>500 Internal Server Error</h1>
|
|
393
|
+
<p class="message">\${escapeHtml(topLevelError.message)}</p>
|
|
394
|
+
<pre>\${escapeHtml(topLevelError.stack || "No stack trace available")}</pre>
|
|
395
|
+
</body>
|
|
396
|
+
</html>\`, { status: 500, headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
397
|
+
} else {
|
|
398
|
+
return new Response("Internal Server Error", { status: 500, headers: { "Content-Type": "text/plain" } });
|
|
399
|
+
}
|
|
355
400
|
}
|
|
356
401
|
}
|
|
357
402
|
};`;
|