@torpor/adapter-cloudflare 0.2.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,20 @@
2
2
 
3
3
  A @torpor/build adapter for deploying to Cloudflare Pages.
4
4
 
5
- 🚧 WARNING: WORK IN PROGRESS 🚧
6
-
7
5
  ## Installation
8
6
 
9
- TODO:
7
+ Install the adapter using `npm` (or your preferred package manager):
8
+
9
+ ```bash
10
+ npm install @torpor/adapter-cloudflare
11
+ ```
12
+
13
+ Then use the adapter in your `site.config`:
14
+
15
+ ```javascript
16
+ import { cloudflare } from "@torpor/adapter-cloudflare";
17
+ import { Site } from "@torpor/build";
18
+
19
+ const site: Site = new Site();
20
+ site.adapter = cloudflare;
21
+ ```
@@ -0,0 +1,35 @@
1
+ // The dev counterpart of the production `_worker.ts`. This file is loaded by
2
+ // `@cloudflare/vite-plugin` into workerd (via the Vite module runner, so HMR
3
+ // works) and is pointed at by the `main` override in the adapter's dev plugin.
4
+ //
5
+ // Imports here resolve from the consuming site's `node_modules` (just like the
6
+ // Node-runtime dev plugin's `vite.ssrLoadModule` paths), and the template is
7
+ // provided by the `virtual:torpor-template` module exposed by the adapter's
8
+ // companion Vite plugin (which applies `transformIndexHtml` Node-side).
9
+
10
+ import { Server, load } from "@torpor/build/dev";
11
+ // @ts-ignore
12
+ import template from "virtual:torpor-template";
13
+
14
+ // Send all requests through Server to handle middleware, cookies, headers, etc
15
+ const server = new Server();
16
+ server.add("*", async (ev: any) => {
17
+ try {
18
+ return await load(ev, template);
19
+ } catch (e: any) {
20
+ console.log("ERROR:", e);
21
+ return new Response(null, {
22
+ status: 500,
23
+ });
24
+ }
25
+ });
26
+
27
+ export default {
28
+ async fetch(request: Request, env: any): Promise<Response> {
29
+ // Put adapter-specific functionality in the `adapter` property of
30
+ // globalThis for now, matching the production _worker.ts
31
+ // @ts-ignore
32
+ globalThis.adapter = { env };
33
+ return await server.fetch(request);
34
+ },
35
+ };
@@ -0,0 +1,47 @@
1
+ // @ts-ignore
2
+ import Server from "%SERVER_CLASS%";
3
+ // @ts-ignore
4
+ import { load } from "%SERVER_SCRIPT%";
5
+
6
+ // This file has the above imports replaced and is then compiled to create a
7
+ // Cloudflare Pages worker file
8
+
9
+ // The backticks around %HTML_TEMPLATE% keep this file parseable; postbuild
10
+ // replaces the whole literal with a JSON string, or `undefined` for
11
+ // endpoints-only sites with no site.html
12
+ const template: string | undefined = `%HTML_TEMPLATE%`;
13
+
14
+ // Send all requests through Server to handle middleware, cookies, headers, etc
15
+ const server = new Server();
16
+ server.add("*", async (ev: any) => {
17
+ try {
18
+ // Render the app HTML (or fetch server data etc) via serverEntry's
19
+ // exported `load` function
20
+ return await load(ev, template);
21
+ } catch (e: any) {
22
+ // TODO: Message?
23
+ console.log("ERROR:", e);
24
+ return new Response(null, {
25
+ status: 500,
26
+ });
27
+ }
28
+ });
29
+
30
+ export default {
31
+ async fetch(request: Request, env: any): Promise<any> {
32
+ // Put adapter-specific functionality in the `adapter` property of
33
+ // globalThis for now
34
+ // @ts-ignore
35
+ globalThis.adapter = { env };
36
+
37
+ const url = new URL(request.url);
38
+
39
+ // Serve dist/client/assets statically
40
+ if (url.pathname.startsWith("/assets/")) {
41
+ return env.ASSETS.fetch(request);
42
+ }
43
+
44
+ // Every request (GET, POST, etc) goes through loadEndPoint
45
+ return await server.fetch(request);
46
+ },
47
+ };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Adapter } from "@torpor/build";
2
-
3
2
  //#region src/adapter.d.ts
4
3
  declare const _default: Adapter;
5
4
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter.ts"],"sourcesContent":[],"mappings":";;;cAMA,UAKuB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter.ts"],"mappings":";;cASA,UAwBuB"}
package/dist/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
- import { promises } from "node:fs";
1
+ import { createRequire } from "node:module";
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync, promises } from "node:fs";
2
4
  import path from "node:path";
3
5
  import { build, defineConfig } from "vite";
4
-
6
+ import { cloudflare } from "@cloudflare/vite-plugin";
5
7
  //#region src/prepareTemplate.ts
6
8
  function prepareTemplate(template, clientScript) {
7
9
  let result = template;
@@ -20,13 +22,77 @@ function regexIndexOf(string, regex, position) {
20
22
  var indexOf = string.substring(position || 0).search(regex);
21
23
  return indexOf >= 0 ? indexOf + (position || 0) : indexOf;
22
24
  }
23
-
25
+ //#endregion
26
+ //#region src/cloudflareDev.ts
27
+ const TEMPLATE_ID = "virtual:torpor-template";
28
+ const RESOLVED_TEMPLATE_ID = `\0${TEMPLATE_ID}`;
29
+ /**
30
+ * Vite plugin(s) for Cloudflare dev. Delegates the workerd runtime + bindings +
31
+ * HMR to `@cloudflare/vite-plugin` (assigning the Worker to Vite's `ssr`
32
+ * environment, so torpor's server code runs in workerd), overriding `main` to
33
+ * point at the adapter's dev worker, and exposing the prepared `site.html`
34
+ * template through a virtual module (since `transformIndexHtml` has to run
35
+ * Node-side but the template is consumed inside workerd).
36
+ */
37
+ function cloudflareDev(site) {
38
+ let viteServer;
39
+ let templatePromise;
40
+ return [{
41
+ name: "torpor-cloudflare-template",
42
+ configureServer(server) {
43
+ viteServer = server;
44
+ },
45
+ resolveId(id) {
46
+ if (id === TEMPLATE_ID) return RESOLVED_TEMPLATE_ID;
47
+ },
48
+ async load(id) {
49
+ if (id !== RESOLVED_TEMPLATE_ID) return;
50
+ templatePromise ??= buildDevTemplate(viteServer, site);
51
+ return `export default ${JSON.stringify(await templatePromise)};`;
52
+ }
53
+ }, ...cloudflare({
54
+ viteEnvironment: { name: "ssr" },
55
+ config: { main: "./node_modules/@torpor/adapter-cloudflare/dist/_worker.dev.ts" }
56
+ })];
57
+ }
58
+ async function buildDevTemplate(vite, site) {
59
+ const templateFile = path.resolve(site.root, "src/site.html");
60
+ if (!existsSync(templateFile)) return;
61
+ let template = await promises.readFile(templateFile, "utf-8");
62
+ template = await vite.transformIndexHtml("", template);
63
+ const siteFolder = path.resolve(site.root, "./node_modules/@torpor/build/src/site/");
64
+ const clientScript = path.join(siteFolder, "clientEntry.ts");
65
+ const clientDevScript = path.join(siteFolder, "clientEntryDev.ts");
66
+ template = prepareTemplate(template, clientScript);
67
+ const clientTag = `<script type="module" src="${clientScript}"><\/script>`;
68
+ template = template.replace(clientTag, `<script type="module" src="${clientDevScript}"><\/script>\n${clientTag}`);
69
+ return template;
70
+ }
24
71
  //#endregion
25
72
  //#region src/adapter.ts
26
73
  var adapter_default = {
27
74
  postbuild,
28
- serve: () => {
29
- console.log("TODO: Cloudflare dev server");
75
+ dev: (site) => cloudflareDev(site),
76
+ serve: (_server, site) => {
77
+ const args = [
78
+ "dev",
79
+ "--config",
80
+ path.join(site.root, "dist", "wrangler.toml")
81
+ ];
82
+ let child;
83
+ try {
84
+ const wranglerBin = createRequire(path.join(site.root, "package.json")).resolve("wrangler/bin/wrangler.js");
85
+ child = spawn(process.execPath, [wranglerBin, ...args], {
86
+ stdio: "inherit",
87
+ cwd: site.root
88
+ });
89
+ } catch {
90
+ child = spawn("wrangler", args, {
91
+ stdio: "inherit",
92
+ cwd: site.root
93
+ });
94
+ }
95
+ child.on("exit", (code) => process.exit(code ?? 0));
30
96
  }
31
97
  };
32
98
  async function postbuild(site) {
@@ -36,18 +102,20 @@ async function postbuild(site) {
36
102
  const tempFolder = path.join(distFolder, "temp");
37
103
  const cloudflareFolder = path.join(distFolder, "cloudflare");
38
104
  const buildSrcFolder = path.resolve(site.root, "./node_modules/@torpor/build/src/");
39
- const adapterSrcFolder = path.resolve(site.root, "./node_modules/@torpor/adapter-cloudflare/src/");
40
- let workerFile = path.join(adapterSrcFolder, "_worker.ts");
105
+ const adapterDistFolder = path.resolve(site.root, "./node_modules/@torpor/adapter-cloudflare/dist/");
106
+ let workerFile = path.join(adapterDistFolder, "_worker.ts");
41
107
  let workerSource = await promises.readFile(workerFile, "utf-8");
42
- let clientScript = (await promises.readdir(path.join(clientFolder, "assets"))).find((f) => f.startsWith("clientEntry-"));
43
- if (!clientScript) throw new Error("clientEntry.js not found");
44
- clientScript = `/assets/${clientScript}`;
45
- let siteHtml = path.join(clientFolder, "site.html");
46
- let template = await promises.readFile(siteHtml, "utf-8");
47
- template = prepareTemplate(template, clientScript);
108
+ let template;
109
+ const siteHtml = path.join(clientFolder, "site.html");
110
+ if (existsSync(siteHtml)) {
111
+ let clientScript = (await promises.readdir(path.join(clientFolder, "assets"))).find((f) => f.startsWith("clientEntry-"));
112
+ if (!clientScript) throw new Error("clientEntry.js not found");
113
+ clientScript = `/assets/${clientScript}`;
114
+ template = prepareTemplate(await promises.readFile(siteHtml, "utf-8"), clientScript);
115
+ }
48
116
  const serverClass = path.join(buildSrcFolder, "server", "Server.ts");
49
117
  const serverScript = path.join(serverFolder, "serverEntry.js");
50
- workerSource = workerSource.replace("%SERVER_CLASS%", serverClass).replace("%SERVER_SCRIPT%", serverScript).replace("%HTML_TEMPLATE%", template);
118
+ workerSource = workerSource.replace("%SERVER_CLASS%", serverClass).replace("%SERVER_SCRIPT%", serverScript).replace("`%HTML_TEMPLATE%`", () => template ? JSON.stringify(template) : "undefined");
51
119
  workerFile = path.join(tempFolder, "_worker.ts");
52
120
  await promises.mkdir(tempFolder);
53
121
  await promises.writeFile(workerFile, workerSource);
@@ -88,7 +156,7 @@ _worker.js
88
156
  await promises.writeFile(assetsIgnoreFile, assetsIgnore);
89
157
  await promises.rm(tempFolder, { recursive: true });
90
158
  }
91
-
92
159
  //#endregion
93
160
  export { adapter_default as cloudflare };
161
+
94
162
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fs"],"sources":["../src/prepareTemplate.ts","../src/adapter.ts"],"sourcesContent":["export default function prepareTemplate(template: string, clientScript: string): string {\n\tlet result = template;\n\n\t// Put %COMPONENT_HEAD% before </head>\n\t// This is where styles will go\n\tlet headStart = result.indexOf(\"</head>\");\n\tif (headStart === -1) {\n\t\tthrow new Error(`Couldn't find <head> end tag`);\n\t}\n\tresult = result.substring(0, headStart) + \"%COMPONENT_HEAD%\" + result.substring(headStart);\n\n\t// Put %COMPONENT_BODY% inside <div id=\"app\"></div>\n\t// This is where the component's HTML will go\n\tlet bodyStart = regexIndexOf(result, /<div\\s+id=(\"app\"|'app'|app)\\s+/);\n\tbodyStart = result.indexOf(\">\", bodyStart) + 1;\n\tlet bodyEnd = result.indexOf(\"</div>\", bodyStart);\n\tif (bodyStart === -1 || bodyEnd === -1) {\n\t\tthrow new Error(`Couldn't find <div id=\"app\"></div>`);\n\t}\n\tresult = result.substring(0, bodyStart) + \"%COMPONENT_BODY%\" + result.substring(bodyEnd);\n\n\t// Add the clientEntry script at the very end, for hydrating and navigating\n\tresult += `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\n\treturn result;\n}\n\n// From https://stackoverflow.com/a/274094\nfunction regexIndexOf(string: string, regex: RegExp, position?: number) {\n\tvar indexOf = string.substring(position || 0).search(regex);\n\treturn indexOf >= 0 ? indexOf + (position || 0) : indexOf;\n}\n","import { type Adapter, Site } from \"@torpor/build\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { build, defineConfig } from \"vite\";\nimport prepareTemplate from \"./prepareTemplate\";\n\nexport default {\n\tpostbuild,\n\tserve: (/* server: Server, site: Site*/) => {\n\t\tconsole.log(\"TODO: Cloudflare dev server\");\n\t},\n} satisfies Adapter as Adapter;\n\nasync function postbuild(site: Site): Promise<void> {\n\t// Build _worker.js as per\n\t// https://developers.cloudflare.com/pages/functions/advanced-mode/\n\n\tconst distFolder = path.resolve(site.root, \"dist\");\n\tconst clientFolder = path.join(distFolder, \"client\");\n\tconst serverFolder = path.join(distFolder, \"server\");\n\tconst tempFolder = path.join(distFolder, \"temp\");\n\tconst cloudflareFolder = path.join(distFolder, \"cloudflare\");\n\n\tconst buildSrcFolder = path.resolve(site.root, \"./node_modules/@torpor/build/src/\");\n\tconst adapterSrcFolder = path.resolve(\n\t\tsite.root,\n\t\t\"./node_modules/@torpor/adapter-cloudflare/src/\",\n\t);\n\n\t// Compile _worker.ts\n\tlet workerFile = path.join(adapterSrcFolder, \"_worker.ts\");\n\tlet workerSource = await fs.readFile(workerFile, \"utf-8\");\n\n\t// Find the client script file in /assets\n\tlet clientScript = (await fs.readdir(path.join(clientFolder, \"assets\"))).find((f) =>\n\t\tf.startsWith(\"clientEntry-\"),\n\t);\n\tif (!clientScript) {\n\t\tthrow new Error(\"clientEntry.js not found\");\n\t}\n\tclientScript = `/assets/${clientScript}`;\n\n\t// Read and prepare site.html so that we can just splice components into it\n\tlet siteHtml = path.join(clientFolder, \"site.html\");\n\tlet template = await fs.readFile(siteHtml, \"utf-8\");\n\ttemplate = prepareTemplate(template, clientScript);\n\n\tconst serverClass = path.join(buildSrcFolder, \"server\", \"Server.ts\");\n\tconst serverScript = path.join(serverFolder, \"serverEntry.js\");\n\n\t// Splice file names into _worker.ts\n\tworkerSource = workerSource\n\t\t.replace(\"%SERVER_CLASS%\", serverClass)\n\t\t.replace(\"%SERVER_SCRIPT%\", serverScript)\n\t\t.replace(\"%HTML_TEMPLATE%\", template);\n\n\tworkerFile = path.join(tempFolder, \"_worker.ts\");\n\tawait fs.mkdir(tempFolder);\n\tawait fs.writeFile(workerFile, workerSource);\n\n\tconst cloudflareConfig = structuredClone(site.viteConfig ?? {});\n\tcloudflareConfig.build ??= {};\n\tcloudflareConfig.build.outDir = cloudflareFolder;\n\tcloudflareConfig.build.rollupOptions ??= {};\n\tcloudflareConfig.build.rollupOptions.input = [workerFile, ...site.inputs];\n\tcloudflareConfig.build.rollupOptions.output = {\n\t\tentryFileNames: `[name].js`,\n\t\tchunkFileNames: `[name].js`,\n\t\tassetFileNames: `[name].[ext]`,\n\t};\n\tcloudflareConfig.build.rollupOptions.preserveEntrySignatures = \"strict\";\n\t// HACK: We don't need to minify as the worker isn't downloaded?\n\t// And it makes debugging easier\n\tcloudflareConfig.build.minify = false;\n\t// HACK: Build for SSR so we don't get document and window references\n\tcloudflareConfig.build.ssr = true;\n\tawait build(defineConfig(cloudflareConfig));\n\n\t// Copy the client assets into the Cloudflare folder\n\tawait fs.mkdir(path.join(cloudflareFolder, \"assets\"));\n\tfor (let file of await fs.readdir(path.join(clientFolder, \"assets\"))) {\n\t\tawait fs.copyFile(\n\t\t\tpath.join(clientFolder, \"assets\", file),\n\t\t\tpath.join(cloudflareFolder, \"assets\", file),\n\t\t);\n\t}\n\n\t// Build a wrangler.toml file for running with `wrangler dev`\n\tconst wranglerConfig = `\nname = \"torpor-miniflare\"\nmain = \"./cloudflare/_worker.js\"\ncompatibility_date = \"2025-01-01\"\n\n[assets]\ndirectory = \"./cloudflare\"\nbinding = \"ASSETS\"\n\t`;\n\tconst wranglerFile = path.join(distFolder, \"wrangler.toml\");\n\tawait fs.writeFile(wranglerFile, wranglerConfig);\n\n\t// Build a .assetsignore file\n\tconst assetsIgnore = `\n**/node_modules\n**/.DS_Store\n**/.git\n_worker.js\n`;\n\tconst assetsIgnoreFile = path.join(cloudflareFolder, \".assetsignore\");\n\tawait fs.writeFile(assetsIgnoreFile, assetsIgnore);\n\n\tawait fs.rm(tempFolder, { recursive: true });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["fs","fs"],"sources":["../src/prepareTemplate.ts","../src/cloudflareDev.ts","../src/adapter.ts"],"sourcesContent":["export default function prepareTemplate(template: string, clientScript: string): string {\n\tlet result = template;\n\n\t// Put %COMPONENT_HEAD% before </head>\n\t// This is where styles will go\n\tlet headStart = result.indexOf(\"</head>\");\n\tif (headStart === -1) {\n\t\tthrow new Error(`Couldn't find <head> end tag`);\n\t}\n\tresult = result.substring(0, headStart) + \"%COMPONENT_HEAD%\" + result.substring(headStart);\n\n\t// Put %COMPONENT_BODY% inside <div id=\"app\"></div>\n\t// This is where the component's HTML will go\n\tlet bodyStart = regexIndexOf(result, /<div\\s+id=(\"app\"|'app'|app)\\s+/);\n\tbodyStart = result.indexOf(\">\", bodyStart) + 1;\n\tlet bodyEnd = result.indexOf(\"</div>\", bodyStart);\n\tif (bodyStart === -1 || bodyEnd === -1) {\n\t\tthrow new Error(`Couldn't find <div id=\"app\"></div>`);\n\t}\n\tresult = result.substring(0, bodyStart) + \"%COMPONENT_BODY%\" + result.substring(bodyEnd);\n\n\t// Add the clientEntry script at the very end, for hydrating and navigating\n\tresult += `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\n\treturn result;\n}\n\n// From https://stackoverflow.com/a/274094\nfunction regexIndexOf(string: string, regex: RegExp, position?: number) {\n\tvar indexOf = string.substring(position || 0).search(regex);\n\treturn indexOf >= 0 ? indexOf + (position || 0) : indexOf;\n}\n","import { cloudflare } from \"@cloudflare/vite-plugin\";\nimport { type Site } from \"@torpor/build\";\nimport { existsSync, promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { type Plugin, type ViteDevServer } from \"vite\";\nimport prepareTemplate from \"./prepareTemplate\";\n\nconst TEMPLATE_ID = \"virtual:torpor-template\";\nconst RESOLVED_TEMPLATE_ID = `\\0${TEMPLATE_ID}`;\n\n/**\n * Vite plugin(s) for Cloudflare dev. Delegates the workerd runtime + bindings +\n * HMR to `@cloudflare/vite-plugin` (assigning the Worker to Vite's `ssr`\n * environment, so torpor's server code runs in workerd), overriding `main` to\n * point at the adapter's dev worker, and exposing the prepared `site.html`\n * template through a virtual module (since `transformIndexHtml` has to run\n * Node-side but the template is consumed inside workerd).\n */\nexport default function cloudflareDev(site: Site): Plugin[] {\n\tlet viteServer: ViteDevServer | undefined;\n\tlet templatePromise: Promise<string | undefined> | undefined;\n\n\tconst templatePlugin: Plugin = {\n\t\tname: \"torpor-cloudflare-template\",\n\t\tconfigureServer(server) {\n\t\t\tviteServer = server;\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === TEMPLATE_ID) return RESOLVED_TEMPLATE_ID;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== RESOLVED_TEMPLATE_ID) return;\n\t\t\ttemplatePromise ??= buildDevTemplate(viteServer!, site);\n\t\t\treturn `export default ${JSON.stringify(await templatePromise)};`;\n\t\t},\n\t};\n\n\tconst cfPlugins = cloudflare({\n\t\tviteEnvironment: { name: \"ssr\" },\n\t\tconfig: {\n\t\t\tmain: \"./node_modules/@torpor/adapter-cloudflare/dist/_worker.dev.ts\",\n\t\t},\n\t});\n\n\treturn [templatePlugin, ...cfPlugins];\n}\n\nasync function buildDevTemplate(vite: ViteDevServer, site: Site): Promise<string | undefined> {\n\t// Read site.html if present. An endpoints-only site has no site.html, and\n\t// no template is needed\n\tconst templateFile = path.resolve(site.root, \"src/site.html\");\n\tif (!existsSync(templateFile)) {\n\t\treturn undefined;\n\t}\n\tlet template = await fs.readFile(templateFile, \"utf-8\");\n\ttemplate = await vite.transformIndexHtml(\"\", template);\n\n\tconst siteFolder = path.resolve(site.root, \"./node_modules/@torpor/build/src/site/\");\n\tconst clientScript = path.join(siteFolder, \"clientEntry.ts\");\n\tconst clientDevScript = path.join(siteFolder, \"clientEntryDev.ts\");\n\n\t// Splice component placeholders and append the production client entry\n\ttemplate = prepareTemplate(template, clientScript);\n\n\t// Inject the dev client entry (for HMR) just before the client entry\n\tconst clientTag = `<script type=\"module\" src=\"${clientScript}\"></script>`;\n\ttemplate = template.replace(\n\t\tclientTag,\n\t\t`<script type=\"module\" src=\"${clientDevScript}\"></script>\\n${clientTag}`,\n\t);\n\n\treturn template;\n}\n","import { type Adapter, Site } from \"@torpor/build\";\nimport { spawn } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { existsSync, promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { build, defineConfig } from \"vite\";\nimport cloudflareDev from \"./cloudflareDev\";\nimport prepareTemplate from \"./prepareTemplate\";\n\nexport default {\n\tpostbuild,\n\tdev: (site: Site) => cloudflareDev(site),\n\tserve: (_server, site: Site) => {\n\t\t// Preview the built Cloudflare worker (from postbuild) with `wrangler dev`\n\t\t// rather than a Node server, so preview matches production (workerd).\n\t\tconst configPath = path.join(site.root, \"dist\", \"wrangler.toml\");\n\t\tconst args = [\"dev\", \"--config\", configPath];\n\n\t\t// Resolve the wrangler bin from the consuming site's node_modules; fall\n\t\t// back to `wrangler` on PATH (e.g. when run via a package manager).\n\t\tlet child;\n\t\ttry {\n\t\t\tconst require = createRequire(path.join(site.root, \"package.json\"));\n\t\t\tconst wranglerBin = require.resolve(\"wrangler/bin/wrangler.js\");\n\t\t\tchild = spawn(process.execPath, [wranglerBin, ...args], {\n\t\t\t\tstdio: \"inherit\",\n\t\t\t\tcwd: site.root,\n\t\t\t});\n\t\t} catch {\n\t\t\tchild = spawn(\"wrangler\", args, { stdio: \"inherit\", cwd: site.root });\n\t\t}\n\t\tchild.on(\"exit\", (code) => process.exit(code ?? 0));\n\t},\n} satisfies Adapter as Adapter;\n\nasync function postbuild(site: Site): Promise<void> {\n\t// Build _worker.js as per\n\t// https://developers.cloudflare.com/pages/functions/advanced-mode/\n\n\tconst distFolder = path.resolve(site.root, \"dist\");\n\tconst clientFolder = path.join(distFolder, \"client\");\n\tconst serverFolder = path.join(distFolder, \"server\");\n\tconst tempFolder = path.join(distFolder, \"temp\");\n\tconst cloudflareFolder = path.join(distFolder, \"cloudflare\");\n\n\tconst buildSrcFolder = path.resolve(site.root, \"./node_modules/@torpor/build/src/\");\n\tconst adapterDistFolder = path.resolve(\n\t\tsite.root,\n\t\t\"./node_modules/@torpor/adapter-cloudflare/dist/\",\n\t);\n\n\t// Compile _worker.ts\n\tlet workerFile = path.join(adapterDistFolder, \"_worker.ts\");\n\tlet workerSource = await fs.readFile(workerFile, \"utf-8\");\n\n\t// Read and prepare site.html so that we can just splice components into it.\n\t// An endpoints-only site has no site.html, and no template is needed\n\tlet template: string | undefined;\n\tconst siteHtml = path.join(clientFolder, \"site.html\");\n\tif (existsSync(siteHtml)) {\n\t\tlet clientScript = (await fs.readdir(path.join(clientFolder, \"assets\"))).find((f) =>\n\t\t\tf.startsWith(\"clientEntry-\"),\n\t\t);\n\t\tif (!clientScript) {\n\t\t\tthrow new Error(\"clientEntry.js not found\");\n\t\t}\n\t\tclientScript = `/assets/${clientScript}`;\n\n\t\ttemplate = prepareTemplate(await fs.readFile(siteHtml, \"utf-8\"), clientScript);\n\t}\n\n\tconst serverClass = path.join(buildSrcFolder, \"server\", \"Server.ts\");\n\tconst serverScript = path.join(serverFolder, \"serverEntry.js\");\n\n\t// Splice file names into _worker.ts\n\tworkerSource = workerSource\n\t\t.replace(\"%SERVER_CLASS%\", serverClass)\n\t\t.replace(\"%SERVER_SCRIPT%\", serverScript)\n\t\t.replace(\"`%HTML_TEMPLATE%`\", () => (template ? JSON.stringify(template) : \"undefined\"));\n\n\tworkerFile = path.join(tempFolder, \"_worker.ts\");\n\tawait fs.mkdir(tempFolder);\n\tawait fs.writeFile(workerFile, workerSource);\n\n\tconst cloudflareConfig = structuredClone(site.viteConfig ?? {});\n\tcloudflareConfig.build ??= {};\n\tcloudflareConfig.build.outDir = cloudflareFolder;\n\tcloudflareConfig.build.rollupOptions ??= {};\n\tcloudflareConfig.build.rollupOptions.input = [workerFile, ...site.inputs];\n\tcloudflareConfig.build.rollupOptions.output = {\n\t\tentryFileNames: `[name].js`,\n\t\tchunkFileNames: `[name].js`,\n\t\tassetFileNames: `[name].[ext]`,\n\t};\n\tcloudflareConfig.build.rollupOptions.preserveEntrySignatures = \"strict\";\n\t// HACK: We don't need to minify as the worker isn't downloaded?\n\t// And it makes debugging easier\n\tcloudflareConfig.build.minify = false;\n\t// HACK: Build for SSR so we don't get document and window references\n\tcloudflareConfig.build.ssr = true;\n\tawait build(defineConfig(cloudflareConfig));\n\n\t// Copy the client assets into the Cloudflare folder\n\tawait fs.mkdir(path.join(cloudflareFolder, \"assets\"));\n\tfor (let file of await fs.readdir(path.join(clientFolder, \"assets\"))) {\n\t\tawait fs.copyFile(\n\t\t\tpath.join(clientFolder, \"assets\", file),\n\t\t\tpath.join(cloudflareFolder, \"assets\", file),\n\t\t);\n\t}\n\n\t// Build a wrangler.toml file for running with `wrangler dev`\n\tconst wranglerConfig = `\nname = \"torpor-miniflare\"\nmain = \"./cloudflare/_worker.js\"\ncompatibility_date = \"2025-01-01\"\n\n[assets]\ndirectory = \"./cloudflare\"\nbinding = \"ASSETS\"\n\t`;\n\tconst wranglerFile = path.join(distFolder, \"wrangler.toml\");\n\tawait fs.writeFile(wranglerFile, wranglerConfig);\n\n\t// Build a .assetsignore file\n\tconst assetsIgnore = `\n**/node_modules\n**/.DS_Store\n**/.git\n_worker.js\n`;\n\tconst assetsIgnoreFile = path.join(cloudflareFolder, \".assetsignore\");\n\tawait fs.writeFile(assetsIgnoreFile, assetsIgnore);\n\n\tawait fs.rm(tempFolder, { recursive: true });\n}\n"],"mappings":";;;;;;;AAAA,SAAwB,gBAAgB,UAAkB,cAA8B;CACvF,IAAI,SAAS;CAIb,IAAI,YAAY,OAAO,QAAQ,SAAS;CACxC,IAAI,cAAc,IACjB,MAAM,IAAI,MAAM,8BAA8B;CAE/C,SAAS,OAAO,UAAU,GAAG,SAAS,IAAI,qBAAqB,OAAO,UAAU,SAAS;CAIzF,IAAI,YAAY,aAAa,QAAQ,gCAAgC;CACrE,YAAY,OAAO,QAAQ,KAAK,SAAS,IAAI;CAC7C,IAAI,UAAU,OAAO,QAAQ,UAAU,SAAS;CAChD,IAAI,cAAc,MAAM,YAAY,IACnC,MAAM,IAAI,MAAM,oCAAoC;CAErD,SAAS,OAAO,UAAU,GAAG,SAAS,IAAI,qBAAqB,OAAO,UAAU,OAAO;CAGvF,UAAU,8BAA8B,aAAa;CAErD,OAAO;AACR;AAGA,SAAS,aAAa,QAAgB,OAAe,UAAmB;CACvE,IAAI,UAAU,OAAO,UAAU,YAAY,CAAC,CAAC,CAAC,OAAO,KAAK;CAC1D,OAAO,WAAW,IAAI,WAAW,YAAY,KAAK;AACnD;;;ACxBA,MAAM,cAAc;AACpB,MAAM,uBAAuB,KAAK;;;;;;;;;AAUlC,SAAwB,cAAc,MAAsB;CAC3D,IAAI;CACJ,IAAI;CAwBJ,OAAO,CAAC;EArBP,MAAM;EACN,gBAAgB,QAAQ;GACvB,aAAa;EACd;EACA,UAAU,IAAI;GACb,IAAI,OAAO,aAAa,OAAO;EAChC;EACA,MAAM,KAAK,IAAI;GACd,IAAI,OAAO,sBAAsB;GACjC,oBAAoB,iBAAiB,YAAa,IAAI;GACtD,OAAO,kBAAkB,KAAK,UAAU,MAAM,eAAe,EAAE;EAChE;CAUoB,GAAG,GAPN,WAAW;EAC5B,iBAAiB,EAAE,MAAM,MAAM;EAC/B,QAAQ,EACP,MAAM,gEACP;CACD,CAEmC,CAAC;AACrC;AAEA,eAAe,iBAAiB,MAAqB,MAAyC;CAG7F,MAAM,eAAe,KAAK,QAAQ,KAAK,MAAM,eAAe;CAC5D,IAAI,CAAC,WAAW,YAAY,GAC3B;CAED,IAAI,WAAW,MAAMA,SAAG,SAAS,cAAc,OAAO;CACtD,WAAW,MAAM,KAAK,mBAAmB,IAAI,QAAQ;CAErD,MAAM,aAAa,KAAK,QAAQ,KAAK,MAAM,wCAAwC;CACnF,MAAM,eAAe,KAAK,KAAK,YAAY,gBAAgB;CAC3D,MAAM,kBAAkB,KAAK,KAAK,YAAY,mBAAmB;CAGjE,WAAW,gBAAgB,UAAU,YAAY;CAGjD,MAAM,YAAY,8BAA8B,aAAa;CAC7D,WAAW,SAAS,QACnB,WACA,8BAA8B,gBAAgB,gBAAe,WAC9D;CAEA,OAAO;AACR;;;AC/DA,IAAA,kBAAe;CACd;CACA,MAAM,SAAe,cAAc,IAAI;CACvC,QAAQ,SAAS,SAAe;EAI/B,MAAM,OAAO;GAAC;GAAO;GADF,KAAK,KAAK,KAAK,MAAM,QAAQ,eACN;EAAC;EAI3C,IAAI;EACJ,IAAI;GAEH,MAAM,cADU,cAAc,KAAK,KAAK,KAAK,MAAM,cAAc,CACvC,CAAC,CAAC,QAAQ,0BAA0B;GAC9D,QAAQ,MAAM,QAAQ,UAAU,CAAC,aAAa,GAAG,IAAI,GAAG;IACvD,OAAO;IACP,KAAK,KAAK;GACX,CAAC;EACF,QAAQ;GACP,QAAQ,MAAM,YAAY,MAAM;IAAE,OAAO;IAAW,KAAK,KAAK;GAAK,CAAC;EACrE;EACA,MAAM,GAAG,SAAS,SAAS,QAAQ,KAAK,QAAQ,CAAC,CAAC;CACnD;AACD;AAEA,eAAe,UAAU,MAA2B;CAInD,MAAM,aAAa,KAAK,QAAQ,KAAK,MAAM,MAAM;CACjD,MAAM,eAAe,KAAK,KAAK,YAAY,QAAQ;CACnD,MAAM,eAAe,KAAK,KAAK,YAAY,QAAQ;CACnD,MAAM,aAAa,KAAK,KAAK,YAAY,MAAM;CAC/C,MAAM,mBAAmB,KAAK,KAAK,YAAY,YAAY;CAE3D,MAAM,iBAAiB,KAAK,QAAQ,KAAK,MAAM,mCAAmC;CAClF,MAAM,oBAAoB,KAAK,QAC9B,KAAK,MACL,iDACD;CAGA,IAAI,aAAa,KAAK,KAAK,mBAAmB,YAAY;CAC1D,IAAI,eAAe,MAAMC,SAAG,SAAS,YAAY,OAAO;CAIxD,IAAI;CACJ,MAAM,WAAW,KAAK,KAAK,cAAc,WAAW;CACpD,IAAI,WAAW,QAAQ,GAAG;EACzB,IAAI,gBAAgB,MAAMA,SAAG,QAAQ,KAAK,KAAK,cAAc,QAAQ,CAAC,EAAA,CAAG,MAAM,MAC9E,EAAE,WAAW,cAAc,CAC5B;EACA,IAAI,CAAC,cACJ,MAAM,IAAI,MAAM,0BAA0B;EAE3C,eAAe,WAAW;EAE1B,WAAW,gBAAgB,MAAMA,SAAG,SAAS,UAAU,OAAO,GAAG,YAAY;CAC9E;CAEA,MAAM,cAAc,KAAK,KAAK,gBAAgB,UAAU,WAAW;CACnE,MAAM,eAAe,KAAK,KAAK,cAAc,gBAAgB;CAG7D,eAAe,aACb,QAAQ,kBAAkB,WAAW,CAAC,CACtC,QAAQ,mBAAmB,YAAY,CAAC,CACxC,QAAQ,2BAA4B,WAAW,KAAK,UAAU,QAAQ,IAAI,WAAY;CAExF,aAAa,KAAK,KAAK,YAAY,YAAY;CAC/C,MAAMA,SAAG,MAAM,UAAU;CACzB,MAAMA,SAAG,UAAU,YAAY,YAAY;CAE3C,MAAM,mBAAmB,gBAAgB,KAAK,cAAc,CAAC,CAAC;CAC9D,iBAAiB,UAAU,CAAC;CAC5B,iBAAiB,MAAM,SAAS;CAChC,iBAAiB,MAAM,kBAAkB,CAAC;CAC1C,iBAAiB,MAAM,cAAc,QAAQ,CAAC,YAAY,GAAG,KAAK,MAAM;CACxE,iBAAiB,MAAM,cAAc,SAAS;EAC7C,gBAAgB;EAChB,gBAAgB;EAChB,gBAAgB;CACjB;CACA,iBAAiB,MAAM,cAAc,0BAA0B;CAG/D,iBAAiB,MAAM,SAAS;CAEhC,iBAAiB,MAAM,MAAM;CAC7B,MAAM,MAAM,aAAa,gBAAgB,CAAC;CAG1C,MAAMA,SAAG,MAAM,KAAK,KAAK,kBAAkB,QAAQ,CAAC;CACpD,KAAK,IAAI,QAAQ,MAAMA,SAAG,QAAQ,KAAK,KAAK,cAAc,QAAQ,CAAC,GAClE,MAAMA,SAAG,SACR,KAAK,KAAK,cAAc,UAAU,IAAI,GACtC,KAAK,KAAK,kBAAkB,UAAU,IAAI,CAC3C;CAID,MAAM,iBAAiB;;;;;;;;;CASvB,MAAM,eAAe,KAAK,KAAK,YAAY,eAAe;CAC1D,MAAMA,SAAG,UAAU,cAAc,cAAc;CAG/C,MAAM,eAAe;;;;;;CAMrB,MAAM,mBAAmB,KAAK,KAAK,kBAAkB,eAAe;CACpE,MAAMA,SAAG,UAAU,kBAAkB,YAAY;CAEjD,MAAMA,SAAG,GAAG,YAAY,EAAE,WAAW,KAAK,CAAC;AAC5C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torpor/adapter-cloudflare",
3
- "version": "0.2.2",
3
+ "version": "1.0.0",
4
4
  "description": "A @torpor/build adapter for deploying to Cloudflare Pages",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,19 +17,23 @@
17
17
  "author": "",
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
- "vite": "^7.2.2",
21
- "@torpor/build": "^0.4.10"
20
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.7",
21
+ "@torpor/build": "^1.0.0"
22
+ },
23
+ "peerDependencies": {
24
+ "@cloudflare/vite-plugin": "^1.0.0",
25
+ "wrangler": "^4.118.0"
22
26
  },
23
27
  "devDependencies": {
24
- "@trivago/prettier-plugin-sort-imports": "^6.0.0",
25
- "@typescript/native-preview": "7.0.0-dev.20251112.1",
26
- "oxlint-tsgolint": "^0.6.0",
27
- "tsdown": "^0.16.4",
28
- "typescript": "^5.9.3"
28
+ "@cloudflare/vite-plugin": "^1.50.0",
29
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
30
+ "typescript": "^7.0.2",
31
+ "vite-plus": "0.2.7",
32
+ "wrangler": "^4.118.0"
29
33
  },
30
34
  "scripts": {
31
35
  "check": "tsgo --noEmit && pnpm dlx oxlint --type-aware",
32
- "build": "tsgo --noEmit && tsdown",
33
- "build:watch": "tsdown --watch"
36
+ "build": "tsgo --noEmit && vp pack",
37
+ "build:watch": "vp pack --watch"
34
38
  }
35
39
  }