'use strict'; const fs = require('node:fs'); const path = require('node:path'); const node_child_process = require('node:child_process'); const kill = require('tree-kill'); const colors = require('picocolors'); const death = require('@breadc/death'); const fg = require('fast-glob'); const mlly = require('mlly'); const createDebug = require('debug'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n.default = e; return n; } const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs); const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path); const kill__default = /*#__PURE__*/_interopDefaultCompat(kill); const colors__default = /*#__PURE__*/_interopDefaultCompat(colors); const fg__default = /*#__PURE__*/_interopDefaultCompat(fg); const createDebug__default = /*#__PURE__*/_interopDefaultCompat(createDebug); const debug = createDebug__default("cloudflare-functions"); function normalizePath(filename) { return filename.split(path.win32.sep).join(path.posix.sep); } async function generate(functionsRoot, dtsPath) { const files = (await fg__default(["**/*.ts", "**/*.js", "!**/*.d.ts", "!node_modules/**/*"], { cwd: functionsRoot })).sort(); const removeSuffix = (file) => file.replace(/\.\w+$/, ""); const ensureRoute = (file) => { file = removeSuffix(file); if (file.endsWith("_middleware")) { file = file.replace(/_middleware$/, "**"); } if (!file.startsWith("/")) { file = "/" + file; } if (file.endsWith("/index")) { file = file.replace(/index$/, ""); } file = file.replace(/\[\[([\w]+)\]\]$/g, "**:$1"); file = file.replace(/\[([\w]+)\]/g, ":$1"); return file; }; const routes = await Promise.all( files.map(async (f) => { const absPath = path__namespace.join(functionsRoot, f); const exports = await getExports(absPath); if (exports.length > 0) { const route = ensureRoute(f); return [ `'${route}': {`, ...exports.map((name) => { const method = name.slice().slice(9).toUpperCase(); const realpath = normalizePath(path__namespace.relative(dtsPath, removeSuffix(absPath))); return ` ${!!method ? method : "ALL"}: CloudflareResponseBody;`; }), `};` ].map((t) => " " + t); } else { return []; } }) ); const lines = routes.flat(); if (lines.length === 0) return ""; return [ `import type { CloudflareResponseBody } from 'vite-plugin-cloudflare-functions/worker'; `, `import 'vite-plugin-cloudflare-functions/client'; `, `declare module 'vite-plugin-cloudflare-functions/client' {`, ` interface PagesResponseBody {`, ...lines, ` }`, `} ` ].join("\n"); } const ALLOW_EXPORTS = /* @__PURE__ */ new Map([ ["onRequest", 0], ["onRequestGet", 1], ["onRequestHead", 2], ["onRequestPost", 3], ["onRequestPut", 4], ["onRequestDelete", 5], ["onRequestOptions", 6], ["onRequestPatch", 7] ]); async function getExports(filepath) { const code = await fs.promises.readFile(filepath, "utf-8"); const exports = mlly.findExportNames(code); return exports.filter((n) => ALLOW_EXPORTS.has(n)).sort((a, b) => ALLOW_EXPORTS.get(a) - ALLOW_EXPORTS.get(b)); } const DefaultPort = 5173; const DefaultWranglerPort = 8788; function CloudflarePagesFunctions(userConfig = {}) { let root; let port; let functionsRoot; let preparePromise; let wranglerProcess; let workerProcess; const killProcess = async () => { if (wranglerProcess) { const pid = wranglerProcess.pid; debug(`Kill wrangler (PID: ${pid})`); wranglerProcess = void 0; if (pid) { await new Promise((res) => kill__default(pid, () => res(void 0))); } } if (workerProcess) { const pid = workerProcess.pid; debug(`Kill wrangler (PID: ${pid})`); workerProcess = void 0; if (pid) { await new Promise((res) => kill__default(pid, () => res(void 0))); } } }; death.onDeath(async () => { await killProcess(); }); if (typeof userConfig.dts !== "boolean" && typeof userConfig.dts !== "string") { userConfig.dts = true; } let shouldGen = false; const doAutoGen = async () => { if (userConfig.dts) { const dts = userConfig.dts === true ? "cloudflare.d.ts" : userConfig.dts; const dtsPath = path__namespace.resolve(root, dts); const content = await generate(functionsRoot, path__namespace.dirname(dtsPath)); if (content && content.length > 0) { await fs__namespace.promises.writeFile(dtsPath, content, "utf-8"); } } }; async function startWrangler() { if (wranglerProcess) { await killProcess(); } const wranglerPort = userConfig.wrangler?.port ?? DefaultWranglerPort; const bindings = []; if (userConfig.wrangler?.binding) { for (const [key, value] of Object.entries(userConfig.wrangler.binding)) { bindings.push("--binding"); bindings.push(`${key}=${value}`); } } if (userConfig.wrangler?.kv) { const kvs = Array.isArray(userConfig.wrangler.kv) ? userConfig.wrangler.kv : [userConfig.wrangler.kv]; for (const kv of kvs) { bindings.push("--kv"); bindings.push(kv); } } if (userConfig.wrangler?.d1) { const d1s = Array.isArray(userConfig.wrangler.d1) ? userConfig.wrangler.d1 : [userConfig.wrangler.d1]; for (const d1 of d1s) { bindings.push("--d1"); bindings.push(d1); } } if (userConfig.wrangler?.do) { for (const [key, value] of Object.entries(userConfig.wrangler.do)) { const config = typeof value === "string" ? value : `${value.class}@${value.script}`; bindings.push("--do"); bindings.push(`"${key}=${config}"`); } } if (userConfig.wrangler?.r2) { const r2s = Array.isArray(userConfig.wrangler.r2) ? userConfig.wrangler.r2 : [userConfig.wrangler.r2]; for (const r2 of r2s) { bindings.push("--r2"); bindings.push(r2); } } if (userConfig.wrangler?.ai) { const ais = Array.isArray(userConfig.wrangler.ai) ? userConfig.wrangler.ai : [userConfig.wrangler.ai]; for (const ai of ais) { bindings.push("--ai"); bindings.push(ai); } } const command = [ "wrangler", "pages", "dev", "--local", "--ip", "localhost", "--port", String(wranglerPort), "--proxy", String(port), "--persist-to", path__namespace.join(functionsRoot, ".wrangler/state"), ...bindings ]; const compatibilityDate = userConfig.wrangler?.compatibilityDate; if (compatibilityDate) { command.push("--compatibility-date"); command.push(compatibilityDate); } debug(command); const wranglerEnv = { ...process.env }; for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"]) { if (key in wranglerEnv) { delete wranglerEnv[key]; } } wranglerProcess = node_child_process.spawn("npx", command, { shell: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], env: wranglerEnv, cwd: path__namespace.dirname(functionsRoot) }); let firstTime = true; wranglerProcess.stdout.on("data", (chunk) => { const text = chunk.toString("utf8").slice(0, -1); if (text.indexOf("Compiled Worker successfully") !== -1) { if (firstTime) { doAutoGen(); firstTime = false; const colorUrl = (url) => colors__default.cyan(url.replace(/:(\d+)\//, (_, port2) => `:${colors__default.bold(port2)}/`)); console.log( ` ${colors__default.green("\u279C")} ${colors__default.bold("Pages")}: ${colorUrl( `http://localhost:${wranglerPort}/` )} ` ); } else { shouldGen = true; console.log( `${colors__default.dim(( new Date()).toLocaleTimeString())} ${colors__default.cyan( colors__default.bold("[cloudflare pages]") )} ${colors__default.green("functions reload")}` ); } } if (userConfig.wrangler?.log && text) { console.log(text); } }); wranglerProcess.stderr.on("data", (chunk) => { if (userConfig.wrangler?.log) { const text = chunk.toString("utf8").slice(0, -1); console.error(text); } }); if (userConfig.wrangler?.do) { const command2 = [ "wrangler", "dev", "--persist-to", path__namespace.join(functionsRoot, ".wrangler/state") ]; if (compatibilityDate) { command2.push(`--compatibility-date=${compatibilityDate}`); } workerProcess = node_child_process.spawn("npx", command2, { shell: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], env: wranglerEnv, cwd: functionsRoot }); workerProcess.stdout.on("data", (chunk) => { const text = chunk.toString("utf8").slice(0, -1); if (userConfig.wrangler?.log && text) { console.log(text); } }); workerProcess.stderr.on("data", (chunk) => { const text = chunk.toString("utf8").slice(0, -1); if (userConfig.wrangler?.log && text) { console.error(text); } }); } } return { name: "vite-plugin-cloudflare-functions", config(config) { return { server: { strictPort: true, hmr: { port: (config.server?.port ?? DefaultPort) + 1 } } }; }, configResolved(resolvedConfig) { root = resolvedConfig.root; port = resolvedConfig.server.port ?? DefaultPort; functionsRoot = normalizePath( !!userConfig.root ? path__namespace.resolve(userConfig.root) : path__namespace.resolve(resolvedConfig.root, "functions") ); debug(`Functions root: ${functionsRoot}`); if (!functionsRoot.endsWith("functions") && functionsRoot.endsWith("functions/")) { console.log("You should put your worker in directory named as functions/"); } if (!preparePromise) { preparePromise = doAutoGen(); } }, async configureServer(_server) { if (userConfig.dts) { setInterval(async () => { if (shouldGen) { shouldGen = false; await doAutoGen(); } }, 1e3); } await startWrangler(); }, async closeBundle() { await killProcess(); }, renderStart() { if (userConfig.outDir) { const functionsDst = normalizePath( userConfig.outDir === true ? path__namespace.resolve(root, "functions") : path__namespace.resolve(root, userConfig.outDir, "functions") ); console.log( `Copying cloudflare functions directory from '${path__namespace.relative( ".", functionsRoot )}' to '${path__namespace.relative(".", functionsDst)}' ...` ); try { fs__namespace.rmSync(functionsDst, { recursive: true }); } catch { } try { fs__namespace.cpSync(functionsRoot, functionsDst, { recursive: true }); } catch { } } } }; } module.exports = CloudflarePagesFunctions;