From 494ee7b61cab3151d3c0ec1f0fddc9c2e47d83c5 Mon Sep 17 00:00:00 2001 From: Hash Date: Tue, 24 Feb 2026 17:35:24 +0800 Subject: [PATCH 1/6] fix: Append to existing `.assetsignore`, not overwriting (#12628) Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .changeset/cool-oranges-fail.md | 7 ++++++ .../assets/__tests__/assets.spec.ts | 22 ++++++++++++++++++- .../playground/assets/public/.assetsignore | 2 ++ .../src/plugins/output-config.ts | 21 ++++++++++++++++-- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 .changeset/cool-oranges-fail.md create mode 100644 packages/vite-plugin-cloudflare/playground/assets/public/.assetsignore diff --git a/.changeset/cool-oranges-fail.md b/.changeset/cool-oranges-fail.md new file mode 100644 index 000000000000..7751b6119189 --- /dev/null +++ b/.changeset/cool-oranges-fail.md @@ -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. diff --git a/packages/vite-plugin-cloudflare/playground/assets/__tests__/assets.spec.ts b/packages/vite-plugin-cloudflare/playground/assets/__tests__/assets.spec.ts index a44270d22c1a..307f742ba2c5 100644 --- a/packages/vite-plugin-cloudflare/playground/assets/__tests__/assets.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/assets/__tests__/assets.spec.ts @@ -1,5 +1,13 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; import { test } from "vitest"; -import { getResponse, page, viteTestUrl } from "../../__test-utils__"; +import { + getResponse, + isBuild, + page, + rootDir, + viteTestUrl, +} from "../../__test-utils__"; import "./base-tests"; test("fetches transformed HTML asset", async ({ expect }) => { @@ -25,3 +33,15 @@ test("fetches original HTML asset if requested directly", async ({ const content = await page.textContent("h1"); expect(content).toBe("Original content"); }); + +test.runIf(isBuild)( + "emits .assetsignore in client output directory merged with defaults", + ({ expect }) => { + expect( + fs.readFileSync( + path.join(rootDir, "dist", "client", ".assetsignore"), + "utf-8" + ) + ).toBe(`test-file.txt\n*.bak\nwrangler.json\n.dev.vars\n`); + } +); diff --git a/packages/vite-plugin-cloudflare/playground/assets/public/.assetsignore b/packages/vite-plugin-cloudflare/playground/assets/public/.assetsignore new file mode 100644 index 000000000000..c4f0cf99ffc2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/assets/public/.assetsignore @@ -0,0 +1,2 @@ +test-file.txt +*.bak diff --git a/packages/vite-plugin-cloudflare/src/plugins/output-config.ts b/packages/vite-plugin-cloudflare/src/plugins/output-config.ts index 548e551aa0cb..40012ea751b1 100644 --- a/packages/vite-plugin-cloudflare/src/plugins/output-config.ts +++ b/packages/vite-plugin-cloudflare/src/plugins/output-config.ts @@ -1,4 +1,5 @@ import assert from "node:assert"; +import { existsSync, readFileSync } from "node:fs"; import * as path from "node:path"; import { MAIN_ENTRY_NAME } from "../cloudflare-environment"; import { assertIsNotPreview } from "../context"; @@ -86,12 +87,18 @@ export const outputConfigPlugin = createPlugin("output-config", (ctx) => { } } } else if (this.environment.name === "client") { - const filesToAssetsIgnore = ["wrangler.json", ".dev.vars"]; + const filesToAssetsIgnore = ["wrangler.json", ".dev.vars"] as const; + const existingAssetsIgnoreContent = + ctx.resolvedViteConfig.publicDir.length > 0 + ? readAssetsIgnoreFile( + path.join(ctx.resolvedViteConfig.publicDir, ".assetsignore") + ) + : ""; this.emitFile({ type: "asset", fileName: ".assetsignore", - source: `${filesToAssetsIgnore.join("\n")}\n`, + source: `${existingAssetsIgnoreContent}${filesToAssetsIgnore.join("\n")}\n`, }); // For assets only projects the `wrangler.json` file is emitted in the client output directory @@ -143,6 +150,16 @@ export const outputConfigPlugin = createPlugin("output-config", (ctx) => { }; }); +function readAssetsIgnoreFile(assetsIgnorePath: string): string { + const content = existsSync(assetsIgnorePath) + ? readFileSync(assetsIgnorePath, "utf-8") + : ""; + if (content.length === 0) { + return ""; + } + return content.at(-1) === "\n" ? content : `${content}\n`; +} + function getAssetsDirectory( workerOutputDirectory: string, resolvedViteConfig: vite.ResolvedConfig From 275ff4e7b10dd1d781377d96097f9c8e3d83247a Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 24 Feb 2026 10:51:37 +0100 Subject: [PATCH 2/6] Update error messages in e2e tests (#12662) --- packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts | 2 +- .../wrangler/e2e/remote-binding/dev-remote-bindings.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts b/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts index cc2ef684916a..403f85eab2cc 100644 --- a/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts @@ -183,7 +183,7 @@ if (isWindows) { expect(await proc.exitCode).not.toBe(0); expect(proc.stderr).toContain( - "R2 bucket 'non-existent-r2-bucket' not found. Please use a different name and try again. [code: 10085]" + "R2 bucket 'non-existent-r2-bucket' not found. Verify the bucket exists in your account and that the bucket_name in your configuration is correct. [code: 10085]" ); expect(proc.stderr).toContain( "Error: Failed to start the remote proxy session. There is likely additional logging output above." diff --git a/packages/wrangler/e2e/remote-binding/dev-remote-bindings.test.ts b/packages/wrangler/e2e/remote-binding/dev-remote-bindings.test.ts index 7e0b9d4d3d13..69049b2b55ff 100644 --- a/packages/wrangler/e2e/remote-binding/dev-remote-bindings.test.ts +++ b/packages/wrangler/e2e/remote-binding/dev-remote-bindings.test.ts @@ -209,7 +209,7 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)( await vi.waitFor( () => expect(worker.currentOutput).toContain( - "Could not resolve service binding 'REMOTE_WORKER'. Target script 'non-existent-service-binding' not found." + "Service binding 'REMOTE_WORKER' references Worker 'non-existent-service-binding' which was not found." ), 7_000 ); From 3d6e421dcd03fad1837c52c0e677d91678c6eed7 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 24 Feb 2026 11:08:49 +0000 Subject: [PATCH 3/6] [C3/wrangler] Fix Angular localhost SSR blocking in development mode (#12648) --- .changeset/fix-angular-localhost-ssr.md | 10 ++++++++++ .../e2e/tests/frameworks/test-config.ts | 9 --------- .../templates/angular/pages/templates/src/server.ts | 6 +++++- .../templates/angular/workers/templates/src/server.ts | 6 +++++- packages/wrangler/src/autoconfig/frameworks/angular.ts | 6 +++++- 5 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 .changeset/fix-angular-localhost-ssr.md diff --git a/.changeset/fix-angular-localhost-ssr.md b/.changeset/fix-angular-localhost-ssr.md new file mode 100644 index 000000000000..d46a2d4deb39 --- /dev/null +++ b/.changeset/fix-angular-localhost-ssr.md @@ -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. diff --git a/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts b/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts index 7dd3c7d980ea..62c91442133a 100644 --- a/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts +++ b/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts @@ -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", @@ -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", @@ -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", diff --git a/packages/create-cloudflare/templates/angular/pages/templates/src/server.ts b/packages/create-cloudflare/templates/angular/pages/templates/src/server.ts index f75db0a749af..a13fcf7814ad 100644 --- a/packages/create-cloudflare/templates/angular/pages/templates/src/server.ts +++ b/packages/create-cloudflare/templates/angular/pages/templates/src/server.ts @@ -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). diff --git a/packages/create-cloudflare/templates/angular/workers/templates/src/server.ts b/packages/create-cloudflare/templates/angular/workers/templates/src/server.ts index f75db0a749af..a13fcf7814ad 100644 --- a/packages/create-cloudflare/templates/angular/workers/templates/src/server.ts +++ b/packages/create-cloudflare/templates/angular/workers/templates/src/server.ts @@ -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). diff --git a/packages/wrangler/src/autoconfig/frameworks/angular.ts b/packages/wrangler/src/autoconfig/frameworks/angular.ts index f9b2af11abd6..193ae77d33fb 100644 --- a/packages/wrangler/src/autoconfig/frameworks/angular.ts +++ b/packages/wrangler/src/autoconfig/frameworks/angular.ts @@ -60,7 +60,11 @@ async function overrideServerFile() { dedent` 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). From 3649d3e408bf352468a59e47f05f42c9bd69c736 Mon Sep 17 00:00:00 2001 From: Ben <4991309+NuroDev@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:06:03 +0000 Subject: [PATCH 4/6] feat(local-explorer-ui): Add data studio table explorer tab (#12599) --- .changeset/light-clocks-enter.md | 9 + packages/local-explorer-ui/package.json | 6 + .../src/__tests__/drivers/common.test.ts | 1 + .../__tests__/drivers/sqlite/driver.test.ts | 1 + .../src/__tests__/utils/studio.test.ts | 2 +- .../src/components/Sidebar.tsx | 28 +- .../src/components/studio/Code/Block.tsx | 21 + .../src/components/studio/Code/Mirror.tsx | 180 ++++ .../studio/Modal/CommitConfirmation.tsx | 87 ++ .../studio/Modal/DeleteConfirmation.tsx | 105 +++ .../components/studio/Query/ResultStats.tsx | 85 ++ .../studio/SQLEditor/SQLThemePlugin.tsx | 104 +++ .../src/components/studio/TabRegister.tsx | 57 +- .../studio/Table/Result/EditableCell.tsx | 1 + .../components/studio/Table/State/Helpers.tsx | 369 ++++++++ .../components/studio/Table/State/index.tsx | 871 ++++++++++++++++++ .../components/studio/Tabs/TableExplorer.tsx | 530 +++++++++++ .../src/components/studio/index.tsx | 76 +- packages/local-explorer-ui/src/drivers/d1.ts | 5 +- .../src/drivers/sqlite/generate.ts | 53 +- .../src/drivers/sqlite/index.ts | 2 +- .../src/drivers/sqlite/parsers.ts | 2 +- .../src/routes/d1/$databaseId.tsx | 26 +- .../local-explorer-ui/src/styles/tailwind.css | 60 ++ .../local-explorer-ui/src/types/studio.ts | 8 +- .../local-explorer-ui/src/utils/is-equal.ts | 51 + .../src/utils/studio/commit.ts | 121 +++ .../src/utils/studio/index.ts | 131 +-- .../local-explorer-ui/src/utils/studio/sql.ts | 126 +++ pnpm-lock.yaml | 164 ++++ 30 files changed, 3045 insertions(+), 237 deletions(-) create mode 100644 .changeset/light-clocks-enter.md create mode 100644 packages/local-explorer-ui/src/components/studio/Code/Block.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Code/Mirror.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Modal/CommitConfirmation.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Modal/DeleteConfirmation.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Query/ResultStats.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/SQLEditor/SQLThemePlugin.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Table/Result/EditableCell.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Table/State/Helpers.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Table/State/index.tsx create mode 100644 packages/local-explorer-ui/src/components/studio/Tabs/TableExplorer.tsx create mode 100644 packages/local-explorer-ui/src/utils/is-equal.ts create mode 100644 packages/local-explorer-ui/src/utils/studio/commit.ts create mode 100644 packages/local-explorer-ui/src/utils/studio/sql.ts diff --git a/.changeset/light-clocks-enter.md b/.changeset/light-clocks-enter.md new file mode 100644 index 000000000000..9ebf092ddca6 --- /dev/null +++ b/.changeset/light-clocks-enter.md @@ -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. diff --git a/packages/local-explorer-ui/package.json b/packages/local-explorer-ui/package.json index d221181e880d..f4060444d596 100644 --- a/packages/local-explorer-ui/package.json +++ b/packages/local-explorer-ui/package.json @@ -20,6 +20,12 @@ "dependencies": { "@base-ui/react": "^1.1.0", "@cloudflare/kumo": "^1.5.0", + "@cloudflare/workers-editor-shared": "^0.1.1", + "@codemirror/autocomplete": "^6.20.0", + "@codemirror/lang-sql": "^6.10.0", + "@codemirror/language": "^6.12.1", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.14", "@phosphor-icons/react": "^2.1.10", "@tailwindcss/vite": "^4.0.15", "@tanstack/react-router": "^1.158.0", diff --git a/packages/local-explorer-ui/src/__tests__/drivers/common.test.ts b/packages/local-explorer-ui/src/__tests__/drivers/common.test.ts index 7d577d0db578..0fc5a14f06dd 100644 --- a/packages/local-explorer-ui/src/__tests__/drivers/common.test.ts +++ b/packages/local-explorer-ui/src/__tests__/drivers/common.test.ts @@ -15,6 +15,7 @@ const EMPTY_RESULT = { rowsAffected: 0, rowsRead: null, rowsWritten: null, + rowCount: 0, }, } satisfies StudioResultSet; diff --git a/packages/local-explorer-ui/src/__tests__/drivers/sqlite/driver.test.ts b/packages/local-explorer-ui/src/__tests__/drivers/sqlite/driver.test.ts index 4d923b8e6d6a..0a630fb3976d 100644 --- a/packages/local-explorer-ui/src/__tests__/drivers/sqlite/driver.test.ts +++ b/packages/local-explorer-ui/src/__tests__/drivers/sqlite/driver.test.ts @@ -10,6 +10,7 @@ const EMPTY_RESULT = { rowsAffected: 0, rowsRead: null, rowsWritten: null, + rowCount: 0, }, } satisfies StudioResultSet; diff --git a/packages/local-explorer-ui/src/__tests__/utils/studio.test.ts b/packages/local-explorer-ui/src/__tests__/utils/studio.test.ts index ab700c175f8b..f6fc0d21f2e5 100644 --- a/packages/local-explorer-ui/src/__tests__/utils/studio.test.ts +++ b/packages/local-explorer-ui/src/__tests__/utils/studio.test.ts @@ -1,9 +1,9 @@ import { describe, expect, test } from "vitest"; import { escapeSqlValue, - tokenizeSQL, transformStudioArrayBasedResult, } from "../../utils/studio"; +import { tokenizeSQL } from "../../utils/studio/sql"; describe("escapeSqlValue", () => { test("undefined returns `DEFAULT`", () => { diff --git a/packages/local-explorer-ui/src/components/Sidebar.tsx b/packages/local-explorer-ui/src/components/Sidebar.tsx index 3141d0f1dd0c..6b5a73214156 100644 --- a/packages/local-explorer-ui/src/components/Sidebar.tsx +++ b/packages/local-explorer-ui/src/components/Sidebar.tsx @@ -35,25 +35,26 @@ function SidebarItemGroup({ title, }: SidebarItemGroupProps): JSX.Element { return ( - - - - + + + + {title} -