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
7 changes: 7 additions & 0 deletions .changeset/cool-oranges-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/vite-plugin": patch
---

Append Cloudflare defaults to existing `.assetsignore` files during build output

When a project includes a `PUBLIC_DIR/.assetsignore`, the plugin now preserves those rules and appends the required `wrangler.json` and `.dev.vars` entries instead of replacing the file content.
10 changes: 10 additions & 0 deletions .changeset/fix-angular-localhost-ssr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"create-cloudflare": patch
"wrangler": patch
---

Fix Angular scaffolding to allow localhost SSR in development mode

Recent versions of Angular's `AngularAppEngine` block serving SSR on `localhost` by default. This caused `wrangler dev` / `wrangler pages dev` to fail with `URL with hostname "localhost" is not allowed.`

The fix passes `allowedHosts: ["localhost"]` to the `AngularAppEngine` constructor in `server.ts`, which is safe to do even in production since Cloudflare will already restrict which host is allowed.
10 changes: 10 additions & 0 deletions .changeset/keen-fish-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@cloudflare/workers-utils": patch
---

Add `removeDir` and `removeDirSync` helpers with automatic retry logic for Windows EBUSY errors

These new helpers wrap `fs.rm`/`fs.rmSync` with `maxRetries: 5` and `retryDelay: 100` to handle cases where file handles aren't immediately released (common on Windows with workerd).
The async helper also has a `fireAndForget` option to silently swallow errors and not await removal.

This improves reliability of cleanup operations across the codebase.
9 changes: 9 additions & 0 deletions .changeset/light-clocks-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@cloudflare/local-explorer-ui": minor
---

Adds the tab definition for the table explorer.

This serves as another stepping stone for adding the complete data studio to the local explorer.

This is a WIP experimental feature.
1 change: 1 addition & 0 deletions fixtures/additional-modules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@cloudflare/eslint-config-shared": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "catalog:default",
"@fixture/shared": "workspace:*",
"typescript": "catalog:default",
"undici": "catalog:default",
"vitest": "catalog:default",
Expand Down
21 changes: 3 additions & 18 deletions fixtures/additional-modules/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { removeDir } from "@fixture/shared/src/fs-helpers";
import { afterAll, assert, beforeAll, describe, test, vi } from "vitest";
import { unstable_startWorker } from "wrangler";
import { wranglerEntryPath } from "../../shared/src/run-wrangler-long-lived";
Expand Down Expand Up @@ -41,21 +42,7 @@ describe("find_additional_modules dev", () => {
});
afterAll(async () => {
await worker.dispose();
try {
await fs.rm(tmpDir, { recursive: true, force: true });
} catch (e) {
// It seems that Windows doesn't let us delete this, with errors like:
//
// Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ'
// ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
// Serialized Error: {
// "code": "EBUSY",
// "errno": -4082,
// "path": "C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ",
// "syscall": "rmdir",
// }
console.error(e);
}
removeDir(tmpDir, { fireAndForget: true });
});

test("supports bundled modules", async ({ expect }) => {
Expand Down Expand Up @@ -141,9 +128,7 @@ describe("find_additional_modules deploy", () => {
beforeAll(async () => {
tmpDir = await getTmpDir();
});
afterAll(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
afterAll(async () => await removeDir(tmpDir, { fireAndForget: true }));

test("doesn't bundle additional modules", async ({ expect }) => {
const outDir = path.join(tmpDir, "out");
Expand Down
73 changes: 9 additions & 64 deletions fixtures/interactive-dev-tests/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import rl from "node:readline";
import stream from "node:stream";
import { setTimeout } from "node:timers/promises";
import { stripVTControlCharacters } from "node:util";
import { removeDir } from "@fixture/shared/src/fs-helpers";
import { fetch } from "undici";
/* eslint-disable workers-sdk/no-vitest-import-expect -- complex test with .each patterns */
import {
Expand Down Expand Up @@ -380,22 +381,8 @@ if (process.platform === "win32") {
});
}
});
afterAll(async () => {
try {
fs.rmSync(tmpDir, { recursive: true, force: true });
} catch (e) {
// It seems that Windows doesn't let us delete this, with errors like:
//
// Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ'
// ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
// Serialized Error: {
// "code": "EBUSY",
// "errno": -4082,
// "path": "C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ",
// "syscall": "rmdir",
// }
console.error(e);
}
afterAll(() => {
removeDir(tmpDir, { fireAndForget: true });
});

it("should print rebuild containers hotkey", async () => {
Expand Down Expand Up @@ -560,22 +547,8 @@ if (process.platform === "win32") {
}
});

afterAll(async () => {
try {
fs.rmSync(tmpDir, { recursive: true, force: true });
} catch (e) {
// It seems that Windows doesn't let us delete this, with errors like:
//
// Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ'
// ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
// Serialized Error: {
// "code": "EBUSY",
// "errno": -4082,
// "path": "C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ",
// "syscall": "rmdir",
// }
console.error(e);
}
afterAll(() => {
removeDir(tmpDir, { fireAndForget: true });
});

it("should allow quitting while the image is building", async () => {
Expand Down Expand Up @@ -675,22 +648,8 @@ if (process.platform === "win32") {
});
}
});
afterAll(async () => {
try {
fs.rmSync(tmpDir, { recursive: true, force: true });
} catch (e) {
// It seems that Windows doesn't let us delete this, with errors like:
//
// Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ'
// ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
// Serialized Error: {
// "code": "EBUSY",
// "errno": -4082,
// "path": "C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ",
// "syscall": "rmdir",
// }
console.error(e);
}
afterAll(() => {
removeDir(tmpDir, { fireAndForget: true });
});

it("should print build logs for all the containers", async () => {
Expand Down Expand Up @@ -837,22 +796,8 @@ if (process.platform === "win32") {
});
}
});
afterAll(async () => {
try {
fs.rmSync(tmpDir, { recursive: true, force: true });
} catch (e) {
// It seems that Windows doesn't let us delete this, with errors like:
//
// Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ'
// ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
// Serialized Error: {
// "code": "EBUSY",
// "errno": -4082,
// "path": "C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ",
// "syscall": "rmdir",
// }
console.error(e);
}
afterAll(() => {
removeDir(tmpDir, { fireAndForget: true });
});

it("should be able to interact with both workers, rebuild the containers with the hotkey and all containers should be cleaned up at the end", async () => {
Expand Down
1 change: 1 addition & 0 deletions fixtures/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"description": "Shared fixtures for testing",
"devDependencies": {
"@cloudflare/workers-utils": "workspace:*",
"wrangler": "workspace:*"
},
"volta": {
Expand Down
3 changes: 3 additions & 0 deletions fixtures/shared/src/fs-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Re-exported so that fixtures can import from `@fixture/shared/src/fs-helpers`
// without each fixture needing a direct dependency on `@cloudflare/workers-utils`.
export { removeDir, removeDirSync } from "@cloudflare/workers-utils";
1 change: 1 addition & 0 deletions fixtures/wildcard-modules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@cloudflare/eslint-config-shared": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "catalog:default",
"@fixture/shared": "workspace:*",
"undici": "catalog:default",
"wrangler": "workspace:*"
},
Expand Down
21 changes: 4 additions & 17 deletions fixtures/wildcard-modules/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { setTimeout } from "node:timers/promises";
import { removeDir } from "@fixture/shared/src/fs-helpers";
import { fetch } from "undici";
import { afterAll, assert, beforeAll, describe, test } from "vitest";
import {
Expand Down Expand Up @@ -55,21 +56,7 @@ describe("wildcard imports: dev", () => {
});
afterAll(async () => {
await worker.stop();
try {
await fs.rm(tmpDir, { recursive: true, force: true });
} catch (e) {
// It seems that Windows doesn't let us delete this, with errors like:
//
// Error: EBUSY: resource busy or locked, rmdir 'C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ'
// ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
// Serialized Error: {
// "code": "EBUSY",
// "errno": -4082,
// "path": "C:\Users\RUNNER~1\AppData\Local\Temp\wrangler-modules-pKJ7OQ",
// "syscall": "rmdir",
// }
console.error(e);
}
removeDir(tmpDir, { fireAndForget: true });
});

test("supports bundled modules", async ({ expect }) => {
Expand Down Expand Up @@ -152,8 +139,8 @@ describe("wildcard imports: deploy", () => {
beforeAll(async () => {
tmpDir = await getTmpDir();
});
afterAll(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
afterAll(() => {
removeDir(tmpDir, { fireAndForget: true });
});

test("bundles wildcard modules", async ({ expect }) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/create-cloudflare/e2e/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ const testProjectDir = (suite: string, test: string) => {

realpathSync(mkdtempSync(nodePath.join(tmpdir(), `c3-tests-${suite}`)));
const filepath = getPath();
// eslint-disable-next-line workers-sdk/no-direct-recursive-rm -- see log-stream.ts for rationale
rmSync(filepath, {
recursive: true,
force: true,
maxRetries: 10,
maxRetries: 5,
retryDelay: 100,
});
} catch (e) {
Expand Down
10 changes: 10 additions & 0 deletions packages/create-cloudflare/e2e/helpers/log-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,19 @@ export function createTestLogStream(task: RunnerTask) {

export function recreateLogFolder(suite: RunnerTestSuite) {
// Clean the old folder if exists (useful for dev)
//
// Note: this is intentionally inlined rather than importing `removeDirSync`
// from `@cloudflare/workers-utils`. That package's barrel export pulls in CJS
// dependencies (e.g. `xdg-app-paths`) that break when Vite bundles the vitest
// config (which imports this file) into ESM — the shimmed `require()` calls
// throw "Dynamic require of 'path' is not supported".
// Keep aligned with `removeDirSync()` in `packages/workers-utils/src/fs-helpers.ts`.
// eslint-disable-next-line workers-sdk/no-direct-recursive-rm
rmSync(getLogPath(suite), {
recursive: true,
force: true,
maxRetries: 5,
retryDelay: 100,
});

mkdirSync(getLogPath(suite), { recursive: true });
Expand Down
7 changes: 4 additions & 3 deletions packages/create-cloudflare/e2e/tests/cli/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { execSync } from "node:child_process";
import fs, { readFileSync } from "node:fs";
import fs from "node:fs";
import { basename, join, resolve } from "node:path";
import { detectPackageManager } from "helpers/packageManagers";
// eslint-disable-next-line workers-sdk/no-vitest-import-expect -- e2e test with complex patterns
Expand Down Expand Up @@ -188,10 +188,11 @@ describe("Create Cloudflare CLI", () => {
expect(output).toContain(`lang TypeScript`);
expect(output).toContain(`no deploy`);
} finally {
// eslint-disable-next-line workers-sdk/no-direct-recursive-rm -- see e2e/helpers/log-stream.ts for rationale
fs.rmSync(existingFilePath, {
recursive: true,
force: true,
maxRetries: 10,
maxRetries: 5,
retryDelay: 100,
});
}
Expand Down Expand Up @@ -257,7 +258,7 @@ describe("Create Cloudflare CLI", () => {
// underlying issue appears to be with the packages pinned in
// the template - not whether or not the settings.json file is
// created
expect(readFileSync(`${project.path}/.vscode/settings.json`, "utf8"))
expect(fs.readFileSync(`${project.path}/.vscode/settings.json`, "utf8"))
.toMatchInlineSnapshot(`
"{
"files.associations": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,6 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
},
nodeCompat: false,
flags: ["--style", "sass"],
// TODO: currently the Angular tests are failing with `URL with hostname "localhost" is not allowed.`
// we need to investigate this so that we can un-quarantine the Angular tests
quarantine: true,
},
{
name: "angular:workers",
Expand All @@ -189,9 +186,6 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
},
nodeCompat: false,
flags: ["--style", "sass"],
// TODO: currently the Angular tests are failing with `URL with hostname "localhost" is not allowed.`
// we need to investigate this so that we can un-quarantine the Angular tests
quarantine: true,
},
{
name: "gatsby:pages",
Expand Down Expand Up @@ -790,9 +784,6 @@ function getExperimentalFrameworkTestConfig(
nodeCompat: false,
flags: ["--style", "sass"],
verifyTypes: false,
// TODO: currently the Angular tests are failing with `URL with hostname "localhost" is not allowed.`
// we need to investigate this so that we can un-quarantine the Angular tests
quarantine: true,
},
{
name: "nuxt:workers",
Expand Down
15 changes: 14 additions & 1 deletion packages/create-cloudflare/src/helpers/packageManagers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,20 @@ export const rectifyPmMismatch = async (ctx: C3Context) => {

const nodeModulesPath = nodePath.join(ctx.project.path, "node_modules");
if (existsSync(nodeModulesPath)) {
rmSync(nodeModulesPath, { recursive: true });
// Note: this is intentionally inlined rather than importing `removeDirSync`
// from `@cloudflare/workers-utils`. That package's barrel export pulls in CJS
// dependencies (e.g. `xdg-app-paths`) that break when Vite bundles code into
// ESM — the shimmed `require()` calls throw "Dynamic require of 'path' is not
// supported". While the production build (esbuild → CJS) would handle this
// fine, the e2e tests import this file through Vitest which uses Vite's bundler.
// Keep aligned with `removeDirSync()` in `packages/workers-utils/src/fs-helpers.ts`.
// eslint-disable-next-line workers-sdk/no-direct-recursive-rm
rmSync(nodeModulesPath, {
recursive: true,
force: true,
maxRetries: 5,
retryDelay: 100,
});
}

const lockfilePath = nodePath.join(ctx.project.path, "package-lock.json");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { AngularAppEngine, createRequestHandler } from '@angular/ssr';

const angularApp = new AngularAppEngine();
const angularApp = new AngularAppEngine({
// It is safe to set allow `localhost`, so that SSR can run in local development,
// as, in production, Cloudflare will ensure that `localhost` is not the host.
allowedHosts: ['localhost'],
});

/**
* This is a request handler used by the Angular CLI (dev-server and during build).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { AngularAppEngine, createRequestHandler } from '@angular/ssr';

const angularApp = new AngularAppEngine();
const angularApp = new AngularAppEngine({
// It is safe to set allow `localhost`, so that SSR can run in local development,
// as, in production, Cloudflare will ensure that `localhost` is not the host.
allowedHosts: ['localhost'],
});

/**
* This is a request handler used by the Angular CLI (dev-server and during build).
Expand Down
Loading
Loading