@opennextjs/cloudflare 1.16.5 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/build/bundle-server.js +3 -1
- package/dist/cli/build/open-next/compile-env-files.js +1 -1
- package/dist/cli/build/open-next/createServerBundle.js +1 -1
- package/dist/cli/build/patches/plugins/dynamic-requires.js +1 -1
- package/dist/cli/build/patches/plugins/instrumentation.js +1 -1
- package/dist/cli/build/patches/plugins/load-manifest.js +1 -1
- package/dist/cli/build/patches/plugins/next-server.js +1 -1
- package/dist/cli/build/patches/plugins/route-module.js +1 -1
- package/dist/cli/build/patches/plugins/wrangler-external.js +1 -1
- package/dist/cli/build/utils/index.d.ts +0 -6
- package/dist/cli/build/utils/index.js +1 -6
- package/dist/cli/commands/build.js +2 -2
- package/dist/cli/commands/deploy.d.ts +1 -1
- package/dist/cli/commands/deploy.js +9 -4
- package/dist/cli/commands/migrate.js +16 -8
- package/dist/cli/commands/populate-cache.d.ts +2 -2
- package/dist/cli/commands/populate-cache.js +19 -7
- package/dist/cli/commands/preview.d.ts +1 -1
- package/dist/cli/commands/preview.js +9 -4
- package/dist/cli/commands/skew-protection.d.ts +1 -1
- package/dist/cli/commands/upload.d.ts +1 -1
- package/dist/cli/commands/upload.js +9 -4
- package/dist/cli/commands/{helpers.js → utils/helpers.js} +1 -1
- package/dist/cli/commands/utils/run-wrangler.d.ts +44 -0
- package/dist/cli/{utils → commands/utils}/run-wrangler.js +46 -11
- package/dist/cli/commands/{utils.d.ts → utils/utils.d.ts} +1 -11
- package/dist/cli/commands/{utils.js → utils/utils.js} +2 -24
- package/dist/cli/project-options.d.ts +1 -1
- package/dist/cli/utils/ask-account-selection.d.ts +12 -0
- package/dist/cli/utils/ask-account-selection.js +23 -0
- package/dist/cli/{build/utils → utils}/create-config-files.d.ts +1 -1
- package/dist/cli/{build/utils → utils}/create-config-files.js +4 -4
- package/dist/cli/utils/create-open-next-config.d.ts +17 -0
- package/dist/cli/utils/create-open-next-config.js +50 -0
- package/dist/cli/utils/create-wrangler-config.d.ts +24 -0
- package/dist/cli/utils/create-wrangler-config.js +244 -0
- package/dist/cli/utils/nextjs-support.d.ts +10 -0
- package/dist/cli/utils/nextjs-support.js +24 -0
- package/package.json +3 -2
- package/templates/open-next.config.ts +1 -1
- package/templates/wrangler.jsonc +2 -2
- package/dist/cli/utils/open-next-config.d.ts +0 -14
- package/dist/cli/utils/open-next-config.js +0 -27
- package/dist/cli/utils/run-wrangler.d.ts +0 -12
- package/dist/cli/utils/wrangler-config.d.ts +0 -15
- package/dist/cli/utils/wrangler-config.js +0 -63
- /package/dist/cli/{build → commands}/utils/files.d.ts +0 -0
- /package/dist/cli/{build → commands}/utils/files.js +0 -0
- /package/dist/cli/commands/{helpers.d.ts → utils/helpers.d.ts} +0 -0
- /package/dist/cli/{build/utils → utils}/extract-project-env-vars.d.ts +0 -0
- /package/dist/cli/{build/utils → utils}/extract-project-env-vars.js +0 -0
- /package/dist/cli/{build/utils → utils}/normalize-path.d.ts +0 -0
- /package/dist/cli/{build/utils → utils}/normalize-path.js +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface Account {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Prompts the user to select a Cloudflare account from a list via an interactive CLI prompt.
|
|
7
|
+
*
|
|
8
|
+
* @param accounts - The list of Cloudflare accounts to choose from.
|
|
9
|
+
* @returns The ID of the selected account, or `undefined` if no selection was made.
|
|
10
|
+
*/
|
|
11
|
+
export declare function askAccountSelection(accounts: Account[]): Promise<string | undefined>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import Enquirer from "enquirer";
|
|
3
|
+
/**
|
|
4
|
+
* Prompts the user to select a Cloudflare account from a list via an interactive CLI prompt.
|
|
5
|
+
*
|
|
6
|
+
* @param accounts - The list of Cloudflare accounts to choose from.
|
|
7
|
+
* @returns The ID of the selected account, or `undefined` if no selection was made.
|
|
8
|
+
*/
|
|
9
|
+
export async function askAccountSelection(accounts) {
|
|
10
|
+
const questionName = randomUUID();
|
|
11
|
+
const enquirerAnswersObject = await Enquirer.prompt({
|
|
12
|
+
name: questionName,
|
|
13
|
+
message: "Select which Cloudflare account to use",
|
|
14
|
+
type: "select",
|
|
15
|
+
choices: accounts.map((account) => ({
|
|
16
|
+
name: account.id,
|
|
17
|
+
message: account.name,
|
|
18
|
+
})),
|
|
19
|
+
format: (accountId) => `${accounts.find(({ id }) => id === accountId)?.name ?? ""}`,
|
|
20
|
+
});
|
|
21
|
+
console.log("");
|
|
22
|
+
return enquirerAnswersObject[questionName];
|
|
23
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProjectOptions } from "
|
|
1
|
+
import type { ProjectOptions } from "../project-options.js";
|
|
2
2
|
/**
|
|
3
3
|
* Creates a `wrangler.jsonc` file for the user if a wrangler config file doesn't already exist,
|
|
4
4
|
* but only after asking for the user's confirmation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { askConfirmation } from "
|
|
2
|
-
import { createOpenNextConfigFile, findOpenNextConfig } from "
|
|
3
|
-
import { createWranglerConfigFile, findWranglerConfig } from "
|
|
1
|
+
import { askConfirmation } from "./ask-confirmation.js";
|
|
2
|
+
import { createOpenNextConfigFile, findOpenNextConfig } from "./create-open-next-config.js";
|
|
3
|
+
import { createWranglerConfigFile, findWranglerConfig } from "./create-wrangler-config.js";
|
|
4
4
|
/**
|
|
5
5
|
* Creates a `wrangler.jsonc` file for the user if a wrangler config file doesn't already exist,
|
|
6
6
|
* but only after asking for the user's confirmation.
|
|
@@ -38,7 +38,7 @@ export async function createOpenNextConfigIfNotExistent(sourceDir) {
|
|
|
38
38
|
if (!answer) {
|
|
39
39
|
throw new Error("The `open-next.config.ts` file is required, aborting!");
|
|
40
40
|
}
|
|
41
|
-
return createOpenNextConfigFile(sourceDir);
|
|
41
|
+
return createOpenNextConfigFile(sourceDir, { cache: false });
|
|
42
42
|
}
|
|
43
43
|
return openNextConfigPath;
|
|
44
44
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the path to the OpenNext configuration file if it exists.
|
|
3
|
+
*
|
|
4
|
+
* @param appDir The directory to check for the open-next.config.ts file
|
|
5
|
+
* @returns The full path to open-next.config.ts if it exists, undefined otherwise
|
|
6
|
+
*/
|
|
7
|
+
export declare function findOpenNextConfig(appDir: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Creates an `open-next.config.ts` file for the application.
|
|
10
|
+
*
|
|
11
|
+
* @param appDir The Next.js application root directory
|
|
12
|
+
* @param options.cache Whether to set up caching
|
|
13
|
+
* @returns The path to the created configuration file
|
|
14
|
+
*/
|
|
15
|
+
export declare function createOpenNextConfigFile(appDir: string, options: {
|
|
16
|
+
cache: boolean;
|
|
17
|
+
}): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js";
|
|
4
|
+
import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js";
|
|
5
|
+
/**
|
|
6
|
+
* Finds the path to the OpenNext configuration file if it exists.
|
|
7
|
+
*
|
|
8
|
+
* @param appDir The directory to check for the open-next.config.ts file
|
|
9
|
+
* @returns The full path to open-next.config.ts if it exists, undefined otherwise
|
|
10
|
+
*/
|
|
11
|
+
export function findOpenNextConfig(appDir) {
|
|
12
|
+
const openNextConfigPath = join(appDir, "open-next.config.ts");
|
|
13
|
+
if (existsSync(openNextConfigPath)) {
|
|
14
|
+
return openNextConfigPath;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates an `open-next.config.ts` file for the application.
|
|
20
|
+
*
|
|
21
|
+
* @param appDir The Next.js application root directory
|
|
22
|
+
* @param options.cache Whether to set up caching
|
|
23
|
+
* @returns The path to the created configuration file
|
|
24
|
+
*/
|
|
25
|
+
export function createOpenNextConfigFile(appDir, options) {
|
|
26
|
+
const openNextConfigPath = join(appDir, "open-next.config.ts");
|
|
27
|
+
let content = readFileSync(join(getPackageTemplatesDirPath(), "open-next.config.ts"), "utf8");
|
|
28
|
+
if (!options.cache) {
|
|
29
|
+
content = patchCode(content, commentOutR2ImportRule);
|
|
30
|
+
content = patchCode(content, commentOutIncrementalCacheRule);
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(openNextConfigPath, content);
|
|
33
|
+
return openNextConfigPath;
|
|
34
|
+
}
|
|
35
|
+
const commentOutR2ImportRule = `
|
|
36
|
+
rule:
|
|
37
|
+
pattern: import $ID from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
|
|
38
|
+
fix: |-
|
|
39
|
+
// import $ID from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
|
|
40
|
+
`;
|
|
41
|
+
const commentOutIncrementalCacheRule = `
|
|
42
|
+
rule:
|
|
43
|
+
pattern: '{ incrementalCache: $ID }'
|
|
44
|
+
fix: |-
|
|
45
|
+
{
|
|
46
|
+
// For best results consider enabling R2 caching
|
|
47
|
+
// See https://opennext.js.org/cloudflare/caching for more details
|
|
48
|
+
// incrementalCache: $ID
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the path to the Wrangler configuration file if it exists.
|
|
3
|
+
*
|
|
4
|
+
* @param appDir The directory to check for the Wrangler config file
|
|
5
|
+
* @returns The path to Wrangler config file if it exists, undefined otherwise
|
|
6
|
+
*/
|
|
7
|
+
export declare function findWranglerConfig(appDir: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a wrangler.jsonc config file in the target directory for the project.
|
|
10
|
+
*
|
|
11
|
+
* If a wrangler.jsonc file already exists it will be overridden.
|
|
12
|
+
*
|
|
13
|
+
* The function attempts to create an R2 bucket for incremental cache. If bucket creation
|
|
14
|
+
* fails (e.g., user not authenticated or R2 not enabled), a configuration without caching
|
|
15
|
+
* will be created instead.
|
|
16
|
+
*
|
|
17
|
+
* @param projectDir The target directory for the project
|
|
18
|
+
* @param defaultCompatDate The default YYYY-MM-DD compatibility date to use in the config (used if fetching the latest workerd version date fails)
|
|
19
|
+
* @returns An object containing a `cachingEnabled` which indicates whether caching has been set up during the wrangler
|
|
20
|
+
* config file creation or not
|
|
21
|
+
*/
|
|
22
|
+
export declare function createWranglerConfigFile(projectDir: string, defaultCompatDate?: string): Promise<{
|
|
23
|
+
cachingEnabled: boolean;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { findPackagerAndRoot } from "@opennextjs/aws/build/helper.js";
|
|
5
|
+
import Cloudflare from "cloudflare";
|
|
6
|
+
import { parse, stringify } from "comment-json";
|
|
7
|
+
import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js";
|
|
8
|
+
import { runWrangler } from "../commands/utils/run-wrangler.js";
|
|
9
|
+
import { askAccountSelection } from "./ask-account-selection.js";
|
|
10
|
+
/**
|
|
11
|
+
* Gets the path to the Wrangler configuration file if it exists.
|
|
12
|
+
*
|
|
13
|
+
* @param appDir The directory to check for the Wrangler config file
|
|
14
|
+
* @returns The path to Wrangler config file if it exists, undefined otherwise
|
|
15
|
+
*/
|
|
16
|
+
export function findWranglerConfig(appDir) {
|
|
17
|
+
const possibleExts = ["toml", "json", "jsonc"];
|
|
18
|
+
for (const ext of possibleExts) {
|
|
19
|
+
const path = join(appDir, `wrangler.${ext}`);
|
|
20
|
+
if (existsSync(path)) {
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a wrangler.jsonc config file in the target directory for the project.
|
|
28
|
+
*
|
|
29
|
+
* If a wrangler.jsonc file already exists it will be overridden.
|
|
30
|
+
*
|
|
31
|
+
* The function attempts to create an R2 bucket for incremental cache. If bucket creation
|
|
32
|
+
* fails (e.g., user not authenticated or R2 not enabled), a configuration without caching
|
|
33
|
+
* will be created instead.
|
|
34
|
+
*
|
|
35
|
+
* @param projectDir The target directory for the project
|
|
36
|
+
* @param defaultCompatDate The default YYYY-MM-DD compatibility date to use in the config (used if fetching the latest workerd version date fails)
|
|
37
|
+
* @returns An object containing a `cachingEnabled` which indicates whether caching has been set up during the wrangler
|
|
38
|
+
* config file creation or not
|
|
39
|
+
*/
|
|
40
|
+
export async function createWranglerConfigFile(projectDir, defaultCompatDate = "2026-02-01") {
|
|
41
|
+
const workerName = getWorkerName(projectDir);
|
|
42
|
+
const compatibilityDate = (await getLatestCompatDate()) ?? defaultCompatDate;
|
|
43
|
+
const wranglerConfigStr = readFileSync(join(getPackageTemplatesDirPath(), "wrangler.jsonc"), "utf8")
|
|
44
|
+
.replaceAll("<WORKER_NAME>", workerName)
|
|
45
|
+
.replaceAll("<COMPATIBILITY_DATE>", compatibilityDate);
|
|
46
|
+
const wranglerConfig = parse(wranglerConfigStr);
|
|
47
|
+
assert(Array.isArray(wranglerConfig.r2_buckets));
|
|
48
|
+
assert(wranglerConfig.r2_buckets[0] != null && typeof wranglerConfig.r2_buckets[0] === "object");
|
|
49
|
+
assert("bucket_name" in wranglerConfig.r2_buckets[0] &&
|
|
50
|
+
typeof wranglerConfig.r2_buckets[0].bucket_name === "string");
|
|
51
|
+
const bucketName = wranglerConfig.r2_buckets[0].bucket_name;
|
|
52
|
+
const { success: cachingEnabled } = await maybeCreateR2Bucket(projectDir, bucketName);
|
|
53
|
+
if (!cachingEnabled) {
|
|
54
|
+
delete wranglerConfig.r2_buckets;
|
|
55
|
+
}
|
|
56
|
+
writeFileSync(join(projectDir, "wrangler.jsonc"), stringify(wranglerConfig, null, "\t"));
|
|
57
|
+
return { cachingEnabled };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets a valid worker name from the project's package.json name, falling back to `app-name`
|
|
61
|
+
* in case the name could not be detected.
|
|
62
|
+
*
|
|
63
|
+
* @param projectDir The project directory containing the package.json file
|
|
64
|
+
* @returns A valid worker name suitable for a Cloudflare Worker
|
|
65
|
+
*/
|
|
66
|
+
function getWorkerName(projectDir) {
|
|
67
|
+
const appName = getNameFromPackageJson(projectDir) ?? "app-name";
|
|
68
|
+
return (appName
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
// Remove org prefix if present (e.g., "@org/my-app" -> "my-app")
|
|
71
|
+
.replace(/^@[^/]+\//, "")
|
|
72
|
+
.replaceAll("_", "-")
|
|
73
|
+
.replace(/[^a-z0-9-]/g, ""));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reads the `name` field from the `package.json` in the given directory.
|
|
77
|
+
*
|
|
78
|
+
* @param sourceDir - The directory containing the `package.json` file.
|
|
79
|
+
* @returns The package name if found, `undefined` otherwise.
|
|
80
|
+
*/
|
|
81
|
+
function getNameFromPackageJson(sourceDir) {
|
|
82
|
+
try {
|
|
83
|
+
const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8");
|
|
84
|
+
const packageJson = JSON.parse(packageJsonStr);
|
|
85
|
+
if (typeof packageJson.name === "string")
|
|
86
|
+
return packageJson.name;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* empty */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fetches the latest compatibility date from the npm registry based on the latest `workerd` version.
|
|
94
|
+
*
|
|
95
|
+
* The workerd version format is `major.yyyymmdd.patch`. The date portion is extracted and formatted
|
|
96
|
+
* as `YYYY-MM-DD`. If the extracted date is in the future, today's date is returned instead.
|
|
97
|
+
*
|
|
98
|
+
* @returns The compatibility date as a `YYYY-MM-DD` string, or `undefined` if the fetch or parsing fails.
|
|
99
|
+
*/
|
|
100
|
+
async function getLatestCompatDate() {
|
|
101
|
+
try {
|
|
102
|
+
const resp = await fetch(`https://registry.npmjs.org/workerd`);
|
|
103
|
+
const latestWorkerdVersion = (await resp.json())["dist-tags"].latest;
|
|
104
|
+
// The format of the workerd version is `major.yyyymmdd.patch`.
|
|
105
|
+
const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/);
|
|
106
|
+
if (match) {
|
|
107
|
+
const [, year, month, day] = match;
|
|
108
|
+
const compatDate = `${year}-${month}-${day}`;
|
|
109
|
+
const currentDate = new Date().toISOString().slice(0, 10);
|
|
110
|
+
return compatDate < currentDate ? compatDate : currentDate;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* empty */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Gets the authentication credentials for Cloudflare API calls.
|
|
119
|
+
*
|
|
120
|
+
* Uses `wrangler auth token --json` which checks the following sources in order:
|
|
121
|
+
* 1. CLOUDFLARE_API_TOKEN environment variable
|
|
122
|
+
* 2. CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL environment variables
|
|
123
|
+
* 3. OAuth token from `wrangler login`
|
|
124
|
+
*
|
|
125
|
+
* @param options The build options containing packager and monorepo root
|
|
126
|
+
* @returns The auth credentials if available, undefined otherwise
|
|
127
|
+
*/
|
|
128
|
+
function getAuthCredentials(options) {
|
|
129
|
+
const result = runWrangler(options, ["auth", "token", "--json"], { logging: "none" });
|
|
130
|
+
if (!result.success) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const json = JSON.parse(result.stdout);
|
|
135
|
+
if (json.type === "api_key") {
|
|
136
|
+
return { type: "api_key", apiKey: json.key, apiEmail: json.email };
|
|
137
|
+
}
|
|
138
|
+
// Both "oauth" and "api_token" types have a token field
|
|
139
|
+
if (json.token) {
|
|
140
|
+
return { type: "token", token: json.token };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
/* empty */
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Gets the account ID for Cloudflare API calls.
|
|
150
|
+
*
|
|
151
|
+
* Tries the following sources in order:
|
|
152
|
+
* 1. CLOUDFLARE_ACCOUNT_ID or CF_ACCOUNT_ID environment variable
|
|
153
|
+
* 2. List accounts using the SDK and return the first one
|
|
154
|
+
*
|
|
155
|
+
* @param client The Cloudflare SDK client
|
|
156
|
+
* @returns The account ID if available, undefined otherwise
|
|
157
|
+
*/
|
|
158
|
+
async function getAccountId(client) {
|
|
159
|
+
if (process.env.CLOUDFLARE_ACCOUNT_ID || process.env.CF_ACCOUNT_ID) {
|
|
160
|
+
return process.env.CLOUDFLARE_ACCOUNT_ID || process.env.CF_ACCOUNT_ID;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const accountsList = await client.accounts.list();
|
|
164
|
+
const accounts = [];
|
|
165
|
+
for await (const account of accountsList) {
|
|
166
|
+
accounts.push({ id: account.id, name: account.name });
|
|
167
|
+
}
|
|
168
|
+
if (accounts.length === 0) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
if (accounts.length === 1 && accounts[0]) {
|
|
172
|
+
return accounts[0].id;
|
|
173
|
+
}
|
|
174
|
+
return await askAccountSelection(accounts);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
/* empty */
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Attempts to log in to Cloudflare via wrangler.
|
|
183
|
+
*
|
|
184
|
+
* @param options The build options containing packager and monorepo root
|
|
185
|
+
* @returns true if login was successful, false otherwise
|
|
186
|
+
*/
|
|
187
|
+
function wranglerLogin(options) {
|
|
188
|
+
const result = runWrangler(options, ["login"], { logging: "all" });
|
|
189
|
+
return result.success;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Creates an R2 bucket.
|
|
193
|
+
*
|
|
194
|
+
* If no auth credentials are available, falls back to wrangler login for OAuth authentication.
|
|
195
|
+
*
|
|
196
|
+
* @param projectDir The project directory to detect the package manager
|
|
197
|
+
* @param bucketName The name of the R2 bucket to create
|
|
198
|
+
* @returns An object indicating success with the bucket name, or failure
|
|
199
|
+
*/
|
|
200
|
+
async function maybeCreateR2Bucket(projectDir, bucketName) {
|
|
201
|
+
try {
|
|
202
|
+
const { packager, root: monorepoRoot } = findPackagerAndRoot(projectDir);
|
|
203
|
+
const options = { packager, monorepoRoot };
|
|
204
|
+
let authCredentials = getAuthCredentials(options);
|
|
205
|
+
// If no credentials available, fall back to wrangler login
|
|
206
|
+
if (!authCredentials) {
|
|
207
|
+
const loginSuccess = wranglerLogin(options);
|
|
208
|
+
if (!loginSuccess) {
|
|
209
|
+
return { success: false };
|
|
210
|
+
}
|
|
211
|
+
// Get credentials after login
|
|
212
|
+
authCredentials = getAuthCredentials(options);
|
|
213
|
+
if (!authCredentials) {
|
|
214
|
+
return { success: false };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const client = authCredentials.type === "api_key"
|
|
218
|
+
? new Cloudflare({ apiKey: authCredentials.apiKey, apiEmail: authCredentials.apiEmail })
|
|
219
|
+
: new Cloudflare({ apiToken: authCredentials.token });
|
|
220
|
+
const accountId = await getAccountId(client);
|
|
221
|
+
if (!accountId) {
|
|
222
|
+
return { success: false };
|
|
223
|
+
}
|
|
224
|
+
// Check if bucket already exists
|
|
225
|
+
try {
|
|
226
|
+
await client.r2.buckets.get(bucketName, { account_id: accountId });
|
|
227
|
+
// Bucket exists
|
|
228
|
+
return { success: true, bucketName };
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (!(error instanceof Cloudflare.NotFoundError)) {
|
|
232
|
+
return { success: false };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await client.r2.buckets.create({
|
|
236
|
+
account_id: accountId,
|
|
237
|
+
name: bucketName,
|
|
238
|
+
});
|
|
239
|
+
return { success: true, bucketName };
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return { success: false };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validates that the Next.js version is supported and checks wrangler compatibility.
|
|
4
|
+
*
|
|
5
|
+
* Note: this function assumes that wrangler is installed.
|
|
6
|
+
*
|
|
7
|
+
* @param options.nextVersion The detected Next.js version string
|
|
8
|
+
* @throws {Error} If the Next.js version is unsupported
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureNextjsVersionSupported({ nextVersion, }: Pick<buildHelper.BuildOptions, "nextVersion">): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as buildHelper from "@opennextjs/aws/build/helper.js";
|
|
2
|
+
import logger from "@opennextjs/aws/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validates that the Next.js version is supported and checks wrangler compatibility.
|
|
5
|
+
*
|
|
6
|
+
* Note: this function assumes that wrangler is installed.
|
|
7
|
+
*
|
|
8
|
+
* @param options.nextVersion The detected Next.js version string
|
|
9
|
+
* @throws {Error} If the Next.js version is unsupported
|
|
10
|
+
*/
|
|
11
|
+
export async function ensureNextjsVersionSupported({ nextVersion, }) {
|
|
12
|
+
if (buildHelper.compareSemver(nextVersion, "<", "14.2.0")) {
|
|
13
|
+
throw new Error("Next.js version unsupported, please upgrade to version 14.2 or greater.");
|
|
14
|
+
}
|
|
15
|
+
const { default: { version: wranglerVersion }, } = await import("wrangler/package.json", { with: { type: "json" } });
|
|
16
|
+
// We need a version of workerd that has a fix for setImmediate for Next.js 16.1+
|
|
17
|
+
// See:
|
|
18
|
+
// - https://github.com/cloudflare/workerd/pull/5869
|
|
19
|
+
// - https://github.com/opennextjs/opennextjs-cloudflare/issues/1049
|
|
20
|
+
if (buildHelper.compareSemver(nextVersion, ">=", "16.1.0") &&
|
|
21
|
+
buildHelper.compareSemver(wranglerVersion, "<", "4.59.2")) {
|
|
22
|
+
logger.warn(`Next.js 16.1+ requires wrangler 4.59.2 or greater (${wranglerVersion} detected).`);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennextjs/cloudflare",
|
|
3
3
|
"description": "Cloudflare builder for next apps",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.17.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opennextjs-cloudflare": "dist/cli/index.js"
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"cloudflare": "^4.4.1",
|
|
49
49
|
"enquirer": "^2.4.1",
|
|
50
50
|
"glob": "^12.0.0",
|
|
51
|
+
"comment-json": "^4.5.1",
|
|
51
52
|
"ts-tqdm": "^0.8.6",
|
|
52
53
|
"yargs": "^18.0.0"
|
|
53
54
|
},
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
"vitest": "^2.1.1"
|
|
76
77
|
},
|
|
77
78
|
"peerDependencies": {
|
|
78
|
-
"wrangler": "^4.
|
|
79
|
+
"wrangler": "^4.65.0",
|
|
79
80
|
"next": "~15.0.8 || ~15.1.12 || ~15.2.9 || ~15.3.9 || ~15.4.11 || ~15.5.10 || ~16.0.11 || ^16.1.5"
|
|
80
81
|
},
|
|
81
82
|
"scripts": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// default open-next.config.ts file created by @opennextjs/cloudflare
|
|
2
|
-
import { defineCloudflareConfig } from "@opennextjs/cloudflare
|
|
2
|
+
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
3
3
|
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
|
|
4
4
|
|
|
5
5
|
export default defineCloudflareConfig({
|
package/templates/wrangler.jsonc
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
3
|
"main": ".open-next/worker.js",
|
|
4
4
|
"name": "<WORKER_NAME>",
|
|
5
|
-
"compatibility_date": "
|
|
5
|
+
"compatibility_date": "<COMPATIBILITY_DATE>",
|
|
6
6
|
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
|
|
7
7
|
"assets": {
|
|
8
8
|
"directory": ".open-next/assets",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// Create the bucket before deploying
|
|
25
25
|
// You can change the bucket name if you want
|
|
26
26
|
// See https://developers.cloudflare.com/workers/wrangler/commands/#r2-bucket-create
|
|
27
|
-
"bucket_name": "cache"
|
|
27
|
+
"bucket_name": "<WORKER_NAME>-opennext-cache"
|
|
28
28
|
}
|
|
29
29
|
],
|
|
30
30
|
"images": {
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Finds the path to the OpenNext configuration file if it exists.
|
|
3
|
-
*
|
|
4
|
-
* @param appDir The directory to check for the open-next.config.ts file
|
|
5
|
-
* @returns The full path to open-next.config.ts if it exists, undefined otherwise
|
|
6
|
-
*/
|
|
7
|
-
export declare function findOpenNextConfig(appDir: string): string | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* Creates a `open-next.config.ts` file in the target directory for the project.
|
|
10
|
-
*
|
|
11
|
-
* @param appDir The Next application root
|
|
12
|
-
* @return The path to the created source file
|
|
13
|
-
*/
|
|
14
|
-
export declare function createOpenNextConfigFile(appDir: string): Promise<string>;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { cpSync, existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js";
|
|
4
|
-
/**
|
|
5
|
-
* Finds the path to the OpenNext configuration file if it exists.
|
|
6
|
-
*
|
|
7
|
-
* @param appDir The directory to check for the open-next.config.ts file
|
|
8
|
-
* @returns The full path to open-next.config.ts if it exists, undefined otherwise
|
|
9
|
-
*/
|
|
10
|
-
export function findOpenNextConfig(appDir) {
|
|
11
|
-
const openNextConfigPath = join(appDir, "open-next.config.ts");
|
|
12
|
-
if (existsSync(openNextConfigPath)) {
|
|
13
|
-
return openNextConfigPath;
|
|
14
|
-
}
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Creates a `open-next.config.ts` file in the target directory for the project.
|
|
19
|
-
*
|
|
20
|
-
* @param appDir The Next application root
|
|
21
|
-
* @return The path to the created source file
|
|
22
|
-
*/
|
|
23
|
-
export async function createOpenNextConfigFile(appDir) {
|
|
24
|
-
const openNextConfigPath = join(appDir, "open-next.config.ts");
|
|
25
|
-
cpSync(join(getPackageTemplatesDirPath(), "open-next.config.ts"), openNextConfigPath);
|
|
26
|
-
return openNextConfigPath;
|
|
27
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
|
|
2
|
-
export type WranglerTarget = "local" | "remote";
|
|
3
|
-
type WranglerOptions = {
|
|
4
|
-
target?: WranglerTarget;
|
|
5
|
-
environment?: string;
|
|
6
|
-
configPath?: string;
|
|
7
|
-
logging?: "all" | "error";
|
|
8
|
-
env?: Record<string, string>;
|
|
9
|
-
};
|
|
10
|
-
export declare function runWrangler(options: BuildOptions, args: string[], wranglerOpts?: WranglerOptions): void;
|
|
11
|
-
export declare function isWranglerTarget(v: string | undefined): v is WranglerTarget;
|
|
12
|
-
export {};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gets the path to the Wrangler configuration file if it exists.
|
|
3
|
-
*
|
|
4
|
-
* @param appDir The directory to check for the Wrangler config file
|
|
5
|
-
* @returns The path to Wrangler config file if it exists, undefined otherwise
|
|
6
|
-
*/
|
|
7
|
-
export declare function findWranglerConfig(appDir: string): string | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* Creates a wrangler.jsonc config file in the target directory for the project.
|
|
10
|
-
*
|
|
11
|
-
* If a wrangler.jsonc file already exists it will be overridden.
|
|
12
|
-
*
|
|
13
|
-
* @param projectDir The target directory for the project
|
|
14
|
-
*/
|
|
15
|
-
export declare function createWranglerConfigFile(projectDir: string): Promise<void>;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getPackageTemplatesDirPath } from "../../utils/get-package-templates-dir-path.js";
|
|
4
|
-
/**
|
|
5
|
-
* Gets the path to the Wrangler configuration file if it exists.
|
|
6
|
-
*
|
|
7
|
-
* @param appDir The directory to check for the Wrangler config file
|
|
8
|
-
* @returns The path to Wrangler config file if it exists, undefined otherwise
|
|
9
|
-
*/
|
|
10
|
-
export function findWranglerConfig(appDir) {
|
|
11
|
-
const possibleExts = ["toml", "json", "jsonc"];
|
|
12
|
-
for (const ext of possibleExts) {
|
|
13
|
-
const path = join(appDir, `wrangler.${ext}`);
|
|
14
|
-
if (existsSync(path)) {
|
|
15
|
-
return path;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Creates a wrangler.jsonc config file in the target directory for the project.
|
|
22
|
-
*
|
|
23
|
-
* If a wrangler.jsonc file already exists it will be overridden.
|
|
24
|
-
*
|
|
25
|
-
* @param projectDir The target directory for the project
|
|
26
|
-
*/
|
|
27
|
-
export async function createWranglerConfigFile(projectDir) {
|
|
28
|
-
let wranglerConfig = readFileSync(join(getPackageTemplatesDirPath(), "wrangler.jsonc"), "utf8");
|
|
29
|
-
const appName = getAppNameFromPackageJson(projectDir) ?? "app-name";
|
|
30
|
-
wranglerConfig = wranglerConfig.replaceAll('"<WORKER_NAME>"', JSON.stringify(appName.replaceAll("_", "-")));
|
|
31
|
-
const compatDate = await getLatestCompatDate();
|
|
32
|
-
if (compatDate) {
|
|
33
|
-
wranglerConfig = wranglerConfig.replace(/"compatibility_date": "\d{4}-\d{2}-\d{2}"/, `"compatibility_date": ${JSON.stringify(compatDate)}`);
|
|
34
|
-
}
|
|
35
|
-
writeFileSync(join(projectDir, "wrangler.jsonc"), wranglerConfig);
|
|
36
|
-
}
|
|
37
|
-
function getAppNameFromPackageJson(sourceDir) {
|
|
38
|
-
try {
|
|
39
|
-
const packageJsonStr = readFileSync(join(sourceDir, "package.json"), "utf8");
|
|
40
|
-
const packageJson = JSON.parse(packageJsonStr);
|
|
41
|
-
if (typeof packageJson.name === "string")
|
|
42
|
-
return packageJson.name;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
/* empty */
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function getLatestCompatDate() {
|
|
49
|
-
try {
|
|
50
|
-
const resp = await fetch(`https://registry.npmjs.org/workerd`);
|
|
51
|
-
const latestWorkerdVersion = (await resp.json())["dist-tags"].latest;
|
|
52
|
-
// The format of the workerd version is `major.yyyymmdd.patch`.
|
|
53
|
-
const match = latestWorkerdVersion.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/);
|
|
54
|
-
if (match) {
|
|
55
|
-
const [, year, month, date] = match;
|
|
56
|
-
const compatDate = `${year}-${month}-${date}`;
|
|
57
|
-
return compatDate;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
/* empty */
|
|
62
|
-
}
|
|
63
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|