Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/fix-db-cloudflare-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/db': patch
---

Fixes `astro build --remote` failing with "Invalid URL string" when using `@astrojs/db` with `@astrojs/cloudflare`. The temporary Vite server created during build now filters out Cloudflare's environment-replacing plugins, preventing the workerd runtime from eagerly loading the manifest placeholder.
36 changes: 30 additions & 6 deletions packages/db/src/core/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,17 +242,41 @@ async function executeSeedFile({
}
}

/**
* Filter out adapter plugins that replace the SSR environment with a non-Node.js
* runtime (e.g. @cloudflare/vite-plugin creates a workerd-based environment).
* The temp server only needs a standard Node.js SSR environment for seed execution.
*/
export function filterPluginsForTempServer(plugins: UserConfig['plugins']): UserConfig['plugins'] {
return (plugins ?? []).flat().filter((plugin) => {
if (
plugin &&
typeof plugin === 'object' &&
'name' in plugin &&
typeof plugin.name === 'string'
) {
if (plugin.name.startsWith('vite-plugin-cloudflare')) return false;
}
return true;
});
}

/**
* Inspired by Astro content collection config loader.
*/
async function getTempViteServer({ viteConfig }: { viteConfig: UserConfig }) {
const filteredPlugins = filterPluginsForTempServer(viteConfig.plugins);

const tempViteServer = await createServer(
mergeConfig(viteConfig, {
server: { middlewareMode: true, hmr: false, watch: null, ws: false },
optimizeDeps: { noDiscovery: true },
ssr: { external: [] },
logLevel: 'silent',
}),
mergeConfig(
{ ...viteConfig, plugins: filteredPlugins },
{
server: { middlewareMode: true, hmr: false, watch: null, ws: false },
optimizeDeps: { noDiscovery: true },
ssr: { external: [] },
logLevel: 'silent',
},
),
);

const hotSend = tempViteServer.environments.client.hot.send;
Expand Down
79 changes: 79 additions & 0 deletions packages/db/test/unit/filter-plugins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import assert from 'node:assert';
import test, { describe } from 'node:test';
import { filterPluginsForTempServer } from '../../dist/core/integration/index.js';

describe('filterPluginsForTempServer', () => {
test('removes vite-plugin-cloudflare plugins', () => {
const plugins = [
{ name: 'astro:build' },
{ name: 'vite-plugin-cloudflare' },
{ name: 'vite-plugin-cloudflare:dev' },
{ name: 'vite-plugin-cloudflare:config' },
{ name: 'astro:assets' },
];
const filtered = filterPluginsForTempServer(plugins);
const names = (filtered ?? []).flatMap((p) =>
p && typeof p === 'object' && 'name' in p ? [p.name] : [],
);
assert.deepStrictEqual(names, ['astro:build', 'astro:assets']);
});

test('preserves all plugins when no cloudflare plugins present', () => {
const plugins = [
{ name: 'astro:build' },
{ name: 'astro:assets' },
{ name: '@astrojs/react' },
];
const filtered = filterPluginsForTempServer(plugins);
const names = (filtered ?? []).flatMap((p) =>
p && typeof p === 'object' && 'name' in p ? [p.name] : [],
);
assert.deepStrictEqual(names, ['astro:build', 'astro:assets', '@astrojs/react']);
});

test('handles undefined plugins', () => {
const filtered = filterPluginsForTempServer(undefined);
assert.deepStrictEqual(filtered, []);
});

test('handles empty plugins array', () => {
const filtered = filterPluginsForTempServer([]);
assert.deepStrictEqual(filtered, []);
});

test('handles nested plugin arrays (flattens)', () => {
const plugins = [
[{ name: 'astro:build' }, { name: 'vite-plugin-cloudflare:rsc' }],
{ name: 'astro:assets' },
];
const filtered = filterPluginsForTempServer(plugins);
const names = (filtered ?? []).flatMap((p) =>
p && typeof p === 'object' && 'name' in p ? [p.name] : [],
);
assert.deepStrictEqual(names, ['astro:build', 'astro:assets']);
});

test('handles null/falsy entries in plugins array', () => {
const plugins = [null, undefined, false, { name: 'astro:build' }] as const;
const filtered = filterPluginsForTempServer(plugins as any);
assert.strictEqual((filtered ?? []).length, 4); // null, undefined, false pass the name check
});

test('preserves non-cloudflare adapter plugins', () => {
const plugins = [
{ name: '@astrojs/cloudflare:cf-imports' },
{ name: '@astrojs/cloudflare:environment' },
{ name: 'vite-plugin-cloudflare' },
{ name: 'vite-plugin-cloudflare:dev' },
];
const filtered = filterPluginsForTempServer(plugins);
const names = (filtered ?? []).flatMap((p) =>
p && typeof p === 'object' && 'name' in p ? [p.name] : [],
);
// Only vite-plugin-cloudflare* are removed; @astrojs/cloudflare:* are kept
assert.deepStrictEqual(names, [
'@astrojs/cloudflare:cf-imports',
'@astrojs/cloudflare:environment',
]);
});
});
Loading