Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .changeset/lucky-chairs-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"create-cloudflare": patch
---

Fix --variant flag being ignored for pages

When creating a Pages project using `npm create cloudflare -- --type pages --variant <variant>`,
the `--variant` flag was being ignored, causing users to be prompted for variant selection
or defaulting to an unexpected variant. This now correctly passes the variant to the project setup.
11 changes: 11 additions & 0 deletions .changeset/reduce-fs-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"create-cloudflare": patch
"miniflare": patch
"@cloudflare/vitest-pool-workers": patch
"@cloudflare/workers-utils": patch
"wrangler": patch
---

Optimize filesystem operations by using Node.js's throwIfNoEntry: false option

This reduces the number of system calls made when checking for file existence by avoiding the overhead of throwing and catching errors for missing paths. This is an internal performance optimization with no user-visible behavioral changes.
39 changes: 39 additions & 0 deletions packages/create-cloudflare/e2e/tests/cli/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,45 @@ describe("Create Cloudflare CLI", () => {
'Unknown variant "invalid-variant". Valid variants are: react-ts, react-swc-ts, react, react-swc',
);
});

test("error when using invalid --variant for React Pages framework", async ({
logStream,
}) => {
const { errors } = await runC3(
[
"my-app",
"--framework=react",
"--platform=pages",
"--variant=invalid-variant",
"--no-deploy",
"--git=false",
],
[],
logStream,
);
expect(errors).toContain(
'Unknown variant "invalid-variant". Valid variants are: react-ts, react-swc-ts, react, react-swc',
);
});

test("accepts --variant for React Pages framework without prompting", async ({
logStream,
}) => {
const { output } = await runC3(
[
"my-app",
"--framework=react",
"--platform=pages",
"--variant=react-ts",
"--no-deploy",
"--git=false",
],
[],
logStream,
);
expect(output).toContain("--template react-ts");
expect(output).not.toContain("Select a variant");
});
});
});

Expand Down
10 changes: 3 additions & 7 deletions packages/create-cloudflare/src/helpers/codemod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { existsSync, lstatSync, readdirSync } from "node:fs";
import { lstatSync, readdirSync } from "node:fs";
import nodePath, { extname, join } from "node:path";
import * as recast from "recast";
import * as esprimaParser from "recast/parsers/esprima";
Expand Down Expand Up @@ -82,11 +82,7 @@ export const transformFile = (
export const loadSnippets = (parentFolder: string) => {
const snippetsPath = join(parentFolder, "snippets");

if (!existsSync(snippetsPath)) {
return {};
}

if (!lstatSync(snippetsPath).isDirectory) {
if (!lstatSync(snippetsPath, { throwIfNoEntry: false })?.isDirectory()) {
return {};
}

Expand All @@ -95,7 +91,7 @@ export const loadSnippets = (parentFolder: string) => {
return (
files
// don't try loading directories
.filter((fileName) => lstatSync(join(snippetsPath, fileName)).isFile)
.filter((fileName) => lstatSync(join(snippetsPath, fileName)).isFile())
// only load js or ts files
.filter((fileName) => [".js", ".ts"].includes(extname(fileName)))
.reduce((acc, snippetPath) => {
Expand Down
7 changes: 2 additions & 5 deletions packages/create-cloudflare/src/helpers/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,9 @@ export const removeFile = (path: string) => {

export const directoryExists = (path: string): boolean => {
try {
const stat = statSync(path);
return stat.isDirectory();
const stat = statSync(path, { throwIfNoEntry: false });
return stat?.isDirectory() ?? false;
} catch (error) {
if ((error as { code: string }).code === "ENOENT") {
return false;
}
throw new Error(error as string);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import nodePath from "node:path";
import xdgAppPaths from "xdg-app-paths";

function isDirectory(configPath: string) {
try {
return fs.statSync(configPath).isDirectory();
} catch {
// ignore error
return false;
}
return (
fs.statSync(configPath, { throwIfNoEntry: false })?.isDirectory() ?? false
);
}

export function getGlobalWranglerConfigPath() {
Expand Down
28 changes: 21 additions & 7 deletions packages/create-cloudflare/templates/react/pages/c3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ import type { C3Context } from "types";
const { npm } = detectPackageManager();

const generate = async (ctx: C3Context) => {
const variant = await inputPrompt({
type: "select",
question: "Select a variant:",
label: "variant",
options: variantsOptions,
defaultValue: variantsOptions[0].value,
});
const variant = await getVariant(ctx);

await runFrameworkGenerator(ctx, [ctx.project.name, "--template", variant]);

Expand All @@ -40,6 +34,26 @@ const variantsOptions = [
},
];

async function getVariant(ctx: C3Context) {
if (ctx.args.variant) {
const selected = variantsOptions.find((v) => v.value === ctx.args.variant);
if (!selected) {
throw new Error(
`Unknown variant "${ctx.args.variant}". Valid variants are: ${variantsOptions.map((v) => v.value).join(", ")}`,
);
}
return selected.value;
}

return await inputPrompt({
type: "select",
question: "Select a variant:",
label: "variant",
options: variantsOptions,
defaultValue: variantsOptions[0].value,
});
}

const config: TemplateConfig = {
configVersion: 1,
id: "react",
Expand Down
9 changes: 3 additions & 6 deletions packages/miniflare/src/shared/wrangler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ import path from "node:path";
import xdgAppPaths from "xdg-app-paths";

function isDirectory(configPath: string) {
try {
return fs.statSync(configPath).isDirectory();
} catch {
// ignore error
return false;
}
return (
fs.statSync(configPath, { throwIfNoEntry: false })?.isDirectory() ?? false
);
}

export function getGlobalWranglerConfigPath() {
Expand Down
29 changes: 18 additions & 11 deletions packages/vite-plugin-cloudflare/e2e/fixtures/basic/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,26 @@ export default {
requestAborted = true;
});

async function sendPing(writable: WritableStream) {
const writer = writable.getWriter();
const enc = new TextEncoder();
const { readable, writable } = new IdentityTransformStream();
// Acquire the writer immediately before returning the Response
// to ensure the stream stays open for writes
const writer = writable.getWriter();
const enc = new TextEncoder();

for (let i = 0; i < 6; i++) {
// Send 'ping' every 500ms to keep the connection alive for 3 seconds
await writer.write(enc.encode("ping\r\n"));
await scheduler.wait(500);
}
}
ctx.waitUntil(
(async () => {
try {
for (let i = 0; i < 6; i++) {
// Send 'ping' every 500ms to keep the connection alive for 3 seconds
await writer.write(enc.encode("ping\r\n"));
await scheduler.wait(500);
}
} finally {
await writer.close();
}
})()
);

const { readable, writable } = new IdentityTransformStream();
ctx.waitUntil(sendPing(writable));
return new Response(readable, {
headers: { "Content-Type": "text/plain" },
});
Expand Down
20 changes: 4 additions & 16 deletions packages/vitest-pool-workers/src/pool/module-fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,13 @@ const forceModuleTypeRegexp = new RegExp(
);

function isFile(filePath: string): boolean {
try {
return fs.statSync(filePath).isFile();
} catch (e) {
if (isFileNotFoundError(e)) {
return false;
}
throw e;
}
return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() ?? false;
}

function isDirectory(filePath: string): boolean {
try {
return fs.statSync(filePath).isDirectory();
} catch (e) {
if (isFileNotFoundError(e)) {
return false;
}
throw e;
}
return (
fs.statSync(filePath, { throwIfNoEntry: false })?.isDirectory() ?? false
);
}

function getParentPaths(filePath: string): string[] {
Expand Down
7 changes: 1 addition & 6 deletions packages/workers-utils/src/fs-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,5 @@ import fs from "node:fs";
* @returns `true` if the path is a directory, `false` otherwise
*/
export function isDirectory(path: string) {
try {
return fs.statSync(path).isDirectory();
} catch {
// ignore error
return false;
}
return fs.statSync(path, { throwIfNoEntry: false })?.isDirectory() ?? false;
}
11 changes: 5 additions & 6 deletions packages/wrangler/src/api/pages/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,11 @@ export async function deploy({
_routesCustom = readFileSync(join(directory, "_routes.json"), "utf-8");
} catch {}

try {
_workerJSIsDirectory = lstatSync(_workerPath).isDirectory();
if (!_workerJSIsDirectory) {
_workerJS = readFileSync(_workerPath, "utf-8");
}
} catch {}
const workerJSStats = lstatSync(_workerPath, { throwIfNoEntry: false });
_workerJSIsDirectory = workerJSStats?.isDirectory() ?? false;
if (workerJSStats !== undefined && !_workerJSIsDirectory) {
_workerJS = readFileSync(_workerPath, "utf-8");
}

// Grab the bindings from the API, we need these for shims and other such hacky inserts
const project = await fetchResult<Project>(
Expand Down
10 changes: 3 additions & 7 deletions packages/wrangler/src/autoconfig/c3-vendor/codemod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { existsSync, lstatSync, readdirSync, writeFileSync } from "node:fs";
import { lstatSync, readdirSync, writeFileSync } from "node:fs";
import path, { extname, join } from "node:path";
import { readFileSync } from "@cloudflare/workers-utils";
import * as recast from "recast";
Expand Down Expand Up @@ -80,11 +80,7 @@ export const transformFile = (
export const loadSnippets = (parentFolder: string) => {
const snippetsPath = join(parentFolder, "snippets");

if (!existsSync(snippetsPath)) {
return {};
}

if (!lstatSync(snippetsPath).isDirectory) {
if (!lstatSync(snippetsPath, { throwIfNoEntry: false })?.isDirectory()) {
return {};
}

Expand All @@ -93,7 +89,7 @@ export const loadSnippets = (parentFolder: string) => {
return (
files
// don't try loading directories
.filter((fileName) => lstatSync(join(snippetsPath, fileName)).isFile)
.filter((fileName) => lstatSync(join(snippetsPath, fileName)).isFile())
// only load js or ts files
.filter((fileName) => [".js", ".ts"].includes(extname(fileName)))
.reduce((acc, snippetPath) => {
Expand Down
11 changes: 2 additions & 9 deletions packages/wrangler/src/autoconfig/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,8 @@ import { spinner } from "@cloudflare/cli/interactive";
// we should clean this duplication up

function directoryExists(path: string): boolean {
try {
const stat = statSync(path);
return stat.isDirectory();
} catch (error) {
if ((error as { code: string }).code === "ENOENT") {
return false;
}
throw new Error(error as string);
}
const stat = statSync(path, { throwIfNoEntry: false });
return stat?.isDirectory() ?? false;
}

export async function appendToGitIgnore(
Expand Down
3 changes: 1 addition & 2 deletions packages/wrangler/src/miniflare-cli/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,7 @@ async function generateAssetsFetch(
}

if (
existsSync(filepath) &&
lstatSync(filepath).isFile() &&
lstatSync(filepath, { throwIfNoEntry: false })?.isFile() &&
!ignoredFiles.includes(filepath)
) {
const hash = hashFile(filepath);
Expand Down
3 changes: 2 additions & 1 deletion packages/wrangler/src/pages/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ export const pagesDevCommand = createCommand({
? join(directory, singleWorkerScriptPath)
: resolve(singleWorkerScriptPath);
const usingWorkerDirectory =
existsSync(workerScriptPath) && lstatSync(workerScriptPath).isDirectory();
lstatSync(workerScriptPath, { throwIfNoEntry: false })?.isDirectory() ??
false;
const usingWorkerScript = existsSync(workerScriptPath);
const enableBundling = args.bundle ?? !(args.noBundle ?? config.no_bundle);

Expand Down
5 changes: 3 additions & 2 deletions packages/wrangler/src/pages/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ export const pagesFunctionsOptimizeRoutesCommand = createCommand({
}

if (
!existsSync(routesOutputDirectory) ||
!lstatSync(routesOutputDirectory).isDirectory()
!lstatSync(routesOutputDirectory, {
throwIfNoEntry: false,
})?.isDirectory()
) {
throw new FatalError(
`Oops! Folder ${routesOutputDirectory} does not exist. Please make sure --output-routes-path is a valid file path (for example "/public/_routes.json").`,
Expand Down
4 changes: 2 additions & 2 deletions packages/wrangler/src/r2/helpers/bulk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export function validateBulkPutFile(
throw new UserError(`The file "${entry.file}" does not exist.`);
}

const stat = fs.statSync(entry.file);
if (!stat.isFile()) {
const stat = fs.statSync(entry.file, { throwIfNoEntry: false });
if (!stat?.isFile()) {
throw new UserError(`The path "${entry.file}" is not a file.`);
}

Expand Down
Loading
Loading