Merge pull request #2 from emdash-cms/fix/virtual-module-optimizedeps

fix: exclude virtual:emdash from optimizeDeps to fix npm installs on Cloudflare
This commit is contained in:
Matt Kane
2026-04-01 12:51:08 +01:00
committed by GitHub
9 changed files with 66 additions and 18 deletions

View File

@@ -0,0 +1,5 @@
---
"emdash": patch
---
Fix virtual module resolution errors when emdash is installed from npm on Cloudflare. The esbuild dependency pre-bundler was encountering `virtual:emdash/*` imports while crawling dist files and failing to resolve them. These are now excluded from the optimizeDeps scan.

View File

@@ -29,7 +29,7 @@ jobs:
- run: pnpm install --frozen-lockfile - run: pnpm install --frozen-lockfile
- run: pnpm build - run: pnpm build
- run: pnpm typecheck - run: pnpm typecheck
- run: pnpm run --filteremdash-demo --filter @emdash-cms/demo-cloudflare typecheck - run: pnpm run --filter emdash-demo --filter @emdash-cms/demo-cloudflare typecheck
- run: pnpm typecheck:templates - run: pnpm typecheck:templates
lint: lint:
@@ -138,7 +138,7 @@ jobs:
cache: pnpm cache: pnpm
- run: pnpm install --frozen-lockfile - run: pnpm install --frozen-lockfile
- run: pnpm build - run: pnpm build
- run: pnpm --filteremdash exec vitest run --config vitest.smoke.config.ts - run: pnpm --filter emdash exec vitest run --config vitest.smoke.config.ts
timeout-minutes: 5 timeout-minutes: 5
env: env:
DATABASE_URL: postgres://postgres:test@localhost:5432/emdash_smoke DATABASE_URL: postgres://postgres:test@localhost:5432/emdash_smoke

View File

@@ -269,6 +269,13 @@ export function createViteConfig(
// Vite discovers them one-by-one on first request, causing workerd // Vite discovers them one-by-one on first request, causing workerd
// to enter "worker cancelled" state on cold cache. // to enter "worker cancelled" state on cold cache.
optimizeDeps: { optimizeDeps: {
// Exclude EmDash virtual modules from esbuild's dependency
// scan. These are resolved by the Vite plugin at transform time,
// but esbuild encounters them when crawling emdash's dist files
// during pre-bundling and can't resolve them. Vite's exclude
// uses prefix matching (id.startsWith(m + "/")), so
// "virtual:emdash" matches all "virtual:emdash/*" imports.
exclude: ["virtual:emdash"],
include: [ include: [
// EmDash direct deps // EmDash direct deps
"emdash > @portabletext/toolkit", "emdash > @portabletext/toolkit",
@@ -322,7 +329,7 @@ export function createViteConfig(
include: useSource include: useSource
? ["@astrojs/react/client.js"] ? ["@astrojs/react/client.js"]
: ["@emdash-cms/admin", "@astrojs/react/client.js"], : ["@emdash-cms/admin", "@astrojs/react/client.js"],
exclude: cloudflare ? [] : NODE_NATIVE_EXTERNALS, exclude: cloudflare ? ["virtual:emdash"] : [...NODE_NATIVE_EXTERNALS, "virtual:emdash"],
}, },
}; };
} }

View File

@@ -14,6 +14,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
// References entries in ec_* tables by collection + entry_id // References entries in ec_* tables by collection + entry_id
await db.schema await db.schema
.createTable("revisions") .createTable("revisions")
.ifNotExists()
.addColumn("id", "text", (col) => col.primaryKey()) .addColumn("id", "text", (col) => col.primaryKey())
.addColumn("collection", "text", (col) => col.notNull()) // e.g., 'posts' .addColumn("collection", "text", (col) => col.notNull()) // e.g., 'posts'
.addColumn("entry_id", "text", (col) => col.notNull()) // ID in the ec_* table .addColumn("entry_id", "text", (col) => col.notNull()) // ID in the ec_* table
@@ -24,6 +25,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
await db.schema await db.schema
.createIndex("idx_revisions_entry") .createIndex("idx_revisions_entry")
.ifNotExists()
.on("revisions") .on("revisions")
.columns(["collection", "entry_id"]) .columns(["collection", "entry_id"])
.execute(); .execute();
@@ -31,6 +33,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
// Taxonomies // Taxonomies
await db.schema await db.schema
.createTable("taxonomies") .createTable("taxonomies")
.ifNotExists()
.addColumn("id", "text", (col) => col.primaryKey()) .addColumn("id", "text", (col) => col.primaryKey())
.addColumn("name", "text", (col) => col.notNull()) .addColumn("name", "text", (col) => col.notNull())
.addColumn("slug", "text", (col) => col.notNull()) .addColumn("slug", "text", (col) => col.notNull())
@@ -43,11 +46,17 @@ export async function up(db: Kysely<unknown>): Promise<void> {
) )
.execute(); .execute();
await db.schema.createIndex("idx_taxonomies_name").on("taxonomies").column("name").execute(); await db.schema
.createIndex("idx_taxonomies_name")
.ifNotExists()
.on("taxonomies")
.column("name")
.execute();
// Content-Taxonomy junction - references entries in ec_* tables // Content-Taxonomy junction - references entries in ec_* tables
await db.schema await db.schema
.createTable("content_taxonomies") .createTable("content_taxonomies")
.ifNotExists()
.addColumn("collection", "text", (col) => col.notNull()) // e.g., 'posts' .addColumn("collection", "text", (col) => col.notNull()) // e.g., 'posts'
.addColumn("entry_id", "text", (col) => col.notNull()) // ID in the ec_* table .addColumn("entry_id", "text", (col) => col.notNull()) // ID in the ec_* table
.addColumn("taxonomy_id", "text", (col) => col.notNull()) .addColumn("taxonomy_id", "text", (col) => col.notNull())
@@ -64,6 +73,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
// Media // Media
await db.schema await db.schema
.createTable("media") .createTable("media")
.ifNotExists()
.addColumn("id", "text", (col) => col.primaryKey()) .addColumn("id", "text", (col) => col.primaryKey())
.addColumn("filename", "text", (col) => col.notNull()) .addColumn("filename", "text", (col) => col.notNull())
.addColumn("mime_type", "text", (col) => col.notNull()) .addColumn("mime_type", "text", (col) => col.notNull())
@@ -80,6 +90,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
await db.schema await db.schema
.createIndex("idx_media_content_hash") .createIndex("idx_media_content_hash")
.ifNotExists()
.on("media") .on("media")
.column("content_hash") .column("content_hash")
.execute(); .execute();
@@ -87,6 +98,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
// Users // Users
await db.schema await db.schema
.createTable("users") .createTable("users")
.ifNotExists()
.addColumn("id", "text", (col) => col.primaryKey()) .addColumn("id", "text", (col) => col.primaryKey())
.addColumn("email", "text", (col) => col.notNull().unique()) .addColumn("email", "text", (col) => col.notNull().unique())
.addColumn("password_hash", "text", (col) => col.notNull()) .addColumn("password_hash", "text", (col) => col.notNull())
@@ -97,11 +109,17 @@ export async function up(db: Kysely<unknown>): Promise<void> {
.addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db))) .addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db)))
.execute(); .execute();
await db.schema.createIndex("idx_users_email").on("users").column("email").execute(); await db.schema
.createIndex("idx_users_email")
.ifNotExists()
.on("users")
.column("email")
.execute();
// Options (key-value store) // Options (key-value store)
await db.schema await db.schema
.createTable("options") .createTable("options")
.ifNotExists()
.addColumn("name", "text", (col) => col.primaryKey()) .addColumn("name", "text", (col) => col.primaryKey())
.addColumn("value", "text", (col) => col.notNull()) .addColumn("value", "text", (col) => col.notNull())
.execute(); .execute();
@@ -109,6 +127,7 @@ export async function up(db: Kysely<unknown>): Promise<void> {
// Audit logs (security events) // Audit logs (security events)
await db.schema await db.schema
.createTable("audit_logs") .createTable("audit_logs")
.ifNotExists()
.addColumn("id", "text", (col) => col.primaryKey()) .addColumn("id", "text", (col) => col.primaryKey())
.addColumn("timestamp", "text", (col) => col.defaultTo(currentTimestamp(db))) .addColumn("timestamp", "text", (col) => col.defaultTo(currentTimestamp(db)))
.addColumn("actor_id", "text") .addColumn("actor_id", "text")
@@ -120,9 +139,24 @@ export async function up(db: Kysely<unknown>): Promise<void> {
.addColumn("status", "text") .addColumn("status", "text")
.execute(); .execute();
await db.schema.createIndex("idx_audit_actor").on("audit_logs").column("actor_id").execute(); await db.schema
await db.schema.createIndex("idx_audit_action").on("audit_logs").column("action").execute(); .createIndex("idx_audit_actor")
await db.schema.createIndex("idx_audit_timestamp").on("audit_logs").column("timestamp").execute(); .ifNotExists()
.on("audit_logs")
.column("actor_id")
.execute();
await db.schema
.createIndex("idx_audit_action")
.ifNotExists()
.on("audit_logs")
.column("action")
.execute();
await db.schema
.createIndex("idx_audit_timestamp")
.ifNotExists()
.on("audit_logs")
.column("timestamp")
.execute();
} }
export async function down(db: Kysely<unknown>): Promise<void> { export async function down(db: Kysely<unknown>): Promise<void> {

View File

@@ -10,8 +10,8 @@
{ {
"binding": "DB", "binding": "DB",
"database_name": "my-emdash-site", "database_name": "my-emdash-site",
// Run `wrangler d1 create my-emdash-site` and paste the ID here // Run `wrangler d1 create my-emdash-site` and paste the real ID here for deploy
"database_id": "", "database_id": "local",
}, },
], ],
"r2_buckets": [ "r2_buckets": [

View File

@@ -10,8 +10,8 @@
{ {
"binding": "DB", "binding": "DB",
"database_name": "my-marketing-site", "database_name": "my-marketing-site",
// Run `wrangler d1 create my-marketing-site` and paste the ID here // Run `wrangler d1 create my-marketing-site` and paste the real ID here for deploy
"database_id": "", "database_id": "local",
}, },
], ],
"r2_buckets": [ "r2_buckets": [

View File

@@ -10,8 +10,8 @@
{ {
"binding": "DB", "binding": "DB",
"database_name": "my-portfolio-site", "database_name": "my-portfolio-site",
// Run `wrangler d1 create my-portfolio-site` and paste the ID here // Run `wrangler d1 create my-portfolio-site` and paste the real ID here for deploy
"database_id": "", "database_id": "local",
}, },
], ],
"r2_buckets": [ "r2_buckets": [

View File

@@ -3,7 +3,7 @@
/// <reference types="emdash/locals" /> /// <reference types="emdash/locals" />
import type { PortableTextBlock } from "emdash"; import type { ContentBylineCredit, PortableTextBlock } from "emdash";
export interface Page { export interface Page {
id: string; id: string;
@@ -14,6 +14,7 @@ export interface Page {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
publishedAt: Date | null; publishedAt: Date | null;
bylines?: ContentBylineCredit[];
} }
export interface Post { export interface Post {
@@ -27,6 +28,7 @@ export interface Post {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
publishedAt: Date | null; publishedAt: Date | null;
bylines?: ContentBylineCredit[];
} }
declare module "emdash" { declare module "emdash" {
@@ -34,4 +36,4 @@ declare module "emdash" {
pages: Page; pages: Page;
posts: Post; posts: Post;
} }
} }

View File

@@ -10,8 +10,8 @@
{ {
"binding": "DB", "binding": "DB",
"database_name": "my-emdash-site", "database_name": "my-emdash-site",
// Run `wrangler d1 create my-emdash-site` and paste the ID here // Run `wrangler d1 create my-emdash-site` and paste the real ID here for deploy
"database_id": "", "database_id": "local",
}, },
], ],
"r2_buckets": [ "r2_buckets": [