@nuxthub/core
Version:
Build full-stack Nuxt applications on Cloudflare, with zero configuration.
773 lines (764 loc) • 29.8 kB
JavaScript
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 };