diff --git a/.changeset/silly-jokes-hammer.md b/.changeset/silly-jokes-hammer.md new file mode 100644 index 00000000..4adf4f12 --- /dev/null +++ b/.changeset/silly-jokes-hammer.md @@ -0,0 +1,12 @@ +--- +"@opennextjs/cloudflare": minor +--- + +feat: commands for cli actions + +The OpenNext Cloudflare CLI now uses the following commands; + +- `build`: build the application +- `populateCache`: populate either the local or remote cache +- `preview`: populate the local cache and start a dev server +- `deploy`: populate the remote cache and deploy to production diff --git a/examples/bugs/gh-119/package.json b/examples/bugs/gh-119/package.json index d5ce0213..a951ae4c 100644 --- a/examples/bugs/gh-119/package.json +++ b/examples/bugs/gh-119/package.json @@ -7,8 +7,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "pnpm opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts", "cf-typegen": "wrangler types --env-interface CloudflareEnv" }, diff --git a/examples/bugs/gh-219/package.json b/examples/bugs/gh-219/package.json index 68f099f7..abaacd4b 100644 --- a/examples/bugs/gh-219/package.json +++ b/examples/bugs/gh-219/package.json @@ -7,8 +7,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm run build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts", "deploy:worker": "pnpm run build:worker && pnpm wrangler deploy" }, diff --git a/examples/bugs/gh-223/package.json b/examples/bugs/gh-223/package.json index 0b6bae93..a7156dc2 100644 --- a/examples/bugs/gh-223/package.json +++ b/examples/bugs/gh-223/package.json @@ -7,8 +7,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm run build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts", "deploy:worker": "pnpm run build:worker && pnpm wrangler deploy" }, diff --git a/examples/common/config-e2e.ts b/examples/common/config-e2e.ts index 9996f261..a84d3460 100644 --- a/examples/common/config-e2e.ts +++ b/examples/common/config-e2e.ts @@ -25,11 +25,11 @@ export function configurePlaywright( if (isWorker) { if (isCI) { // Do not build on CI - there is a preceding build step - command = `pnpm wrangler dev --port ${port} --inspector-port ${inspectorPort}`; + command = `pnpm preview:worker -- --port ${port} --inspector-port ${inspectorPort}`; timeout = 100_000; } else { timeout = 500_000; - command = `pnpm preview --port ${port} --inspector-port ${inspectorPort}`; + command = `pnpm preview -- --port ${port} --inspector-port ${inspectorPort}`; } } else { timeout = 100_000; diff --git a/examples/create-next-app/package.json b/examples/create-next-app/package.json index 53062ea5..55e8b71b 100644 --- a/examples/create-next-app/package.json +++ b/examples/create-next-app/package.json @@ -7,8 +7,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/e2e/app-pages-router/package.json b/examples/e2e/app-pages-router/package.json index 5e0eec36..23e906b2 100644 --- a/examples/e2e/app-pages-router/package.json +++ b/examples/e2e/app-pages-router/package.json @@ -9,8 +9,9 @@ "start": "next start --port 3003", "lint": "next lint", "clean": "rm -rf .turbo node_modules .next .open-next", - "build:worker": "pnpm opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/e2e/app-router/package.json b/examples/e2e/app-router/package.json index 9d26a4c6..5c71e9a5 100644 --- a/examples/e2e/app-router/package.json +++ b/examples/e2e/app-router/package.json @@ -10,8 +10,9 @@ "lint": "next lint", "clean": "rm -rf .turbo node_modules .next .open-next", "d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS revalidations\"", - "build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare --populateCache=local", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/e2e/pages-router/package.json b/examples/e2e/pages-router/package.json index cfcc5b9f..96b3a8cd 100644 --- a/examples/e2e/pages-router/package.json +++ b/examples/e2e/pages-router/package.json @@ -9,8 +9,9 @@ "start": "next start --port 3002", "lint": "next lint", "clean": "rm -rf .turbo node_modules .next .open-next", - "build:worker": "pnpm opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/middleware/package.json b/examples/middleware/package.json index 33e62aa8..f712c4dc 100644 --- a/examples/middleware/package.json +++ b/examples/middleware/package.json @@ -6,8 +6,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "pnpm opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts", "e2e:dev": "playwright test -c e2e/playwright.dev.config.ts" }, diff --git a/examples/next-partial-prerendering/package.json b/examples/next-partial-prerendering/package.json index 79582b0e..1da41fa4 100644 --- a/examples/next-partial-prerendering/package.json +++ b/examples/next-partial-prerendering/package.json @@ -5,8 +5,9 @@ "build": "next build", "dev": "next dev --turbo", "start": "next start", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev" + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker" }, "dependencies": { "@heroicons/react": "2.1.5", diff --git a/examples/overrides/d1-tag-next/package.json b/examples/overrides/d1-tag-next/package.json index 01bdf566..7909151b 100644 --- a/examples/overrides/d1-tag-next/package.json +++ b/examples/overrides/d1-tag-next/package.json @@ -9,8 +9,9 @@ "lint": "next lint", "d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS revalidations\"", "d1:setup": "wrangler d1 execute NEXT_CACHE_D1 --command \"CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);\"", - "build:worker": "opennextjs-cloudflare && pnpm d1:clean && pnpm d1:setup", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare build && pnpm d1:setup", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/overrides/memory-queue/package.json b/examples/overrides/memory-queue/package.json index 0ed10a10..cfc9a5cf 100644 --- a/examples/overrides/memory-queue/package.json +++ b/examples/overrides/memory-queue/package.json @@ -7,8 +7,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/overrides/r2-incremental-cache/package.json b/examples/overrides/r2-incremental-cache/package.json index 8eda7f82..f6bd1725 100644 --- a/examples/overrides/r2-incremental-cache/package.json +++ b/examples/overrides/r2-incremental-cache/package.json @@ -8,8 +8,9 @@ "start": "next start", "lint": "next lint", "d1:clean": "wrangler d1 execute NEXT_CACHE_D1 --command \"DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS revalidations\"", - "build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare --populateCache=local", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm d1:clean && pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/playground14/package.json b/examples/playground14/package.json index 6dbec953..8c7d2614 100644 --- a/examples/playground14/package.json +++ b/examples/playground14/package.json @@ -8,8 +8,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "pnpm opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts", "e2e:dev": "playwright test -c e2e/playwright.dev.config.ts", "cf-typegen": "wrangler types --env-interface CloudflareEnv" diff --git a/examples/playground15/package.json b/examples/playground15/package.json index 29967395..449e996c 100644 --- a/examples/playground15/package.json +++ b/examples/playground15/package.json @@ -8,8 +8,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "pnpm opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts", "e2e:dev": "playwright test -c e2e/playwright.dev.config.ts", "cf-typegen": "wrangler types --env-interface CloudflareEnv" diff --git a/examples/ssg-app/package.json b/examples/ssg-app/package.json index da716640..681ded13 100644 --- a/examples/ssg-app/package.json +++ b/examples/ssg-app/package.json @@ -7,8 +7,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev", + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker", "e2e": "playwright test -c e2e/playwright.config.ts" }, "dependencies": { diff --git a/examples/vercel-blog-starter/package.json b/examples/vercel-blog-starter/package.json index 7c9a4b8d..5cf746e3 100644 --- a/examples/vercel-blog-starter/package.json +++ b/examples/vercel-blog-starter/package.json @@ -5,8 +5,9 @@ "dev": "next", "build": "next build", "start": "next start", - "build:worker": "opennextjs-cloudflare", - "preview": "pnpm build:worker && pnpm wrangler dev" + "build:worker": "pnpm opennextjs-cloudflare build", + "preview:worker": "pnpm opennextjs-cloudflare preview", + "preview": "pnpm build:worker && pnpm preview:worker" }, "dependencies": { "classnames": "^2.5.1", diff --git a/examples/vercel-commerce/package.json b/examples/vercel-commerce/package.json index e50d63bc..6341cb5d 100644 --- a/examples/vercel-commerce/package.json +++ b/examples/vercel-commerce/package.json @@ -12,8 +12,9 @@ "prettier": "prettier --write --ignore-unknown .", "prettier:check": "prettier --check --ignore-unknown .", "test": "pnpm prettier:check", - "tofix-build:worker": "opennextjs-cloudflare", - "tofix-preview": "pnpm build:worker && pnpm wrangler dev" + "tofix-build:worker": "pnpm opennextjs-cloudflare build", + "tofix-preview:worker": "pnpm opennextjs-cloudflare preview", + "tofix-preview": "pnpm build:worker && pnpm preview:worker" }, "dependencies": { "@headlessui/react": "^2.1.2", diff --git a/packages/cloudflare/README.md b/packages/cloudflare/README.md index bfc0336a..cc1f5e72 100644 --- a/packages/cloudflare/README.md +++ b/packages/cloudflare/README.md @@ -19,13 +19,13 @@ Run the following commands to preview the production build of your application l - build the app and adapt it for Cloudflare ```bash - npx opennextjs-cloudflare + npx opennextjs-cloudflare build # or - pnpm opennextjs-cloudflare + pnpm opennextjs-cloudflare build # or - yarn opennextjs-cloudflare + yarn opennextjs-cloudflare build # or - bun opennextjs-cloudflare + bun opennextjs-cloudflare build ``` - Preview the app in Wrangler @@ -47,11 +47,11 @@ Deploy your application to production with the following: - build the app and adapt it for Cloudflare ```bash - npx opennextjs-cloudflare && npx wrangler deploy + npx opennextjs-cloudflare build && npx opennextjs-cloudflare deploy # or - pnpm opennextjs-cloudflare && pnpm wrangler deploy + pnpm opennextjs-cloudflare build && pnpm opennextjs-cloudflare deploy # or - yarn opennextjs-cloudflare && yarn wrangler deploy + yarn opennextjs-cloudflare build && yarn opennextjs-cloudflare deploy # or - bun opennextjs-cloudflare && bun wrangler deploy + bun opennextjs-cloudflare build && bun opennextjs-cloudflare deploy ``` diff --git a/packages/cloudflare/src/cli/args.ts b/packages/cloudflare/src/cli/args.ts index 5a546ad9..1dbec4f2 100644 --- a/packages/cloudflare/src/cli/args.ts +++ b/packages/cloudflare/src/cli/args.ts @@ -2,71 +2,61 @@ import { mkdirSync, type Stats, statSync } from "node:fs"; import { resolve } from "node:path"; import { parseArgs } from "node:util"; -import type { CacheBindingMode } from "./build/utils/index.js"; -import { isCacheBindingMode } from "./build/utils/index.js"; +import { isWranglerTarget, WranglerTarget } from "./utils/run-wrangler.js"; -export function getArgs(): { - skipNextBuild: boolean; - skipWranglerConfigCheck: boolean; - outputDir?: string; - minify: boolean; - populateCache?: { mode: CacheBindingMode; onlyPopulateWithoutBuilding: boolean }; -} { - const { skipBuild, skipWranglerConfigCheck, output, noMinify, populateCache, onlyPopulateCache } = - parseArgs({ - options: { - skipBuild: { - type: "boolean", - short: "s", - default: false, - }, - output: { - type: "string", - short: "o", - }, - noMinify: { - type: "boolean", - default: false, - }, - skipWranglerConfigCheck: { - type: "boolean", - default: false, - }, - populateCache: { - type: "string", - }, - onlyPopulateCache: { - type: "boolean", - default: false, - }, - }, - allowPositionals: false, - }).values; +export type Arguments = ( + | { + command: "build"; + skipNextBuild: boolean; + skipWranglerConfigCheck: boolean; + minify: boolean; + } + | { command: "preview" | "deploy"; passthroughArgs: string[] } + | { command: "populateCache"; target: WranglerTarget } +) & { outputDir?: string }; - const outputDir = output ? resolve(output) : undefined; +export function getArgs(): Arguments { + const { positionals, values } = parseArgs({ + options: { + skipBuild: { type: "boolean", short: "s", default: false }, + output: { type: "string", short: "o" }, + noMinify: { type: "boolean", default: false }, + skipWranglerConfigCheck: { type: "boolean", default: false }, + }, + allowPositionals: true, + }); - if (outputDir) { - assertDirArg(outputDir, "output", true); - } + const outputDir = values.output ? resolve(values.output) : undefined; + if (outputDir) assertDirArg(outputDir, "output", true); - if ( - (populateCache !== undefined || onlyPopulateCache) && - (!populateCache?.length || !isCacheBindingMode(populateCache)) - ) { - throw new Error(`Error: missing mode for populate cache flag, expected 'local' | 'remote'`); + switch (positionals[0]) { + case "build": + return { + command: "build", + outputDir, + skipNextBuild: + values.skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), + skipWranglerConfigCheck: + values.skipWranglerConfigCheck || + ["1", "true", "yes"].includes(String(process.env.SKIP_WRANGLER_CONFIG_CHECK)), + minify: !values.noMinify, + }; + case "preview": + case "deploy": + return { command: positionals[0], outputDir, passthroughArgs: getPassthroughArgs() }; + case "populateCache": + if (!isWranglerTarget(positionals[1])) { + throw new Error(`Error: invalid target for populating the cache, expected 'local' | 'remote'`); + } + return { command: "populateCache", outputDir, target: positionals[1] }; + default: + throw new Error("Error: invalid command, expected 'build' | 'preview' | 'deploy' | 'populateCache'"); } +} - return { - outputDir, - skipNextBuild: skipBuild || ["1", "true", "yes"].includes(String(process.env.SKIP_NEXT_APP_BUILD)), - skipWranglerConfigCheck: - skipWranglerConfigCheck || - ["1", "true", "yes"].includes(String(process.env.SKIP_WRANGLER_CONFIG_CHECK)), - minify: !noMinify, - populateCache: populateCache - ? { mode: populateCache, onlyPopulateWithoutBuilding: !!onlyPopulateCache } - : undefined, - }; +function getPassthroughArgs() { + const passthroughPos = process.argv.indexOf("--"); + return passthroughPos === -1 ? [] : process.argv.slice(passthroughPos + 1); } function assertDirArg(path: string, argName?: string, make?: boolean) { diff --git a/packages/cloudflare/src/cli/build/build.ts b/packages/cloudflare/src/cli/build/build.ts index 4d78bba6..f229be25 100644 --- a/packages/cloudflare/src/cli/build/build.ts +++ b/packages/cloudflare/src/cli/build/build.ts @@ -1,14 +1,12 @@ -import { createRequire } from "node:module"; -import { dirname } from "node:path"; - import { buildNextjsApp, setStandaloneBuildMode } from "@opennextjs/aws/build/buildNextApp.js"; import { compileCache } from "@opennextjs/aws/build/compileCache.js"; -import { compileOpenNextConfig } from "@opennextjs/aws/build/compileConfig.js"; import { createCacheAssets, createStaticAssets } from "@opennextjs/aws/build/createAssets.js"; import { createMiddleware } from "@opennextjs/aws/build/createMiddleware.js"; import * as buildHelper from "@opennextjs/aws/build/helper.js"; -import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js"; +import { BuildOptions } from "@opennextjs/aws/build/helper.js"; +import { printHeader } from "@opennextjs/aws/build/utils.js"; import logger from "@opennextjs/aws/logger.js"; +import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; import type { ProjectOptions } from "../project-options.js"; import { bundleServer } from "./bundle-server.js"; @@ -17,12 +15,7 @@ import { compileEnvFiles } from "./open-next/compile-env-files.js"; import { compileDurableObjects } from "./open-next/compileDurableObjects.js"; import { copyCacheAssets } from "./open-next/copyCacheAssets.js"; import { createServerBundle } from "./open-next/createServerBundle.js"; -import { - createOpenNextConfigIfNotExistent, - createWranglerConfigIfNotExistent, - ensureCloudflareConfig, - populateCache, -} from "./utils/index.js"; +import { createWranglerConfigIfNotExistent } from "./utils/index.js"; import { getVersion } from "./utils/version.js"; /** @@ -30,27 +23,15 @@ import { getVersion } from "./utils/version.js"; * * It saves the output in a `.worker-next` directory * + * @param options The OpenNext options + * @param config The OpenNext config * @param projectOpts The options for the project */ -export async function build(projectOpts: ProjectOptions): Promise { - printHeader("Cloudflare build"); - - showWarningOnWindows(); - - const baseDir = projectOpts.sourceDir; - const require = createRequire(import.meta.url); - const openNextDistDir = dirname(require.resolve("@opennextjs/aws/index.js")); - - await createOpenNextConfigIfNotExistent(projectOpts); - - const { config, buildDir } = await compileOpenNextConfig(baseDir); - - ensureCloudflareConfig(config); - - // Initialize options - const options = buildHelper.normalizeOptions(config, openNextDistDir, buildDir); - logger.setLevel(options.debug ? "debug" : "info"); - +export async function build( + options: BuildOptions, + config: OpenNextConfig, + projectOpts: ProjectOptions +): Promise { // Do not minify the code so that we can apply string replacement patch. // Note that wrangler will still minify the bundle. options.minify = false; @@ -64,11 +45,6 @@ export async function build(projectOpts: ProjectOptions): Promise { logger.info(`@opennextjs/cloudflare version: ${cloudflare}`); logger.info(`@opennextjs/aws version: ${aws}`); - if (projectOpts.populateCache?.onlyPopulateWithoutBuilding) { - populateCache(options, config, projectOpts.populateCache.mode); - return; - } - if (projectOpts.skipNextBuild) { logger.warn("Skipping Next.js build"); } else { @@ -112,10 +88,6 @@ export async function build(projectOpts: ProjectOptions): Promise { await createWranglerConfigIfNotExistent(projectOpts); } - if (projectOpts.populateCache) { - populateCache(options, config, projectOpts.populateCache.mode); - } - logger.info("OpenNext build complete."); } diff --git a/packages/cloudflare/src/cli/build/utils/create-config-files.ts b/packages/cloudflare/src/cli/build/utils/create-config-files.ts index b13f735e..8515bfd1 100644 --- a/packages/cloudflare/src/cli/build/utils/create-config-files.ts +++ b/packages/cloudflare/src/cli/build/utils/create-config-files.ts @@ -92,10 +92,10 @@ export async function getLatestCompatDate(): Promise { * * If the user refuses an error is thrown (since the file is mandatory). * - * @param projectOpts The options for the project + * @param sourceDir The source directory for the project */ -export async function createOpenNextConfigIfNotExistent(projectOpts: ProjectOptions): Promise { - const openNextConfigPath = join(projectOpts.sourceDir, "open-next.config.ts"); +export async function createOpenNextConfigIfNotExistent(sourceDir: string): Promise { + const openNextConfigPath = join(sourceDir, "open-next.config.ts"); if (!existsSync(openNextConfigPath)) { const answer = await askConfirmation( diff --git a/packages/cloudflare/src/cli/build/utils/index.ts b/packages/cloudflare/src/cli/build/utils/index.ts index e7fb383b..cca97f02 100644 --- a/packages/cloudflare/src/cli/build/utils/index.ts +++ b/packages/cloudflare/src/cli/build/utils/index.ts @@ -4,4 +4,3 @@ export * from "./ensure-cf-config.js"; export * from "./extract-project-env-vars.js"; export * from "./needs-experimental-react.js"; export * from "./normalize-path.js"; -export * from "./populate-cache.js"; diff --git a/packages/cloudflare/src/cli/deploy/deploy.ts b/packages/cloudflare/src/cli/deploy/deploy.ts new file mode 100644 index 00000000..31f71477 --- /dev/null +++ b/packages/cloudflare/src/cli/deploy/deploy.ts @@ -0,0 +1,14 @@ +import { BuildOptions } from "@opennextjs/aws/build/helper.js"; +import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; + +import { populateCache } from "../populate-cache/populate-cache.js"; +import { runWrangler } from "../utils/run-wrangler.js"; + +export async function deploy( + options: BuildOptions, + config: OpenNextConfig, + deployOptions: { passthroughArgs: string[] } +) { + await populateCache(options, config, { target: "remote" }); + runWrangler(options, ["deploy", ...deployOptions.passthroughArgs], { logging: "all" }); +} diff --git a/packages/cloudflare/src/cli/index.ts b/packages/cloudflare/src/cli/index.ts index be3c1e76..9c966807 100644 --- a/packages/cloudflare/src/cli/index.ts +++ b/packages/cloudflare/src/cli/index.ts @@ -1,18 +1,49 @@ #!/usr/bin/env node -import { resolve } from "node:path"; +import { createRequire } from "node:module"; +import path from "node:path"; -import { getArgs } from "./args.js"; +import { compileOpenNextConfig } from "@opennextjs/aws/build/compileConfig.js"; +import { normalizeOptions } from "@opennextjs/aws/build/helper.js"; +import { printHeader, showWarningOnWindows } from "@opennextjs/aws/build/utils.js"; +import logger from "@opennextjs/aws/logger.js"; + +import { Arguments, getArgs } from "./args.js"; import { build } from "./build/build.js"; +import { createOpenNextConfigIfNotExistent, ensureCloudflareConfig } from "./build/utils/index.js"; +import { deploy } from "./deploy/deploy.js"; +import { populateCache } from "./populate-cache/populate-cache.js"; +import { preview } from "./preview/preview.js"; const nextAppDir = process.cwd(); -const { skipNextBuild, skipWranglerConfigCheck, outputDir, minify, populateCache } = getArgs(); +async function runCommand(args: Arguments) { + printHeader(`Cloudflare ${args.command}`); + + showWarningOnWindows(); + + const baseDir = nextAppDir; + const require = createRequire(import.meta.url); + const openNextDistDir = path.dirname(require.resolve("@opennextjs/aws/index.js")); + + await createOpenNextConfigIfNotExistent(baseDir); + const { config, buildDir } = await compileOpenNextConfig(baseDir); + + ensureCloudflareConfig(config); + + // Initialize options + const options = normalizeOptions(config, openNextDistDir, buildDir); + logger.setLevel(options.debug ? "debug" : "info"); + + switch (args.command) { + case "build": + return build(options, config, { ...args, sourceDir: baseDir }); + case "preview": + return preview(options, config, args); + case "deploy": + return deploy(options, config, args); + case "populateCache": + return populateCache(options, config, args); + } +} -await build({ - sourceDir: nextAppDir, - outputDir: resolve(outputDir ?? nextAppDir, ".open-next"), - skipNextBuild, - skipWranglerConfigCheck, - minify, - populateCache, -}); +await runCommand(getArgs()); diff --git a/packages/cloudflare/src/cli/build/utils/populate-cache.ts b/packages/cloudflare/src/cli/populate-cache/populate-cache.ts similarity index 63% rename from packages/cloudflare/src/cli/build/utils/populate-cache.ts rename to packages/cloudflare/src/cli/populate-cache/populate-cache.ts index cc6ea02e..949a04f7 100644 --- a/packages/cloudflare/src/cli/build/utils/populate-cache.ts +++ b/packages/cloudflare/src/cli/populate-cache/populate-cache.ts @@ -1,4 +1,3 @@ -import { spawnSync } from "node:child_process"; import { existsSync } from "node:fs"; import path from "node:path"; @@ -13,7 +12,8 @@ import type { import type { IncrementalCache, TagCache } from "@opennextjs/aws/types/overrides.js"; import { globSync } from "glob"; -export type CacheBindingMode = "local" | "remote"; +import type { WranglerTarget } from "../utils/run-wrangler.js"; +import { runWrangler } from "../utils/run-wrangler.js"; async function resolveCacheName( value: @@ -25,32 +25,6 @@ async function resolveCacheName( return typeof value === "function" ? (await value()).name : value; } -function runWrangler( - opts: BuildOptions, - wranglerOpts: { mode: CacheBindingMode; excludeRemoteFlag?: boolean }, - args: string[] -) { - const result = spawnSync( - opts.packager, - [ - "exec", - "wrangler", - ...args, - wranglerOpts.mode === "remote" && !wranglerOpts.excludeRemoteFlag && "--remote", - wranglerOpts.mode === "local" && "--local", - ].filter((v): v is string => !!v), - { - shell: true, - stdio: ["ignore", "ignore", "inherit"], - } - ); - - if (result.status !== 0) { - logger.error("Failed to populate cache"); - process.exit(1); - } -} - function getCacheAssetPaths(opts: BuildOptions) { return globSync(path.join(opts.outputDir, "cache/**/*"), { withFileTypes: true, @@ -69,10 +43,14 @@ function getCacheAssetPaths(opts: BuildOptions) { }); } -export async function populateCache(opts: BuildOptions, config: OpenNextConfig, mode: CacheBindingMode) { +export async function populateCache( + options: BuildOptions, + config: OpenNextConfig, + populateCacheOptions: { target: WranglerTarget } +) { const { incrementalCache, tagCache } = config.default.override ?? {}; - if (!existsSync(opts.outputDir)) { + if (!existsSync(options.outputDir)) { logger.error("Unable to populate cache: Open Next build not found"); process.exit(1); } @@ -83,7 +61,7 @@ export async function populateCache(opts: BuildOptions, config: OpenNextConfig, case "r2-incremental-cache": { logger.info("\nPopulating R2 incremental cache..."); - const assets = getCacheAssetPaths(opts); + const assets = getCacheAssetPaths(options); assets.forEach(({ fsPath, destPath }) => { const fullDestPath = path.join( "NEXT_CACHE_R2_BUCKET", @@ -91,11 +69,11 @@ export async function populateCache(opts: BuildOptions, config: OpenNextConfig, destPath ); - runWrangler(opts, { mode, excludeRemoteFlag: true }, [ - "r2 object put", - JSON.stringify(fullDestPath), - `--file ${JSON.stringify(fsPath)}`, - ]); + runWrangler( + options, + ["r2 object put", JSON.stringify(fullDestPath), `--file ${JSON.stringify(fsPath)}`], + { ...populateCacheOptions, excludeRemoteFlag: true, logging: "error" } + ); }); logger.info(`Successfully populated cache with ${assets.length} assets`); break; @@ -111,11 +89,15 @@ export async function populateCache(opts: BuildOptions, config: OpenNextConfig, case "d1-tag-cache": { logger.info("\nPopulating D1 tag cache..."); - runWrangler(opts, { mode }, [ - "d1 execute", - "NEXT_CACHE_D1", - `--file ${JSON.stringify(path.join(opts.outputDir, "cloudflare/cache-assets-manifest.sql"))}`, - ]); + runWrangler( + options, + [ + "d1 execute", + "NEXT_CACHE_D1", + `--file ${JSON.stringify(path.join(options.outputDir, "cloudflare/cache-assets-manifest.sql"))}`, + ], + { ...populateCacheOptions, logging: "error" } + ); logger.info("Successfully populated cache"); break; } @@ -124,7 +106,3 @@ export async function populateCache(opts: BuildOptions, config: OpenNextConfig, } } } - -export function isCacheBindingMode(v: string | undefined): v is CacheBindingMode { - return !!v && ["local", "remote"].includes(v); -} diff --git a/packages/cloudflare/src/cli/preview/preview.ts b/packages/cloudflare/src/cli/preview/preview.ts new file mode 100644 index 00000000..b4df9d0d --- /dev/null +++ b/packages/cloudflare/src/cli/preview/preview.ts @@ -0,0 +1,14 @@ +import { BuildOptions } from "@opennextjs/aws/build/helper.js"; +import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; + +import { populateCache } from "../populate-cache/populate-cache.js"; +import { runWrangler } from "../utils/run-wrangler.js"; + +export async function preview( + options: BuildOptions, + config: OpenNextConfig, + previewOptions: { passthroughArgs: string[] } +) { + await populateCache(options, config, { target: "local" }); + runWrangler(options, ["dev", ...previewOptions.passthroughArgs], { logging: "all" }); +} diff --git a/packages/cloudflare/src/cli/project-options.ts b/packages/cloudflare/src/cli/project-options.ts index 15b23259..7e41d8ba 100644 --- a/packages/cloudflare/src/cli/project-options.ts +++ b/packages/cloudflare/src/cli/project-options.ts @@ -1,15 +1,13 @@ -import type { CacheBindingMode } from "./build/utils/index.js"; +import type { WranglerTarget } from "./utils/run-wrangler.js"; export type ProjectOptions = { // Next app root folder sourceDir: string; - // The directory to save the output to (defaults to the app's directory) - outputDir: string; // Whether the Next.js build should be skipped (i.e. if the `.next` dir is already built) skipNextBuild: boolean; // Whether the check to see if a wrangler config file exists should be skipped skipWranglerConfigCheck: boolean; // Whether minification of the worker should be enabled minify: boolean; - populateCache?: { mode: CacheBindingMode; onlyPopulateWithoutBuilding: boolean }; + populateCache?: { mode: WranglerTarget; onlyPopulateWithoutBuilding: boolean }; }; diff --git a/packages/cloudflare/src/cli/utils/run-wrangler.ts b/packages/cloudflare/src/cli/utils/run-wrangler.ts new file mode 100644 index 00000000..6852d7b6 --- /dev/null +++ b/packages/cloudflare/src/cli/utils/run-wrangler.ts @@ -0,0 +1,36 @@ +import { spawnSync } from "node:child_process"; + +import type { BuildOptions } from "@opennextjs/aws/build/helper.js"; +import logger from "@opennextjs/aws/logger.js"; + +export type WranglerTarget = "local" | "remote"; + +export function runWrangler( + options: BuildOptions, + args: string[], + wranglerOpts: { target?: WranglerTarget; excludeRemoteFlag?: boolean; logging?: "all" | "error" } = {} +) { + const result = spawnSync( + options.packager, + [ + "exec", + "wrangler", + ...args, + wranglerOpts.target === "remote" && !wranglerOpts.excludeRemoteFlag && "--remote", + wranglerOpts.target === "local" && "--local", + ].filter((v): v is string => !!v), + { + shell: true, + stdio: wranglerOpts.logging === "error" ? ["ignore", "ignore", "inherit"] : "inherit", + } + ); + + if (result.status !== 0) { + logger.error("Wrangler command failed"); + process.exit(1); + } +} + +export function isWranglerTarget(v: string | undefined): v is WranglerTarget { + return !!v && ["local", "remote"].includes(v); +}