Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions fixtures/react-router-cloudflare/workers/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { createRequestHandler } from "react-router";
// @ts-ignore - app files are provided by the react-router template
import { redirectRequest } from "../app/redirect-url";
// @todo think about how to make __generated__ typeable
// @ts-ignore
import { redirects } from "../app/__generated__/$resources.redirects";

declare module "react-router" {
export interface AppLoadContext {
Expand All @@ -17,6 +22,11 @@ const requestHandler = createRequestHandler(

export default {
async fetch(request, env, ctx) {
const redirectResponse = redirectRequest(request, redirects);
if (redirectResponse !== undefined) {
return redirectResponse;
}

return requestHandler(request, {
EXCLUDE_FROM_SEARCH: false,
getDefaultActionResource: undefined,
Expand Down
16 changes: 15 additions & 1 deletion fixtures/webstudio-cloudflare-template/functions/[[path]].ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// @ts-ignore - app files are provided by the defaults template
import { redirectRequest } from "../app/redirect-url";
// @todo think about how to make __generated__ typeable
// @ts-ignore
import { redirects } from "../app/__generated__/$resources.redirects";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
import * as build from "../build/server";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
export const onRequest = createPagesFunctionHandler({ build });
const requestHandler = createPagesFunctionHandler({ build });

export const onRequest: PagesFunction = (context) => {
const redirectResponse = redirectRequest(context.request, redirects);
if (redirectResponse !== undefined) {
return redirectResponse;
}

return requestHandler(context);
};
76 changes: 76 additions & 0 deletions packages/cli/src/cloudflare-redirect-entrypoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, test } from "vitest";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { pathToFileURL } from "node:url";

const cliRoot = process.cwd();

const writeModule = async (path: string, contents: string) => {
await mkdir(dirname(path), { recursive: true });
await writeFile(path, contents, "utf8");
};

const importTempModule = async <Module>(path: string) => {
return (await import(
`${pathToFileURL(path).href}?test=${crypto.randomUUID()}`
)) as Module;
};

const writeTempApp = async (tempDir: string) => {
await writeModule(
join(tempDir, "app/redirect-url.ts"),
`
export const redirectRequest = (request, redirects) => {
const pathname = new URL(request.url).pathname;
const redirect = redirects.find((redirect) => redirect.old === pathname);
if (redirect === undefined) {
return;
}
return new Response(null, {
status: redirect.status,
headers: { Location: redirect.new },
});
};
`
);
await writeModule(
join(tempDir, "app/__generated__/$resources.redirects.ts"),
`export const redirects = [{ old: "/missing", new: "/target", status: 301 }];`
);

await writeModule(
join(tempDir, "workers/app.ts"),
await readFile(
join(cliRoot, "templates/react-router-cloudflare/workers/app.ts"),
"utf8"
)
);
};

describe("cloudflare redirect entrypoint", () => {
test("redirects before unmatched routes reach react-router", async () => {
const tempDir = await mkdtemp(join(cliRoot, ".tmp-cloudflare-redirect-"));
try {
await writeTempApp(tempDir);
const worker = await importTempModule<{
default: {
fetch: (
request: Request,
env: Record<string, never>,
ctx: Record<string, never>
) => Response | Promise<Response>;
};
}>(join(tempDir, "workers/app.ts"));
const response = await worker.default.fetch(
new Request("https://example.com/missing"),
{},
{}
);

expect(response.status).toBe(301);
expect(response.headers.get("Location")).toBe("/target");
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
});
31 changes: 31 additions & 0 deletions packages/cli/src/redirect-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,37 @@ describe("redirect-url fixture copies", () => {
)
);
});

test("keeps react-router-cloudflare worker redirect handling synced with template", async () => {
await expect(
readFile(
join(repoRoot, "fixtures/react-router-cloudflare/workers/app.ts"),
"utf8"
)
).resolves.toEqual(
await readFile(
join(cliRoot, "templates/react-router-cloudflare/workers/app.ts"),
"utf8"
)
);
});

test("keeps webstudio-cloudflare-template function redirect handling synced with template", async () => {
await expect(
readFile(
join(
repoRoot,
"fixtures/webstudio-cloudflare-template/functions/[[path]].ts"
),
"utf8"
)
).resolves.toEqual(
await readFile(
join(cliRoot, "templates/cloudflare/functions/[[path]].ts"),
"utf8"
)
);
});
});

for (const {
Expand Down
16 changes: 15 additions & 1 deletion packages/cli/templates/cloudflare/functions/[[path]].ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// @ts-ignore - app files are provided by the defaults template
import { redirectRequest } from "../app/redirect-url";
// @todo think about how to make __generated__ typeable
// @ts-ignore
import { redirects } from "../app/__generated__/$resources.redirects";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
import * as build from "../build/server";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
export const onRequest = createPagesFunctionHandler({ build });
const requestHandler = createPagesFunctionHandler({ build });

export const onRequest: PagesFunction = (context) => {
const redirectResponse = redirectRequest(context.request, redirects);
if (redirectResponse !== undefined) {
return redirectResponse;
}

return requestHandler(context);
};
10 changes: 10 additions & 0 deletions packages/cli/templates/react-router-cloudflare/workers/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { createRequestHandler } from "react-router";
// @ts-ignore - app files are provided by the react-router template
import { redirectRequest } from "../app/redirect-url";
// @todo think about how to make __generated__ typeable
// @ts-ignore
import { redirects } from "../app/__generated__/$resources.redirects";

declare module "react-router" {
export interface AppLoadContext {
Expand All @@ -17,6 +22,11 @@ const requestHandler = createRequestHandler(

export default {
async fetch(request, env, ctx) {
const redirectResponse = redirectRequest(request, redirects);
if (redirectResponse !== undefined) {
return redirectResponse;
}

return requestHandler(request, {
EXCLUDE_FROM_SEARCH: false,
getDefaultActionResource: undefined,
Expand Down
Loading