UNPKG

@nuxthub/core

Version:

Build full-stack Nuxt applications on Cloudflare, with zero configuration.

773 lines (764 loc) • 29.8 kB
import { mkdir, writeFile, cp, readFile } from 'node:fs/promises'; import { argv } from 'node:process'; import { logger, createResolver, addServerScanDir, addServerImportsDir, addImportsDir, defineNuxtModule, addServerHandler, installModule, addServerPlugin } from '@nuxt/kit'; import { join, resolve as resolve$1, relative } from 'pathe'; import { defu } from 'defu'; import { findWorkspaceDir } from 'pkg-types'; import { parseArgs } from 'citty'; import { stringifyTOML } from 'confbox'; import { execSync } from 'node:child_process'; import { pathToFileURL } from 'node:url'; import { isWindows } from 'std-env'; import { joinURL } from 'ufo'; import { $fetch } from 'ofetch'; import { addCustomTab } from '@nuxt/devtools-kit'; import { getCloudflareAccessHeaders } from '../dist/runtime/utils/cloudflareAccess.js'; import { copyDatabaseMigrationsToHubDir, copyDatabaseQueriesToHubDir } from '../dist/runtime/database/server/utils/migrations/helpers.js'; import { applyRemoteDatabaseMigrations, applyRemoteDatabaseQueries } from '../dist/runtime/database/server/utils/migrations/remote.js'; const version = "0.9.0"; function generateWrangler(nuxt, hub) { const wrangler = {}; const name = hub.env === "test" ? "test" : "default"; if (hub.analytics && !hub.remote) { wrangler["analytics_engine_datasets"] = [{ binding: "ANALYTICS", dataset: name }]; } if (hub.blob && !hub.remote) { wrangler["r2_buckets"] = [{ binding: "BLOB", bucket_name: name }]; } if (hub.kv || hub.cache) { wrangler["kv_namespaces"] = []; if (hub.kv && !hub.remote) { wrangler["kv_namespaces"].push({ binding: "KV", id: `kv_${name}` }); } if (hub.cache) { wrangler["kv_namespaces"].push({ binding: "CACHE", id: `cache_${name}` }); } } if (hub.database && !hub.remote) { wrangler["d1_databases"] = [{ binding: "DB", database_name: name, database_id: name }]; } return stringifyTOML(wrangler); } function addDevToolsCustomTabs(nuxt, hub) { nuxt.hook("listen", (_, { url }) => { hub.database && addCustomTab({ category: "server", name: "hub-database", title: "Hub Database", icon: "i-lucide-database", view: { type: "iframe", src: `https://admin.hub.nuxt.com/embed/database?url=${url}` } }); hub.kv && addCustomTab({ category: "server", name: "hub-kv", title: "Hub KV", icon: "i-lucide-list", view: { type: "iframe", src: `https://admin.hub.nuxt.com/embed/kv?url=${url}` } }); hub.blob && addCustomTab({ category: "server", name: "hub-blob", title: "Hub Blob", icon: "i-lucide-shapes", view: { type: "iframe", src: `https://admin.hub.nuxt.com/embed/blob?url=${url}` } }); hub.cache && addCustomTab({ category: "server", name: "hub-cache", title: "Hub Cache", icon: "i-lucide-database-zap", view: { type: "iframe", src: `https://admin.hub.nuxt.com/embed/cache?url=${url}` } }); hub.openAPIRoute && addCustomTab({ category: "server", name: "hub-open-api", title: "OpenAPI", icon: "i-lucide-file-text", view: { type: "iframe", src: `/api/_hub/scalar` } }); }); } const log$2 = logger.withTag("nuxt:hub"); const { resolve, resolvePath } = createResolver(import.meta.url); async function setupBase(nuxt, hub) { hub.dir = join(nuxt.options.rootDir, hub.dir); try { await mkdir(hub.dir, { recursive: true }); } catch (e) { if (e.errno === -17) ; else { throw e; } } addServerScanDir(resolve("./runtime/base/server")); addServerImportsDir([resolve("./runtime/base/server/utils"), resolve("./runtime/base/server/utils/migrations")]); if (nuxt.options.dev) { addDevToolsCustomTabs(nuxt, hub); } nuxt.options.routeRules = nuxt.options.routeRules || {}; nuxt.options.routeRules["/api/_hub/**"] = nuxt.options.routeRules["/api/_hub/**"] || {}; nuxt.options.routeRules["/api/_hub/**"].csurf = false; nuxt.options.routeRules["/api/_hub/**"].cache = false; nuxt.options.routeRules["/api/_hub/**"].prerender = false; if (!nuxt.options.dev && hub.env === "preview") { nuxt.options.routeRules["/**"] ||= {}; nuxt.options.routeRules["/**"].headers ||= {}; nuxt.options.routeRules["/**"].headers["X-Robots-Tag"] = "noindex"; } nuxt.options.nitro.prerender ||= {}; nuxt.options.nitro.prerender.autoSubfolderIndex ||= false; } async function setupAI(nuxt, hub) { if (nuxt.options.dev && !hub.remote && !hub.projectKey) { return log$2.warn("`hubAI()` and `hubAutoRAG()` are disabled: link a project with `npx nuxthub link` to run AI models in development mode."); } addServerImportsDir(resolve("./runtime/ai/server/utils")); if (nuxt.options.dev && !hub.remote && hub.projectKey) { try { await $fetch(`/api/projects/${hub.projectKey}`, { method: "HEAD", baseURL: hub.url, headers: { authorization: `Bearer ${hub.userToken}` } }); } catch (err) { if (!err.status) { log$2.warn("`hubAI()` and `hubAutoRAG()` are disabled: it seems that you are offline."); } else if (err.status === 401) { log$2.warn("`hubAI()` and `hubAutoRAG()` are disabled: you are not logged in, make sure to run `npx nuxthub login`."); } else { log$2.error("`hubAI()` and `hubAutoRAG()` are disabled: failed to fetch linked project `" + hub.projectKey + "` on NuxtHub, make sure to run `npx nuxthub link` again."); } return; } } addServerScanDir(resolve("./runtime/ai/server")); } function setupAnalytics(_nuxt) { addServerScanDir(resolve("./runtime/analytics/server")); addServerImportsDir(resolve("./runtime/analytics/server/utils")); } function setupBlob(_nuxt) { addServerScanDir(resolve("./runtime/blob/server")); addServerImportsDir(resolve("./runtime/blob/server/utils")); addImportsDir(resolve("./runtime/blob/app/composables")); } async function setupBrowser(nuxt) { addServerImportsDir(resolve("./runtime/browser/server/utils")); const missingDeps = []; try { const pkg = "@cloudflare/puppeteer"; await import(pkg); } catch (err) { missingDeps.push("@cloudflare/puppeteer"); } if (nuxt.options.dev) { try { const pkg = "puppeteer"; await import(pkg); } catch (err) { missingDeps.push("puppeteer"); } } if (missingDeps.length > 0) { console.error(`Missing dependencies for \`hubBrowser()\`, please install with: \`npx nypm i ${missingDeps.join(" ")}\``); process.exit(1); } } async function setupCache(nuxt) { let driver = await resolvePath("./runtime/cache/driver"); if (isWindows) { driver = pathToFileURL(driver).href; } nuxt.options.nitro = defu(nuxt.options.nitro, { storage: { cache: { driver, binding: "CACHE" } }, devStorage: { cache: nuxt.options.dev ? { driver, binding: "CACHE" } : { // Used for pre-rendering driver: "fs", base: join(nuxt.options.rootDir, ".data/cache") } } }); addServerScanDir(resolve("./runtime/cache/server")); } async function setupDatabase(nuxt, hub) { addServerScanDir(resolve("./runtime/database/server")); addServerImportsDir(resolve("./runtime/database/server/utils")); if (nuxt.options.nitro.experimental?.database) { nuxt.options.nitro.database = defu(nuxt.options.nitro.database, { default: { connector: "cloudflare-d1", options: { bindingName: "DB" } } }); } nuxt.hook("modules:done", async () => { await nuxt.callHook("hub:database:migrations:dirs", hub.databaseMigrationsDirs); await copyDatabaseMigrationsToHubDir(hub); await nuxt.callHook("hub:database:queries:paths", hub.databaseQueriesPaths); await copyDatabaseQueriesToHubDir(hub); }); } function setupKV(_nuxt) { addServerScanDir(resolve("./runtime/kv/server")); addServerImportsDir(resolve("./runtime/kv/server/utils")); } function setupVectorize(nuxt, hub) { addServerImportsDir(resolve("./runtime/vectorize/server/utils")); if (nuxt.options.dev && !hub.remote) { log$2.warn("`hubVectorize()` is disabled: only supported with remote storage in development mode (`nuxt dev --remote`)."); return; } addServerScanDir(resolve("./runtime/vectorize/server")); } function vectorizeRemoteCheck(hub) { let isIndexConfigurationChanged = false; const localVectorize = hub.vectorize || {}; const remoteVectorize = hub.remoteManifest?.storage.vectorize || {}; Object.keys(localVectorize).forEach((key) => { if (!remoteVectorize[key]) { return; } const isDimensionsChanged = localVectorize[key].dimensions !== remoteVectorize[key].dimensions; const isMetricChanged = localVectorize[key].metric !== remoteVectorize[key].metric; if (isDimensionsChanged || isMetricChanged) { log$2.warn(`Vectorize index \`${key}\` configuration changed Remote: \`${remoteVectorize[key].dimensions}\` dimensions - \`${remoteVectorize[key].metric}\` metric Local: \`${localVectorize[key].dimensions}\` dimensions - \`${localVectorize[key].metric}\` metric`); isIndexConfigurationChanged = true; } }); if (isIndexConfigurationChanged) { log$2.warn("Modified Vectorize index(es) will be recreated with new configuration on deployment and existing data will not be migrated!"); } } function setupOpenAPI(nuxt, hub) { nuxt.options.nitro ||= {}; nuxt.options.nitro.openAPI ||= {}; nuxt.options.nitro.openAPI.production ||= "runtime"; nuxt.options.nitro.openAPI.route ||= "/api/_hub/openapi.json"; nuxt.options.nitro.openAPI.ui ||= {}; if (nuxt.options.dev) { nuxt.options.nitro.openAPI.ui.scalar = { route: "/api/_hub/scalar" }; } nuxt.options.nitro.openAPI.ui.swagger ||= false; hub.openAPIRoute = nuxt.options.nitro.openAPI.route; addServerScanDir(resolve("./runtime/openapi/server")); } async function setupRemote(_nuxt, hub) { let env = hub.remote; let branch = "main"; if (String(env) === "true") { try { branch = execSync("git branch --show-current", { stdio: ["ignore", "pipe", "ignore"] }).toString().trim(); env = branch === "main" ? "production" : "preview"; } catch { log$2.warn("Could not guess the environment from the branch name, using `production` as default"); env = "production"; } } if (typeof hub.projectUrl === "function" && !hub.projectKey) { hub.projectUrl = hub.projectUrl({ env, branch }); } if (hub.projectKey) { if (hub.projectSecretKey) { log$2.warn("Ignoring `NUXT_HUB_PROJECT_SECRET_KEY` as `NUXT_HUB_PROJECT_KEY` is set."); } const project = await $fetch(`/api/projects/${hub.projectKey}`, { baseURL: hub.url, headers: { authorization: `Bearer ${hub.userToken}` } }).catch((err) => { log$2.debug(err); if (!err.status) { log$2.error("It seems that you are offline."); } else if (err.status === 401) { log$2.error("It seems that you are not logged in, make sure to run `npx nuxthub login`."); } else { log$2.error("Failed to fetch linked project on NuxtHub, make sure to run `npx nuxthub link` again."); } process.exit(1); }); if (project.userProjectToken) { hub.userToken = project.userProjectToken; } if (project.type === "pages") { if (String(hub.remote) === "true") { env = branch === project.productionBranch ? "production" : "preview"; } else { env = String(hub.remote); } } else { const environment = await determineEnvironment(hub, hub.projectKey, branch); env = environment.name; hub.projectUrl = environment.url; } if (typeof hub.projectUrl === "function") { hub.projectUrl = hub.projectUrl({ env, branch }); } const adminUrl = joinURL(hub.url, project.teamSlug, project.slug); log$2.info(`Linked to \`${adminUrl}\``); log$2.info(`Using \`${env}\` environment`); hub.projectUrl = hub.projectUrl || (env === "production" ? project.url : project.previewUrl); if (!hub.projectUrl) { log$2.error(`No deployment found for \`${env}\`, make sure to deploy the project using \`npx nuxthub deploy\`.`); process.exit(1); } hub.env = env; } if (!hub.projectUrl) { log$2.error("No project URL defined, make sure to link your project with `npx nuxthub link` or add the deployed URL as `NUXT_HUB_PROJECT_URL` environment variable (if self-hosted)."); process.exit(1); } if (!hub.projectKey && !hub.projectSecretKey && !hub.userToken) { log$2.error("No project secret key found, make sure to add the `NUXT_HUB_PROJECT_SECRET_KEY` environment variable."); process.exit(1); } log$2.info(`Using remote storage from \`${hub.projectUrl}\``); const remoteManifest = hub.remoteManifest = await $fetch("/api/_hub/manifest", { baseURL: hub.projectUrl, headers: { authorization: `Bearer ${hub.projectSecretKey || hub.userToken}`, ...getCloudflareAccessHeaders(hub.cloudflareAccess) } }).catch(async (err) => { log$2.debug(err); let message = "Project not found.\nMake sure to deploy the project using `npx nuxthub deploy` or add the deployed URL as `NUXT_HUB_PROJECT_URL` environment variable."; if (err.status >= 500) { message = "Internal server error"; } else if (err.status === 401) { message = "Authorization failed.\nMake sure to provide a valid NUXT_HUB_PROJECT_SECRET_KEY or being logged in with `npx nuxthub login`"; if (hub.cloudflareAccess?.clientId && hub.cloudflareAccess?.clientSecret) { message += ", and ensure the provided NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID and NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET are valid."; } } log$2.error(`Failed to fetch remote storage: ${message}`); process.exit(1); }); if (remoteManifest?.version !== hub.version) { log$2.warn(`\`${hub.projectUrl}\` is running \`@nuxthub/core@${remoteManifest?.version}\` while the local project is running \`@nuxthub/core@${hub.version}\`. Make sure to use the same version on both sides for a smooth experience.`); } Object.keys(remoteManifest?.storage || {}).filter((k) => hub[k] && !remoteManifest?.storage[k]).forEach((k) => { if (!remoteManifest?.storage[k]) { log$2.warn(`Remote storage \`${k}\` is enabled locally but it's not enabled in the remote project. Deploy a new version with \`${k}\` enabled to use it remotely.`); } }); const availableStorages = Object.keys(remoteManifest?.storage || {}).filter((k) => { if (k === "vectorize") { return Object.keys(hub.vectorize ?? {}).length && Object.keys(remoteManifest.storage.vectorize).length; } return hub[k] && remoteManifest?.storage[k]; }); if (availableStorages.length > 0) { const storageDescriptions = availableStorages.map((storage) => { if (storage === "vectorize") { const indexes = Object.keys(remoteManifest.storage.vectorize).join(", "); return `\`${storage} (${indexes})\``; } return `\`${storage}\``; }); logger.info(`Remote storage available: ${storageDescriptions.join(", ")}`); } else { log$2.fatal("No remote storage available: make sure to enable at least one of the storage options in your `nuxt.config.ts` and deploy new version before using remote storage. Read more at https://hub.nuxt.com/docs/getting-started/remote-storage"); process.exit(1); } } async function determineEnvironment(hub, projectKey, branch) { try { return await $fetch(`/api/projects/${projectKey}/environments/determine?branch=${branch}`, { method: "GET", baseURL: hub.url, headers: { authorization: `Bearer ${hub.userToken}` } }); } catch (error) { log$2.error("Failed to determine environment:", error); process.exit(1); } } const log$1 = logger.withTag("nuxt:hub"); function addBuildHooks(nuxt, hub) { if (!nuxt.options.dev && process.env.CF_PAGES && process.env.NUXT_HUB_PROJECT_DEPLOY_TOKEN && process.env.NUXT_HUB_PROJECT_KEY && process.env.NUXT_HUB_ENV) { hub.remote = false; nuxt.hook("modules:done", async () => { const { bindingsChanged } = await $fetch(`/api/projects/${process.env.NUXT_HUB_PROJECT_KEY}/build/${process.env.NUXT_HUB_ENV}/before`, { baseURL: hub.url, method: "POST", headers: { authorization: `Bearer ${process.env.NUXT_HUB_PROJECT_DEPLOY_TOKEN}` }, body: { pagesUrl: process.env.CF_PAGES_URL, ai: hub.ai, analytics: hub.analytics, blob: hub.blob, browser: hub.browser, cache: hub.cache, database: hub.database, kv: hub.kv, vectorize: hub.vectorize, bindings: hub.bindings } }).catch((e) => { if (e.response?._data?.message) { log$1.error(e.response._data.message); } else { log$1.error("Failed run build:before hook on NuxtHub.", e); } process.exit(1); }); if (bindingsChanged) { log$1.box([ "NuxtHub detected some changes in this project bindings and updated your Pages project on your Cloudflare account.", "In order to enable this changes, this deployment will be cancelled and a new one has been created." ].join("\n")); await new Promise((resolve2) => setTimeout(resolve2, 2e3)); process.exit(1); } }); nuxt.hook("build:error", async (error) => { await $fetch(`/api/projects/${process.env.NUXT_HUB_PROJECT_KEY}/build/${process.env.NUXT_HUB_ENV}/error`, { baseURL: hub.url, method: "POST", headers: { authorization: `Bearer ${process.env.NUXT_HUB_PROJECT_DEPLOY_TOKEN}` }, body: { pagesUrl: process.env.CF_PAGES_URL, error: { message: error.message, name: error.name, stack: error.stack } } }).catch(() => { }); }); nuxt.hook("nitro:init", async (nitro) => { nitro.hooks.hook("compiled", async () => { await $fetch(`/api/projects/${process.env.NUXT_HUB_PROJECT_KEY}/build/${process.env.NUXT_HUB_ENV}/done`, { baseURL: hub.url, method: "POST", headers: { authorization: `Bearer ${process.env.NUXT_HUB_PROJECT_DEPLOY_TOKEN}` }, body: { pagesUrl: process.env.CF_PAGES_URL } }).catch((e) => { if (e.response?._data?.message) { log$1.error(e.response._data.message); } else { log$1.error("Failed run compiled:done hook on NuxtHub.", e); } process.exit(1); }); if (hub.database) { const migrationsApplied = await applyRemoteDatabaseMigrations(hub); if (!migrationsApplied) { process.exit(1); } const queriesApplied = await applyRemoteDatabaseQueries(hub); if (!queriesApplied) { process.exit(1); } } }); }); } else { nuxt.hook("nitro:build:public-assets", async (nitro) => { const hubConfig = { ai: hub.ai, analytics: hub.analytics, blob: hub.blob, browser: hub.browser, cache: hub.cache, database: hub.database, kv: hub.kv, vectorize: hub.vectorize, bindings: hub.bindings, nitroPreset: nuxt.options.nitro.preset }; const distDir = nitro.options.output.dir || nitro.options.output.publicDir; await writeFile(join(distDir, "hub.config.json"), JSON.stringify(hubConfig, null, 2), "utf-8"); if (hub.database) { try { await cp(resolve$1(nitro.options.rootDir, hub.dir, "database"), resolve$1(nitro.options.output.dir, "database"), { recursive: true }); log$1.info("Database migrations and queries included in build"); } catch (error) { if (error.code === "ENOENT") { log$1.info("Skipping bundling database migrations - no migrations found"); } } } }); } } function getNitroPreset(hub, nitroOption) { if (nitroOption.preset) return nitroOption.preset.replace("-", "_"); if (hub.workers) { if (nitroOption?.experimental?.websocket) { return "cloudflare_durable"; } return "cloudflare_module"; } return "cloudflare_pages"; } const log = logger.withTag("nuxt:hub"); const module = defineNuxtModule({ meta: { name: "@nuxthub/core", configKey: "hub", version, docs: "https://hub.nuxt.com" }, defaults: {}, async setup(options, nuxt) { if (nuxt.options._generate) { log.error("NuxtHub is not compatible with `nuxt generate` as it needs a server to run."); log.info("To pre-render all pages: `https://hub.nuxt.com/docs/recipes/pre-rendering#pre-render-all-pages`"); return process.exit(1); } const rootDir = nuxt.options.rootDir; const { resolve } = createResolver(import.meta.url); const cliArgs = parseArgs(argv, { remote: { type: "string" }, hubEnv: { type: "string" } }); const remoteArg = cliArgs.remote === "" ? "true" : cliArgs.remote; const runtimeConfig = nuxt.options.runtimeConfig; const databaseMigrationsDirs = nuxt.options._layers?.map((layer) => join(layer.config.serverDir, "database/migrations")).filter(Boolean); const hub = defu({ url: process.env.NUXT_HUB_URL, userToken: process.env.NUXT_HUB_USER_TOKEN }, runtimeConfig.hub || {}, options, { // Self-hosted project projectUrl: process.env.NUXT_HUB_PROJECT_URL || "", projectSecretKey: process.env.NUXT_HUB_PROJECT_SECRET_KEY || "", // Deployed on NuxtHub url: process.env.NUXT_HUB_URL || "https://admin.hub.nuxt.com", projectKey: process.env.NUXT_HUB_PROJECT_KEY || "", userToken: process.env.NUXT_HUB_USER_TOKEN || "", // Remote storage remote: remoteArg || process.env.NUXT_HUB_REMOTE, remoteManifest: void 0, // Local storage dir: ".data/hub", // Workers support workers: void 0, // NuxtHub features ai: false, analytics: false, blob: false, browser: false, cache: false, database: false, kv: false, vectorize: {}, // Database Migrations databaseMigrationsDirs, databaseQueriesPaths: [], // Other options version, env: process.env.NUXT_HUB_ENV || cliArgs.hubEnv || "production", openapi: nuxt.options.nitro.experimental?.openAPI === true, // Extra bindings for the project bindings: { observability: { logs: true // enable with default settings }, hyperdrive: {}, compatibilityFlags: nuxt.options.nitro.cloudflare?.wrangler?.compatibility_flags }, // Cloudflare Access cloudflareAccess: { clientId: process.env.NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID || null, clientSecret: process.env.NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET || null } }); if (typeof hub.workers === "undefined") { const remoteProjectType = process.env.REMOTE_PROJECT_TYPE; if (remoteProjectType === "pages") { hub.workers = false; } else if (remoteProjectType === "workers") { hub.workers = true; } } if (process.env.REMOTE_PROJECT_TYPE === "pages" && nuxt.options.nitro.experimental?.websocket) { log.error("Nitro websocket is only compatible with Workers project type, but the current project type is Pages. Please link a new project with the Workers project type to use Nitro websocket."); process.exit(1); } if (!["test", "preview", "production"].includes(hub.env) && !hub.workers) { log.error("Invalid hub environment, should be `test`, `preview` or `production`"); process.exit(1); } if (nuxt.options.test) { hub.env = "test"; } if (hub.env === "test") { log.info("NuxtHub test environment detected, using `test` dataset for all storage & disabling remote storage."); hub.remote = false; } runtimeConfig.hub = hub; runtimeConfig.public.hub = {}; nuxt.options.nitro.cloudflare ||= {}; nuxt.options.nitro.cloudflare.deployConfig = false; nuxt.options.nitro.cloudflare.nodeCompat = true; delete nuxt.options.nitro.cloudflare?.wrangler?.compatibility_flags; if (nuxt.options.nitro.cloudflare?.wrangler && Object.keys(nuxt.options.nitro.cloudflare.wrangler).length) { log.warn("The `nitro.cloudflare.wrangler` defined options are not supported by NuxtHub, ignoring..."); nuxt.options.nitro.cloudflare.wrangler = {}; } if (hub.remote && !["true", "production", "preview"].includes(String(hub.remote)) && !hub.workers) { log.error("Invalid remote option, should be `false`, `true`, `'production'` or `'preview'`"); hub.remote = false; } if (hub.url !== "https://admin.hub.nuxt.com") { log.info(`Using \`${hub.url}\` as NuxtHub Admin URL`); } if (nuxt.options.dev) { addServerHandler({ route: "/api/_hub", middleware: true, handler: resolve("./runtime/cors.dev") }); } await setupBase(nuxt, hub); hub.openapi && setupOpenAPI(nuxt, hub); hub.ai && await setupAI(nuxt, hub); hub.analytics && setupAnalytics(); hub.blob && setupBlob(); hub.browser && await setupBrowser(nuxt); hub.cache && await setupCache(nuxt); hub.database && await setupDatabase(nuxt, hub); hub.kv && setupKV(); Object.keys(hub.vectorize).length && setupVectorize(nuxt, hub); if (nuxt.options._prepare) { return; } addBuildHooks(nuxt, hub); nuxt.options.nitro.rollupConfig = nuxt.options.nitro.rollupConfig || {}; nuxt.options.nitro.rollupConfig.plugins = [].concat(nuxt.options.nitro.rollupConfig.plugins || []); nuxt.options.nitro.rollupConfig.plugins.push({ name: "nuxthub-rollup-plugin", resolveId(id) { if (id.startsWith("cloudflare:")) { return { id, external: true }; } return null; } }); nuxt.options.nitro.experimental = nuxt.options.nitro.experimental || {}; nuxt.options.nitro.experimental.asyncContext = true; nuxt.options.nitro.unenv = nuxt.options.nitro.unenv || {}; nuxt.options.nitro.unenv.external = nuxt.options.nitro.unenv.external || []; if (!nuxt.options.nitro.unenv.external.includes("node:async_hooks")) { nuxt.options.nitro.unenv.external.push("node:async_hooks"); } if (hub.remote) { await setupRemote(nuxt, hub); vectorizeRemoteCheck(hub); } if (!hub.remote && !nuxt.options.dev) { nuxt.options.nitro.preset = getNitroPreset(hub, nuxt.options.nitro); if (!["cloudflare_pages", "cloudflare_module", "cloudflare_durable"].includes(nuxt.options.nitro.preset)) { log.error("NuxtHub is only compatible with the `cloudflare_pages`, `cloudflare_module` or `cloudflare_durable` presets."); process.exit(1); } if (nuxt.options.nitro.preset !== "cloudflare_pages" && nuxt.options.compatibilityDate?.default && nuxt.options.compatibilityDate.default < "2024-11-20") { log.warn("Found a compatibility date in `nuxt.config.ts` earlier than `2024-09-19`, forcing it to `2024-09-19`. Please update your `nuxt.config.ts` file."); nuxt.options.compatibilityDate.default = "2024-09-19"; } nuxt.options.nitro.output ||= {}; nuxt.options.nitro.output.dir = "dist"; nuxt.options.nitro.commands = nuxt.options.nitro.commands || {}; nuxt.options.nitro.commands.preview = "npx nuxthub preview"; nuxt.options.nitro.commands.deploy = "npx nuxthub deploy"; if (!nuxt.options.nitro.unenv.external.includes("node:stream")) { nuxt.options.nitro.unenv.external.push("node:stream"); } if (!nuxt.options.nitro.unenv.external.includes("node:process")) { nuxt.options.nitro.unenv.external.push("node:process"); } nuxt.options.nitro.unenv.alias ||= {}; if (!nuxt.options.nitro.unenv.alias["safer-buffer"]) { nuxt.options.nitro.unenv.alias["safer-buffer"] = "node:buffer"; } nuxt.options.nitro.handlers ||= []; nuxt.options.nitro.handlers.unshift({ middleware: true, handler: resolve("./runtime/env") }); } if (nuxt.options.dev) { if (!hub.remote) { log.info(`Using local storage from \`${relative(nuxt.options.rootDir, hub.dir)}\``); } const workspaceDir = await findWorkspaceDir(rootDir); const gitignorePath = join(workspaceDir, ".gitignore"); const gitignore = await readFile(gitignorePath, "utf-8").catch(() => ""); if (!gitignore.includes(".data")) { await writeFile(gitignorePath, `${gitignore ? gitignore + "\n" : gitignore}.data`, "utf-8"); } const needWrangler = Boolean(hub.analytics || hub.blob || hub.database || hub.kv || hub.cache); if (needWrangler) { const wranglerPath = join(hub.dir, "./wrangler.toml"); await writeFile(wranglerPath, generateWrangler(nuxt, hub), "utf-8"); nuxt.options.nitro.cloudflareDev = { persistDir: hub.dir, configPath: wranglerPath, silent: true }; await installModule("nitro-cloudflare-dev"); } addServerPlugin(resolve("./runtime/ready.dev")); } } }); export { module as default };