diff --git a/packages/cloudflare/src/cli/build/build.ts b/packages/cloudflare/src/cli/build/build.ts new file mode 100644 index 00000000..9f3d8d6d --- /dev/null +++ b/packages/cloudflare/src/cli/build/build.ts @@ -0,0 +1,119 @@ +import { cpSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } 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 logger from "@opennextjs/aws/logger.js"; + +import type { ProjectOptions } from "../config.js"; +import { containsDotNextDir, getConfig } from "../config.js"; +import { bundleServer } from "./bundle-server.js"; +import { compileEnvFiles } from "./open-next/compile-env-files.js"; +import { copyCacheAssets } from "./open-next/copyCacheAssets.js"; +import { createServerBundle } from "./open-next/createServerBundle.js"; +import { + createOpenNextConfigIfNotExistent, + createWranglerConfigIfNotExistent, + ensureCloudflareConfig, +} from "./utils/index.js"; + +/** + * Builds the application in a format that can be passed to workerd + * + * It saves the output in a `.worker-next` directory + * + * @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"); + + // Do not minify the code so that we can apply string replacement patch. + // Note that wrangler will still minify the bundle. + options.minify = false; + + // Pre-build validation + buildHelper.checkRunningInsideNextjsApp(options); + logger.info(`App directory: ${options.appPath}`); + buildHelper.printNextjsVersion(options); + ensureNextjsVersionSupported(options); + buildHelper.printOpenNextVersion(options); + + if (projectOpts.skipNextBuild) { + logger.warn("Skipping Next.js build"); + } else { + // Build the next app + printHeader("Building Next.js app"); + setStandaloneBuildMode(options); + buildNextjsApp(options); + } + + if (!containsDotNextDir(projectOpts.sourceDir)) { + throw new Error(`.next folder not found in ${projectOpts.sourceDir}`); + } + + // Generate deployable bundle + printHeader("Generating bundle"); + buildHelper.initOutputDir(options); + + // Compile cache.ts + compileCache(options); + + // Compile .env files + compileEnvFiles(options); + + // Compile middleware + await createMiddleware(options, { forceOnlyBuildOnce: true }); + + createStaticAssets(options); + + if (config.dangerous?.disableIncrementalCache !== true) { + createCacheAssets(options); + copyCacheAssets(options); + } + + await createServerBundle(options); + + // TODO: drop this copy. + // Copy the .next directory to the output directory so it can be mutated. + cpSync(join(projectOpts.sourceDir, ".next"), join(projectOpts.outputDir, ".next"), { recursive: true }); + + const projConfig = getConfig(projectOpts); + + // TODO: rely on options only. + await bundleServer(projConfig, options); + + if (!projectOpts.skipWranglerConfigCheck) { + await createWranglerConfigIfNotExistent(projectOpts); + } + + logger.info("OpenNext build complete."); +} + +function ensureNextjsVersionSupported(options: buildHelper.BuildOptions) { + if (buildHelper.compareSemver(options.nextVersion, "14.0.0") < 0) { + logger.error("Next.js version unsupported, please upgrade to version 14 or greater."); + process.exit(1); + } +} diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 52374e64..35cd43c2 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -8,7 +8,7 @@ import { build, Plugin } from "esbuild"; import { Config } from "../config.js"; import * as patches from "./patches/index.js"; -import { normalizePath } from "./utils/index.js"; +import { normalizePath, patchCodeWithValidations } from "./utils/index.js"; /** The dist directory of the Cloudflare adapter package */ const packageDistDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "../.."); @@ -190,37 +190,6 @@ function createFixRequiresESBuildPlugin(config: Config): Plugin { }; } -/** - * Applies multiple code patches in order to a given piece of code, at each step it validates that the code - * has actually been patched/changed, if not an error is thrown - * - * @param code the code to apply the patches to - * @param patches array of tuples, containing a string indicating the target of the patching (for logging) and - * a patching function that takes a string (pre-patch code) and returns a string (post-patch code) - * @returns the patched code - */ -async function patchCodeWithValidations( - code: string, - patches: [string, (code: string) => string | Promise, opts?: { isOptional?: boolean }][] -): Promise { - console.log(`Applying code patches:`); - let patchedCode = code; - - for (const [target, patchFunction, opts] of patches) { - console.log(` - patching ${target}`); - - const prePatchCode = patchedCode; - patchedCode = await patchFunction(patchedCode); - - if (!opts?.isOptional && prePatchCode === patchedCode) { - throw new Error(`Failed to patch ${target}`); - } - } - - console.log(`All ${patches.length} patches applied\n`); - return patchedCode; -} - /** * Gets the path of the worker.js file generated by the build process * diff --git a/packages/cloudflare/src/cli/build/index.ts b/packages/cloudflare/src/cli/build/index.ts deleted file mode 100644 index 81c32056..00000000 --- a/packages/cloudflare/src/cli/build/index.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs"; -import { createRequire } from "node:module"; -import { dirname, join } 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 logger from "@opennextjs/aws/logger.js"; -import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; - -import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js"; -import type { ProjectOptions } from "../config.js"; -import { containsDotNextDir, getConfig } from "../config.js"; -import { askConfirmation } from "../utils/ask-confirmation.js"; -import { bundleServer } from "./bundle-server.js"; -import { compileEnvFiles } from "./open-next/compile-env-files.js"; -import { copyCacheAssets } from "./open-next/copyCacheAssets.js"; -import { createServerBundle } from "./open-next/createServerBundle.js"; - -/** - * Builds the application in a format that can be passed to workerd - * - * It saves the output in a `.worker-next` directory - * - * @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"); - - // Do not minify the code so that we can apply string replacement patch. - // Note that wrangler will still minify the bundle. - options.minify = false; - - // Pre-build validation - buildHelper.checkRunningInsideNextjsApp(options); - logger.info(`App directory: ${options.appPath}`); - buildHelper.printNextjsVersion(options); - ensureNextjsVersionSupported(options); - buildHelper.printOpenNextVersion(options); - - if (projectOpts.skipNextBuild) { - logger.warn("Skipping Next.js build"); - } else { - // Build the next app - printHeader("Building Next.js app"); - setStandaloneBuildMode(options); - buildNextjsApp(options); - } - - if (!containsDotNextDir(projectOpts.sourceDir)) { - throw new Error(`.next folder not found in ${projectOpts.sourceDir}`); - } - - // Generate deployable bundle - printHeader("Generating bundle"); - buildHelper.initOutputDir(options); - - // Compile cache.ts - compileCache(options); - - // Compile .env files - compileEnvFiles(options); - - // Compile middleware - await createMiddleware(options, { forceOnlyBuildOnce: true }); - - createStaticAssets(options); - - if (config.dangerous?.disableIncrementalCache !== true) { - createCacheAssets(options); - copyCacheAssets(options); - } - - await createServerBundle(options); - - // TODO: drop this copy. - // Copy the .next directory to the output directory so it can be mutated. - cpSync(join(projectOpts.sourceDir, ".next"), join(projectOpts.outputDir, ".next"), { recursive: true }); - - const projConfig = getConfig(projectOpts); - - // TODO: rely on options only. - await bundleServer(projConfig, options); - - if (!projectOpts.skipWranglerConfigCheck) { - await createWranglerConfigIfNotExistent(projectOpts); - } - - logger.info("OpenNext build complete."); -} - -/** - * Creates a `open-next.config.ts` file for the user if it doesn't exist, but only after asking for the user's confirmation. - * - * If the user refuses an error is thrown (since the file is mandatory). - * - * @param projectOpts The options for the project - */ -async function createOpenNextConfigIfNotExistent(projectOpts: ProjectOptions): Promise { - const openNextConfigPath = join(projectOpts.sourceDir, "open-next.config.ts"); - - if (!existsSync(openNextConfigPath)) { - const answer = await askConfirmation( - "Missing required `open-next.config.ts` file, do you want to create one?" - ); - - if (!answer) { - throw new Error("The `open-next.config.ts` file is required, aborting!"); - } - - cpSync(join(getPackageTemplatesDirPath(), "defaults", "open-next.config.ts"), openNextConfigPath); - } -} - -/** - * Ensures open next is configured for cloudflare. - * - * @param config OpenNext configuration. - */ -function ensureCloudflareConfig(config: OpenNextConfig) { - const requirements = { - dftUseCloudflareWrapper: config.default?.override?.wrapper === "cloudflare-node", - dftUseEdgeConverter: config.default?.override?.converter === "edge", - dftMaybeUseCache: - config.default?.override?.incrementalCache === "dummy" || - typeof config.default?.override?.incrementalCache === "function", - dftUseDummyTagCacheAndQueue: - config.default?.override?.tagCache === "dummy" && config.default?.override?.queue === "dummy", - disableCacheInterception: config.dangerous?.enableCacheInterception !== true, - mwIsMiddlewareExternal: config.middleware?.external == true, - mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge", - mwUseEdgeConverter: config.middleware?.override?.converter === "edge", - mwUseFetchProxy: config.middleware?.override?.proxyExternalRequest === "fetch", - }; - - if (Object.values(requirements).some((satisfied) => !satisfied)) { - throw new Error( - "The `open-next.config.ts` should have a default export like this:\n\n" + - `{ - default: { - override: { - wrapper: "cloudflare-node", - converter: "edge", - incrementalCache: "dummy" | function, - tagCache: "dummy", - queue: "dummy", - }, - }, - - middleware: { - external: true, - override: { - wrapper: "cloudflare-edge", - converter: "edge", - proxyExternalRequest: "fetch", - }, - }, - - "dangerous": { - "enableCacheInterception": false - }, - }\n\n`.replace(/^ {8}/gm, "") - ); - } -} - -/** - * Creates a `wrangler.json` file for the user if a wrangler config file doesn't already exist, - * but only after asking for the user's confirmation. - * - * If the user refuses a warning is shown (which offers ways to opt out of this check to the user). - * - * Note: we generate a wrangler.json file with comments instead of using the jsonc extension, - * we decided to do that since json is more common than jsonc, wrangler also parses - * them in the same way and we also expect developers to associate `wrangler.json` - * files to the jsonc language - * - * @param projectOpts The options for the project - */ -async function createWranglerConfigIfNotExistent(projectOpts: ProjectOptions): Promise { - const possibleExts = ["toml", "json", "jsonc"]; - - const wranglerConfigFileExists = possibleExts.some((ext) => - existsSync(join(projectOpts.sourceDir, `wrangler.${ext}`)) - ); - if (wranglerConfigFileExists) { - return; - } - - const answer = await askConfirmation( - "No `wrangler.(toml|json|jsonc)` config file found, do you want to create one?" - ); - - if (!answer) { - console.warn( - "No Wrangler config file created" + - "\n" + - "(to avoid this check use the `--skipWranglerConfigCheck` flag or set a `SKIP_WRANGLER_CONFIG_CHECK` environment variable to `yes`)" - ); - return; - } - - let wranglerConfig = readFileSync(join(getPackageTemplatesDirPath(), "defaults", "wrangler.json"), "utf8"); - - const appName = getAppNameFromPackageJson(projectOpts.sourceDir) ?? "app-name"; - if (appName) { - wranglerConfig = wranglerConfig.replace('"app-name"', JSON.stringify(appName.replaceAll("_", "-"))); - } - - const compatDate = await getLatestCompatDate(); - if (compatDate) { - wranglerConfig = wranglerConfig.replace( - /"compatibility_date": "\d{4}-\d{2}-\d{2}"/, - `"compatibility_date": ${JSON.stringify(compatDate)}` - ); - } - - writeFileSync(join(projectOpts.sourceDir, "wrangler.json"), wranglerConfig); -} - -function getAppNameFromPackageJson(sourceDir: string): string | undefined { - try { - const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8"); - const packageJson: Record = JSON.parse(packageJsonStr); - if (typeof packageJson.name === "string") return packageJson.name; - } catch { - /* empty */ - } -} - -export async function getLatestCompatDate(): Promise { - try { - const resp = await fetch(`https://registry.npmjs.org/workerd`); - const latestWorkerdVersion = ( - (await resp.json()) as { - "dist-tags": { latest: string }; - } - )["dist-tags"].latest; - - // The format of the workerd version is `major.yyyymmdd.patch`. - const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/); - - if (match) { - const [, year, month, date] = match; - const compatDate = `${year}-${month}-${date}`; - - return compatDate; - } - } catch { - /* empty */ - } -} - -function ensureNextjsVersionSupported(options: buildHelper.BuildOptions) { - if (buildHelper.compareSemver(options.nextVersion, "14.0.0") < 0) { - logger.error("Next.js version unsupported, please upgrade to version 14 or greater."); - process.exit(1); - } -} diff --git a/packages/cloudflare/src/cli/build/utils/apply-patches.ts b/packages/cloudflare/src/cli/build/utils/apply-patches.ts new file mode 100644 index 00000000..fe080d41 --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/apply-patches.ts @@ -0,0 +1,30 @@ +/** + * Applies multiple code patches in order to a given piece of code, at each step it validates that the code + * has actually been patched/changed, if not an error is thrown + * + * @param code the code to apply the patches to + * @param patches array of tuples, containing a string indicating the target of the patching (for logging) and + * a patching function that takes a string (pre-patch code) and returns a string (post-patch code) + * @returns the patched code + */ +export async function patchCodeWithValidations( + code: string, + patches: [string, (code: string) => string | Promise, opts?: { isOptional?: boolean }][] +): Promise { + console.log(`Applying code patches:`); + let patchedCode = code; + + for (const [target, patchFunction, opts] of patches) { + console.log(` - patching ${target}`); + + const prePatchCode = patchedCode; + patchedCode = await patchFunction(patchedCode); + + if (!opts?.isOptional && prePatchCode === patchedCode) { + throw new Error(`Failed to patch ${target}`); + } + } + + console.log(`All ${patches.length} patches applied\n`); + return patchedCode; +} diff --git a/packages/cloudflare/src/cli/build/utils/create-config-files.ts b/packages/cloudflare/src/cli/build/utils/create-config-files.ts new file mode 100644 index 00000000..53b2ef69 --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/create-config-files.ts @@ -0,0 +1,116 @@ +import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +import { getPackageTemplatesDirPath } from "../../../utils/get-package-templates-dir-path.js"; +import type { ProjectOptions } from "../../config.js"; +import { askConfirmation } from "../../utils/ask-confirmation.js"; + +/** + * Creates a `wrangler.json` file for the user if a wrangler config file doesn't already exist, + * but only after asking for the user's confirmation. + * + * If the user refuses a warning is shown (which offers ways to opt out of this check to the user). + * + * Note: we generate a wrangler.json file with comments instead of using the jsonc extension, + * we decided to do that since json is more common than jsonc, wrangler also parses + * them in the same way and we also expect developers to associate `wrangler.json` + * files to the jsonc language + * + * @param projectOpts The options for the project + */ +export async function createWranglerConfigIfNotExistent(projectOpts: ProjectOptions): Promise { + const possibleExts = ["toml", "json", "jsonc"]; + + const wranglerConfigFileExists = possibleExts.some((ext) => + existsSync(join(projectOpts.sourceDir, `wrangler.${ext}`)) + ); + if (wranglerConfigFileExists) { + return; + } + + const answer = await askConfirmation( + "No `wrangler.(toml|json|jsonc)` config file found, do you want to create one?" + ); + + if (!answer) { + console.warn( + "No Wrangler config file created" + + "\n" + + "(to avoid this check use the `--skipWranglerConfigCheck` flag or set a `SKIP_WRANGLER_CONFIG_CHECK` environment variable to `yes`)" + ); + return; + } + + let wranglerConfig = readFileSync(join(getPackageTemplatesDirPath(), "defaults", "wrangler.json"), "utf8"); + + const appName = getAppNameFromPackageJson(projectOpts.sourceDir) ?? "app-name"; + if (appName) { + wranglerConfig = wranglerConfig.replace('"app-name"', JSON.stringify(appName.replaceAll("_", "-"))); + } + + const compatDate = await getLatestCompatDate(); + if (compatDate) { + wranglerConfig = wranglerConfig.replace( + /"compatibility_date": "\d{4}-\d{2}-\d{2}"/, + `"compatibility_date": ${JSON.stringify(compatDate)}` + ); + } + + writeFileSync(join(projectOpts.sourceDir, "wrangler.json"), wranglerConfig); +} + +function getAppNameFromPackageJson(sourceDir: string): string | undefined { + try { + const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8"); + const packageJson: Record = JSON.parse(packageJsonStr); + if (typeof packageJson.name === "string") return packageJson.name; + } catch { + /* empty */ + } +} + +export async function getLatestCompatDate(): Promise { + try { + const resp = await fetch(`https://registry.npmjs.org/workerd`); + const latestWorkerdVersion = ( + (await resp.json()) as { + "dist-tags": { latest: string }; + } + )["dist-tags"].latest; + + // The format of the workerd version is `major.yyyymmdd.patch`. + const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/); + + if (match) { + const [, year, month, date] = match; + const compatDate = `${year}-${month}-${date}`; + + return compatDate; + } + } catch { + /* empty */ + } +} + +/** + * Creates a `open-next.config.ts` file for the user if it doesn't exist, but only after asking for the user's confirmation. + * + * If the user refuses an error is thrown (since the file is mandatory). + * + * @param projectOpts The options for the project + */ +export async function createOpenNextConfigIfNotExistent(projectOpts: ProjectOptions): Promise { + const openNextConfigPath = join(projectOpts.sourceDir, "open-next.config.ts"); + + if (!existsSync(openNextConfigPath)) { + const answer = await askConfirmation( + "Missing required `open-next.config.ts` file, do you want to create one?" + ); + + if (!answer) { + throw new Error("The `open-next.config.ts` file is required, aborting!"); + } + + cpSync(join(getPackageTemplatesDirPath(), "defaults", "open-next.config.ts"), openNextConfigPath); + } +} diff --git a/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts new file mode 100644 index 00000000..9a3138a3 --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/ensure-cf-config.ts @@ -0,0 +1,53 @@ +import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; + +/** + * Ensures open next is configured for cloudflare. + * + * @param config OpenNext configuration. + */ +export function ensureCloudflareConfig(config: OpenNextConfig) { + const requirements = { + dftUseCloudflareWrapper: config.default?.override?.wrapper === "cloudflare-node", + dftUseEdgeConverter: config.default?.override?.converter === "edge", + dftMaybeUseCache: + config.default?.override?.incrementalCache === "dummy" || + typeof config.default?.override?.incrementalCache === "function", + dftUseDummyTagCacheAndQueue: + config.default?.override?.tagCache === "dummy" && config.default?.override?.queue === "dummy", + disableCacheInterception: config.dangerous?.enableCacheInterception !== true, + mwIsMiddlewareExternal: config.middleware?.external == true, + mwUseCloudflareWrapper: config.middleware?.override?.wrapper === "cloudflare-edge", + mwUseEdgeConverter: config.middleware?.override?.converter === "edge", + mwUseFetchProxy: config.middleware?.override?.proxyExternalRequest === "fetch", + }; + + if (Object.values(requirements).some((satisfied) => !satisfied)) { + throw new Error( + "The `open-next.config.ts` should have a default export like this:\n\n" + + `{ + default: { + override: { + wrapper: "cloudflare-node", + converter: "edge", + incrementalCache: "dummy" | function, + tagCache: "dummy", + queue: "dummy", + }, + }, + + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + }, + }, + + "dangerous": { + "enableCacheInterception": false + }, + }\n\n`.replace(/^ {8}/gm, "") + ); + } +} diff --git a/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts b/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts index 24b4dadb..87f2d18a 100644 --- a/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts +++ b/packages/cloudflare/src/cli/build/utils/extract-project-env-vars.spec.ts @@ -4,7 +4,7 @@ import { BuildOptions } from "@opennextjs/aws/build/helper.js"; import mockFs from "mock-fs"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { extractProjectEnvVars } from "./extract-project-env-vars"; +import { extractProjectEnvVars } from "./extract-project-env-vars.js"; const options = { monorepoRoot: "", appPath: "" } as BuildOptions; diff --git a/packages/cloudflare/src/cli/build/utils/index.ts b/packages/cloudflare/src/cli/build/utils/index.ts index fec50715..6b4f970c 100644 --- a/packages/cloudflare/src/cli/build/utils/index.ts +++ b/packages/cloudflare/src/cli/build/utils/index.ts @@ -1,3 +1,6 @@ +export * from "./apply-patches.js"; +export * from "./create-config-files.js"; +export * from "./ensure-cf-config.js"; export * from "./extract-project-env-vars.js"; export * from "./normalize-path.js"; export * from "./ts-parse-file.js"; diff --git a/packages/cloudflare/src/cli/index.ts b/packages/cloudflare/src/cli/index.ts index 74d96f34..2a6c654e 100644 --- a/packages/cloudflare/src/cli/index.ts +++ b/packages/cloudflare/src/cli/index.ts @@ -2,7 +2,7 @@ import { resolve } from "node:path"; import { getArgs } from "./args.js"; -import { build } from "./build/index.js"; +import { build } from "./build/build.js"; const nextAppDir = process.cwd();