Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a50ff91
Add SKILL.md for porting PRs and AGENTS.md for coding guidelines
conico974 Mar 8, 2026
1d3b7e5
update skill
conico974 Mar 8, 2026
dc05a5a
Port https://github.com/opennextjs/opennextjs-aws/pull/1118 as a test
conico974 Mar 8, 2026
8614793
Port https://github.com/opennextjs/opennextjs-aws/pull/1117
conico974 Mar 21, 2026
a559cfe
update skill
conico974 Mar 21, 2026
132ad05
Port https://github.com/opennextjs/opennextjs-aws/pull/1114
conico974 Mar 21, 2026
fe987a1
Port PR https://github.com/opennextjs/opennextjs-aws/pull/1107
conico974 Mar 21, 2026
6f3d001
update skills
conico974 Mar 21, 2026
643b158
Port PR https://github.com/opennextjs/opennextjs-aws/pull/1108
conico974 Mar 21, 2026
e9d9243
Port PR https://github.com/opennextjs/opennextjs-aws/pull/1104
conico974 Mar 21, 2026
3abe5d2
Port PR https://github.com/opennextjs/opennextjs-aws/pull/1101
conico974 Mar 21, 2026
b4463be
Port PR https://github.com/opennextjs/opennextjs-aws/pull/1098
conico974 Mar 21, 2026
a0783a4
chore: port PR #1083 from source repository
conico974 Mar 21, 2026
3d5945c
chore: port PR #1105 from source repository
conico974 Mar 21, 2026
c8337b0
chore: port PR #1097 from source repository
conico974 Mar 21, 2026
e082616
chore: port PR #1122 from source repository
conico974 Mar 21, 2026
78d6ad7
chore: update port PR skill instructions for staging and committing c…
conico974 Mar 21, 2026
322d80c
chore: port PR #1126 from source repository
conico974 Mar 21, 2026
ab06c09
linting
conico974 Mar 21, 2026
79c5af3
chore: port PR #1127 from source repository
conico974 Mar 21, 2026
7204997
chore: port PR #1138 from source repository
conico974 Mar 21, 2026
e14b0b9
chore: port PR #1133 from source repository
conico974 Mar 21, 2026
3bfab5a
chore: port PR #1142 from source repository
conico974 Mar 21, 2026
a35e4f9
chore: port PR #1147 from source repository
conico974 Mar 21, 2026
2a65d4a
chore: port PR #1150 from source repository
conico974 Mar 21, 2026
a1d153e
fix lockfile
conico974 Mar 21, 2026
5170ff5
Merge remote-tracking branch 'origin/main' into conico/port-cloudflare
conico974 Mar 29, 2026
3ba5da2
fix test
conico974 Mar 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/port-pr-1083.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1083 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1083
7 changes: 7 additions & 0 deletions .changeset/port-pr-1097.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1097 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1097
7 changes: 7 additions & 0 deletions .changeset/port-pr-1105.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1105 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1105
9 changes: 9 additions & 0 deletions .changeset/port-pr-1122-aws.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@opennextjs/aws": patch
---

Ported PR #1122 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1122

Changed `checkRunningInsideNextjsApp` function signature to accept `{ appPath: string }` instead of full `BuildOptions` object, making it more flexible for use in the migrate command.
13 changes: 13 additions & 0 deletions .changeset/port-pr-1122-cloudflare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1122 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1122

Applied bugfixes and improvements to the `migrate` command:

- Fixed extra newlines when appending to files (updated `conditionalAppendFileSync` function signature to use options object with `appendIf` and `appendPrefix`)
- Fixed error when `public` directory is missing (now creates parent directories automatically)
- Fixed Next.js config file update to check if the file exists before attempting to update
7 changes: 7 additions & 0 deletions .changeset/port-pr-1126.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1126 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1126
7 changes: 7 additions & 0 deletions .changeset/port-pr-1127.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1127 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1127
7 changes: 7 additions & 0 deletions .changeset/port-pr-1133.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1133 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1133
7 changes: 7 additions & 0 deletions .changeset/port-pr-1138.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1138 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1138
7 changes: 7 additions & 0 deletions .changeset/port-pr-1142.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1142 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1142
7 changes: 7 additions & 0 deletions .changeset/port-pr-1146.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1146 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1146
7 changes: 7 additions & 0 deletions .changeset/port-pr-1147.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1147 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1147
7 changes: 7 additions & 0 deletions .changeset/port-pr-1150.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/cloudflare": patch
---

Ported PR #1150 from source repository

https://github.com/opennextjs/opennextjs-cloudflare/pull/1150
46 changes: 30 additions & 16 deletions .claude/skills/port-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ Apply similar changes following this repository's conventions:
After implementation, run:

```bash
pnpm code:checks
pnpm code:checks 2>&1 | tail -20
```

This runs formatting, linting, and TypeScript checks.
This runs formatting, linting, and TypeScript checks. Only the tail of the output is shown to see the result.

### 7. Run Unit Tests

After code checks pass, run only the unit tests from the tests-unit package:

```bash
pnpm --filter tests-unit test
pnpm --filter tests-unit test 2>&1 | tail -20
Comment on lines +67 to +77
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally i wouldn't include the 2>&1 | tail -20 in the snippet - most will reach for that out-of-the-box depending on what they're doing, so inlining might make them do it in cases where they might have gone for a different approach.

Suggested change
pnpm code:checks 2>&1 | tail -20
```
This runs formatting, linting, and TypeScript checks.
This runs formatting, linting, and TypeScript checks. Only the tail of the output is shown to see the result.
### 7. Run Unit Tests
After code checks pass, run only the unit tests from the tests-unit package:
```bash
pnpm --filter tests-unit test
pnpm --filter tests-unit test 2>&1 | tail -20
pnpm code:checks

This runs formatting, linting, and TypeScript checks. Only the tail of the output is shown to see the result.

7. Run Unit Tests

After code checks pass, run only the unit tests from the tests-unit package:

pnpm --filter tests-unit test

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good call, I used a bunch of different AI to test them on that and some were very dumb 😄

```

**Important:** Do NOT run `pnpm test` (which runs all tests), `pnpm e2e:test`, or any full test suite. Only unit tests from the tests-unit package should be run during the porting process.
Expand Down Expand Up @@ -105,32 +105,46 @@ Ported PR #$PR_NUMBER from source repository
$ARGUMENTS" > .changeset/port-pr-$PR_NUMBER.md
```

### 10. Stage Changes and Prepare Commit
### 10. Stage All Files

Stage the changeset file and prepare a commit message (but do not commit):
Stage all files that were modified or created during the port:

```bash
# Stage the changeset file
git add .changeset/port-pr-$PR_NUMBER.md
git status --short
git add .changeset/port-pr-$PR_NUMBER.md <other-modified-files>
```

# Prepare the commit message with the PR link (stored for later)
echo "chore: port PR #$PR_NUMBER from source repository
### 11. Ask for Commit

$ARGUMENTS
Ask the user if they want to commit the changes directly:

Changeset: .changeset/port-pr-$PR_NUMBER.md" > /tmp/commit-message-port-pr-$PR_NUMBER.txt
Ask the user: **"Should I stage all files and commit the ported PR changes now?"**

# Display the prepared commit message
cat /tmp/commit-message-port-pr-$PR_NUMBER.txt
```
Options:

The changeset is staged and ready to commit. The commit message is saved at `/tmp/commit-message-port-pr-$PR_NUMBER.txt` for reference.
- "Yes, commit now" - Stage all files and commit with the prepared message
- "No, I'll commit manually" - Let the user stage and commit themselves

If the user chooses to commit:

```bash
# Stage all modified and new files
git add <all-modified-files>

# Commit with the prepared message
git commit -m "chore: port PR #$PR_NUMBER from source repository

$ARGUMENTS

Changeset: .changeset/port-pr-$PR_NUMBER.md"
```

### 11. Summary
### 12. Summary

Provide a summary of:

- What was ported
- Any adaptations made
- Files modified/created
- Whether the changes were committed
- Any remaining TODOs or follow-up items
2 changes: 1 addition & 1 deletion create-cloudflare/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"cf-typegen": "wrangler types --env-interface CloudflareEnv ./cloudflare-env.d.ts"
},
"dependencies": {
"@opennextjs/cloudflare": "^1.15.1",
"@opennextjs/cloudflare": "^1.17.1",
"next": "16.1.4",
"react": "19.1.4",
"react-dom": "19.1.4"
Expand Down
2 changes: 1 addition & 1 deletion create-cloudflare/next/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"$schema": "node_modules/wrangler/config-schema.json",
"name": "worker_name",
"main": ".open-next/worker.js",
"compatibility_date": "2025-12-01",
"compatibility_date": "<COMPATIBILITY_DATE>",
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"assets": {
"binding": "ASSETS",
Expand Down
2 changes: 2 additions & 0 deletions packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@dotenvx/dotenvx": "catalog:",
"@opennextjs/aws": "workspace:*",
"cloudflare": "^4.4.1",
"comment-json": "^4.5.1",
"enquirer": "^2.4.1",
"glob": "catalog:",
"ts-tqdm": "^0.8.6",
Expand All @@ -62,6 +63,7 @@
"devDependencies": {
"@cloudflare/workers-types": "catalog:",
"@tsconfig/strictest": "catalog:",
"@types/comment-json": "^2.4.5",
"@types/mock-fs": "catalog:",
"@types/node": "catalog:",
"@types/picomatch": "^4.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,80 @@
import { describe, expect, test } from "vitest";
import { beforeEach, describe, expect, test, vi } from "vitest";

import { isUserWorkerFirst } from "./index.js";

const mockAssetsFetch = vi.fn();

vi.mock("../../cloudflare-context.js", () => ({
getCloudflareContext: () => ({
env: {
ASSETS: { fetch: mockAssetsFetch },
},
}),
}));

describe("maybeGetAssetResult", () => {
let resolver: typeof import("./index.js").default;

beforeEach(async () => {
vi.resetModules();
mockAssetsFetch.mockReset();
globalThis.__ASSETS_RUN_WORKER_FIRST__ = true;
resolver = (await import("./index.js")).default;
});

const makeEvent = (method: string, rawPath: string) =>
({
method,
rawPath,
headers: { accept: "*/*" },
}) as Parameters<typeof resolver.maybeGetAssetResult>[0];

test("GET request returns response body", async () => {
const body = new ReadableStream();
mockAssetsFetch.mockResolvedValue(new Response(body, { status: 200 }));

const result = await resolver.maybeGetAssetResult(makeEvent("GET", "/style.css"));

expect(result).toBeDefined();
expect(result!.statusCode).toBe(200);
expect(result!.body).not.toBeNull();
});

test("HEAD request returns null body", async () => {
mockAssetsFetch.mockResolvedValue(new Response(null, { status: 200 }));

const result = await resolver.maybeGetAssetResult(makeEvent("HEAD", "/style.css"));

expect(result).toBeDefined();
expect(result!.statusCode).toBe(200);
expect(result!.body).toBeNull();
});

test("returns undefined for 404 responses", async () => {
mockAssetsFetch.mockResolvedValue(new Response(null, { status: 404 }));

const result = await resolver.maybeGetAssetResult(makeEvent("GET", "/missing.css"));

expect(result).toBeUndefined();
});

test("returns undefined for POST requests", async () => {
const result = await resolver.maybeGetAssetResult(makeEvent("POST", "/style.css"));

expect(result).toBeUndefined();
expect(mockAssetsFetch).not.toHaveBeenCalled();
});

test("returns undefined when run_worker_first is false", async () => {
globalThis.__ASSETS_RUN_WORKER_FIRST__ = false;

const result = await resolver.maybeGetAssetResult(makeEvent("GET", "/style.css"));

expect(result).toBeUndefined();
expect(mockAssetsFetch).not.toHaveBeenCalled();
});
});

describe("isUserWorkerFirst", () => {
test("run_worker_first = false", () => {
expect(isUserWorkerFirst(false, "/test")).toBe(false);
Expand Down
25 changes: 22 additions & 3 deletions packages/cloudflare/src/api/overrides/asset-resolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,33 @@ const resolver: AssetResolver = {
type: "core",
statusCode: response.status,
headers: Object.fromEntries(response.headers.entries()),
// Workers and Node types differ.
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
body: response.body || (new ReadableStream() as any),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: getResponseBody(method, response) as any,
isBase64Encoded: false,
} satisfies InternalResult;
},
};

/**
* Returns the response body for an asset result.
*
* HEAD responses must return `null` because `response.body` is `null` per the HTTP spec
* and the `new ReadableStream()` fallback would create a stream that never closes, hanging the Worker.
*
* @param method - The HTTP method of the request.
* @param response - The response from the ASSETS binding.
* @returns The body to use in the internal result.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getResponseBody(method: string, response: Response): ReadableStream<any> | null {
if (method === "HEAD") {
return null;
}
// Workers and Node ReadableStream types differ.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return response.body || (new ReadableStream() as any);
}

/**
* @param runWorkerFirst `run_worker_first` config
* @param pathname pathname of the request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
import { getCloudflareContext } from "../../cloudflare-context.js";
import { debugCache, FALLBACK_BUILD_ID, IncrementalCacheEntry, isPurgeCacheEnabled } from "../internal.js";

import { NAME as KV_CACHE_NAME } from "./kv-incremental-cache.js";

const ONE_MINUTE_IN_SECONDS = 60;
const THIRTY_MINUTES_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 30;

Expand Down Expand Up @@ -79,20 +77,17 @@ class RegionalCache implements IncrementalCache {
private store: IncrementalCache,
private opts: Options
) {
if (this.store.name === KV_CACHE_NAME) {
throw new Error("The KV incremental cache does not need a regional cache.");
}
this.name = this.store.name;
// `shouldLazilyUpdateOnCacheHit` is not needed when cache purge is enabled.
this.opts.shouldLazilyUpdateOnCacheHit ??= this.opts.mode === "long-lived" && !isPurgeCacheEnabled();
}

get #bypassTagCacheOnCacheHit(): boolean {
if (this.opts.bypassTagCacheOnCacheHit !== undefined) {
// If the bypassTagCacheOnCacheHit option is set we return that one
return this.opts.bypassTagCacheOnCacheHit;
}

// Otherwise we default to whether the automatic cache purging is enabled or not
// When `bypassTagCacheOnCacheHit` is not set, we default to whether the automatic cache purging is enabled or not
return isPurgeCacheEnabled();
}

Expand Down Expand Up @@ -238,17 +233,7 @@ class RegionalCache implements IncrementalCache {
* a request is made to another region that has an entry stored in its regional cache.
*
* @param cache Incremental cache instance.
* @param opts.mode The mode to use for the regional cache.
* - `short-lived`: Re-use a cache entry for up to a minute after it has been retrieved.
* - `long-lived`: Re-use a fetch cache entry until it is revalidated (per-region),
* or an ISR/SSG entry for up to 30 minutes.
* @param opts.shouldLazilyUpdateOnCacheHit Whether the regional cache entry should be updated in
* the background or not when it experiences a cache hit.
* @param opts.defaultLongLivedTtlSec The default age to use for long-lived cache entries.
* When no revalidate is provided, the default age will be used.
* @default `THIRTY_MINUTES_IN_SECONDS`
*
* @default `false` for the `short-lived` mode, and `true` for the `long-lived` mode.
* @param opts Options for the regional cache.
*/
export function withRegionalCache(cache: IncrementalCache, opts: Options) {
return new RegionalCache(cache, opts);
Expand Down
Loading
Loading