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
10 changes: 10 additions & 0 deletions .changeset/pnpm-init-build-approvals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"cf": patch
---

Allow required dependency build scripts in pnpm Worker projects

`cf init` now writes `pnpm-workspace.yaml` when pnpm is selected, approving the
`esbuild` and `workerd` build scripts before dependency installation. This also
applies with `--no-install` so a later `pnpm install` succeeds on pnpm 11 and
newer.
35 changes: 29 additions & 6 deletions packages/cli/src/__tests__/commands/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ describe("cf init", () => {

describe("new projects", () => {
it("creates a hello-world Worker in a new directory", async () => {
const result = await runCf(["init", "my-app", "--no-install"], {
npm_config_user_agent: undefined,
});
const result = await runCf(["init", "my-app", "--no-install"]);

expect(result.exitCode).toBe(0);
expect(readdirSync("my-app").sort()).toEqual(TEMPLATE_FILES);
Expand Down Expand Up @@ -293,6 +291,13 @@ describe("cf init", () => {

describe("dependency installation", () => {
it("installs with the package manager that launched cf", async () => {
vi.mocked(runProjectCommand).mockImplementationOnce(async () => {
expect(readFileSync("my-app/pnpm-workspace.yaml", "utf8")).toBe(
"# Approve build scripts needed by the generated Worker's dependencies.\n" +
"allowBuilds:\n esbuild: true\n workerd: true\n"
);
return { exitCode: 0 };
});
const result = await runCf(["init", "my-app"], {
npm_config_user_agent: PNPM_USER_AGENT,
});
Expand All @@ -306,6 +311,25 @@ describe("cf init", () => {
expect(stdout()).not.toContain("Install dependencies");
});

it("writes pnpm build approvals when installation is skipped", async () => {
const result = await runCf([
"init",
"my-app",
"--package-manager",
"pnpm",
"--no-install",
]);

expect(result.exitCode).toBe(0);
expect(readdirSync("my-app").sort()).toEqual(
[...TEMPLATE_FILES, "pnpm-workspace.yaml"].sort()
);
expect(readFileSync("my-app/pnpm-workspace.yaml", "utf8")).toContain(
" workerd: true"
);
expect(runProjectCommand).not.toHaveBeenCalled();
});

it("shows the target directory when prompting for the package manager", async () => {
let outputBeforePrompt = "";
vi.mocked(select).mockImplementationOnce(async () => {
Expand Down Expand Up @@ -351,6 +375,7 @@ describe("cf init", () => {
"bun install",
resolve("my-app")
);
expect(existsSync("my-app/pnpm-workspace.yaml")).toBe(false);
});

it("rejects unknown package managers", async () => {
Expand All @@ -363,9 +388,7 @@ describe("cf init", () => {
it("keeps the files and reports a failed install", async () => {
vi.mocked(runProjectCommand).mockResolvedValueOnce({ exitCode: 7 });

const result = await runCf(["init", "my-app"], {
npm_config_user_agent: undefined,
});
const result = await runCf(["init", "my-app"]);

expect(result.exitCode).toBe(7);
expect(readdirSync("my-app").sort()).toEqual(TEMPLATE_FILES);
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/__tests__/helpers/run-cf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ import { CliExit } from "../../lib/cli-exit.js";
* failures, handler throws that aren't `CliExit`) propagate.
*
* `env` lets a test stub specific env vars for the duration of the call.
* The package manager user agent is cleared by default so tests do not
* depend on which package manager launched Vitest.
* Vitest's `unstubEnvs: true` (in `vite.config.ts`) auto-restores
* after each test so callers don't need to clean up.
*/
export async function runCf(
argv: string[] = [],
env: Record<string, string | undefined> = {}
): Promise<{ exitCode: number }> {
for (const [key, value] of Object.entries(env)) {
for (const [key, value] of Object.entries({
npm_config_user_agent: undefined,
...env,
})) {
vi.stubEnv(key, value);
}
try {
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/commands/init/template.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { PackageManager } from "./workers.js";

export const WORKER_TEMPLATE_DEV_DEPENDENCIES = {
"@cloudflare/vite-plugin": "beta",
typescript: "^7.0.2",
Expand All @@ -8,6 +10,7 @@ export interface WorkerTemplateOptions {
name: string;
compatibilityDate: string;
cfVersion: string;
packageManager: PackageManager;
}

export function renderWorkerTemplate(
Expand All @@ -17,12 +20,23 @@ export function renderWorkerTemplate(
".gitignore": renderGitignore(),
"cloudflare.config.ts": renderCloudflareConfig(options),
"package.json": renderPackageJson(options),
...(options.packageManager === "pnpm"
? { "pnpm-workspace.yaml": renderPnpmWorkspace() }
: {}),
"src/index.ts": renderEntrypoint(),
"tsconfig.json": renderTsconfig(),
"vite.config.ts": renderViteConfig(),
};
}

function renderPnpmWorkspace(): string {
return `# Approve build scripts needed by the generated Worker's dependencies.
allowBuilds:
esbuild: true
workerd: true
`;
}

export function cfDependencyRange(version: string): string {
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)
? `^${version}`
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commands/init/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ async function createWorkerProject(
name,
compatibilityDate: DEFAULT_COMPAT_DATE,
cfVersion: VERSION,
packageManager,
});

for (const [path, contents] of Object.entries(files)) {
Expand Down
Loading