Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions .changeset/c3-mkdir-drive-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": patch
---

Skip `mkdir` when the project parent directory already exists, so `create-cloudflare` works at a Windows drive root (`E:\`) instead of throwing `EPERM`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { existsSync, mkdirSync } from "node:fs";
import { chdir } from "node:process";
import { resolve } from "node:path";
import { beforeEach, describe, test, vi } from "vitest";
import { setupProjectDirectory } from "../project-directory";
import type { C3Context } from "types";

vi.mock("node:fs");
vi.mock("node:process", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:process")>();
return { ...actual, chdir: vi.fn() };
});

const ctxFor = (projectPath: string): C3Context =>
({
args: {},
project: { name: "my-app", path: projectPath },
template: {},
deployment: {},
originalCWD: "/",
gitRepoAlreadyExisted: false,
}) as unknown as C3Context;

describe("setupProjectDirectory", () => {
beforeEach(() => {
vi.resetAllMocks();
});

test("does not mkdir when the parent already exists", ({ expect }) => {
const projectPath = resolve("already-there", "my-app");
const parent = resolve("already-there");
vi.mocked(existsSync).mockImplementation((p) => String(p) === parent);

setupProjectDirectory(ctxFor(projectPath));

expect(mkdirSync).not.toHaveBeenCalled();
expect(chdir).toHaveBeenCalledWith(parent);
});

test("creates the parent when it is missing", ({ expect }) => {
const projectPath = resolve("new-parent", "my-app");
const parent = resolve("new-parent");
vi.mocked(existsSync).mockReturnValue(false);

setupProjectDirectory(ctxFor(projectPath));

expect(mkdirSync).toHaveBeenCalledWith(parent, { recursive: true });
expect(chdir).toHaveBeenCalledWith(parent);
});
});
21 changes: 1 addition & 20 deletions packages/create-cloudflare/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#!/usr/bin/env node
import { mkdirSync } from "node:fs";
import { dirname } from "node:path";
import { chdir } from "node:process";
import {
cancel,
Expand Down Expand Up @@ -40,7 +38,7 @@ import {
updatePackageScripts,
writeAgentsMd,
} from "./templates";
import { validateProjectDirectory } from "./validators";
import { setupProjectDirectory } from "./project-directory";
import { addTypes } from "./workers";
import { updateWranglerConfig } from "./wrangler/config";
import type { C3Args, C3Context } from "types";
Expand Down Expand Up @@ -115,23 +113,6 @@ export const runCli = async (args: Partial<C3Args>) => {
logRaw("");
};

export const setupProjectDirectory = (ctx: C3Context) => {
// Crash if the directory already exists
const path = ctx.project.path;
const err = validateProjectDirectory(path, ctx.args);
if (err) {
throw new Error(err);
}

const directory = dirname(path);

// If the target is a nested directory, create the parent
mkdirSync(directory, { recursive: true });

// Change to the parent directory
chdir(directory);
};

const create = async (ctx: C3Context) => {
const { template } = ctx;

Expand Down
29 changes: 29 additions & 0 deletions packages/create-cloudflare/src/project-directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { existsSync, mkdirSync } from "node:fs";
import { dirname } from "node:path";
import { chdir } from "node:process";
import { validateProjectDirectory } from "./validators";
import type { C3Context } from "types";

/**
* Validates the target project directory and ensures its parent exists before
* changing into it. Skips `mkdir` when the parent already exists so a Windows
* drive root (`E:\`) does not throw `EPERM`.
*
* @param ctx - The C3 context containing the resolved project path and args
*/
export const setupProjectDirectory = (ctx: C3Context) => {
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
const path = ctx.project.path;
const err = validateProjectDirectory(path, ctx.args);
if (err) {
throw new Error(err);
}

const directory = dirname(path);

// Creating a Windows drive root (`E:\`) throws EPERM. Skip if it already exists.
if (!existsSync(directory)) {
mkdirSync(directory, { recursive: true });
}

chdir(directory);
};
Loading