8000 fix(cloudflare): add preview IDs for KV, D1, and R2 in wrangler.json by sam-goodwin · Pull Request #413 · sam-goodwin/alchemy · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(cloudflare): add preview IDs for KV, D1, and R2 in wrangler.json #413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 18, 2025
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
2 changes: 1 addition & 1 deletion alchemy/src/cloudflare/d1-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export type D1Database = D1DatabaseResource & Bound<D1DatabaseResource>;

export async function D1Database(
id: string,
props: Omit<D1DatabaseProps, "migrationsFiles">,
props: Omit<D1DatabaseProps, "migrationsFiles"> = {},
): Promise<D1Database> {
const migrationsFiles = props.migrationsDir
? await listMigrationsFiles(props.migrationsDir)
Expand Down
16 changes: 13 additions & 3 deletions alchemy/src/cloudflare/wrangler.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,14 +383,19 @@ function processBindings(
workerName: string,
): void {
// Arrays to collect different binding types
const kvNamespaces: { binding: string; id: string }[] = [];
const kvNamespaces: { binding: string; id: string; preview_id: string }[] =
[];
const durableObjects: {
name: string;
class_name: string;
script_name?: string;
environment?: string;
}[] = [];
const r2Buckets: { binding: string; bucket_name: string }[] = [];
const r2Buckets: {
binding: string;
bucket_name: string;
preview_bucket_name: string;
}[] = [];
const services: { binding: string; service: string; environment?: string }[] =
[];
const secrets: string[] = [];
Expand All @@ -405,6 +410,7 @@ function processBindings(
database_id: string;
database_name: string;
migrations_dir?: string;
preview_database_id: string;
}[] = [];
const queues: {
producers: { queue: string; binding: string }[];
Expand Down Expand Up @@ -484,9 +490,11 @@ function processBindings(
});
} else if (binding.type === "kv_namespace") {
// KV Namespace binding
const id = "namespaceId" in binding ? binding.namespaceId : binding.id;
kvNamespaces.push({
binding: bindingName,
id: "namespaceId" in binding ? binding.namespaceId : binding.id,
id: id,
preview_id: id,
});
} else if (
typeof binding === "object" &&
Expand All @@ -509,6 +517,7 @@ function processBindings(
r2Buckets.push({
binding: bindingName,
bucket_name: binding.name,
preview_bucket_name: binding.name,
});
} else if (binding.type === "secret") {
// Secret binding
Expand All @@ -531,6 +540,7 @@ function processBindings(
database_id: binding.id,
database_name: binding.name,
migrations_dir: binding.migrationsDir,
preview_database_id: binding.id,
});
} else if (binding.type === "queue") {
queues.producers.push({
Expand Down
208 changes: 169 additions & 39 deletions alchemy/test/cloudflare/wrangler-json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import * as path from "node:path";
import { describe, expect } from "vitest";
import { alchemy } from "../../src/alchemy.ts";
import { Ai } from "../../src/cloudflare/ai.ts";
import { R2Bucket } from "../../src/cloudflare/bucket.ts";
import { D1Database } from "../../src/cloudflare/d1-database.ts";
import { DurableObjectNamespace } from "../../src/cloudflare/durable-object-namespace.ts";
import { KVNamespace } from "../../src/cloudflare/kv-namespace.ts";
import { Worker } from "../../src/cloudflare/worker.ts";
import { WranglerJson } from "../../src/cloudflare/wrangler.json.ts";
import { destroy } from "../../src/destroy.ts";
Expand Down Expand Up @@ -131,10 +134,12 @@ describe("WranglerJson Resource", () => {
{ worker },
);

expect(spec.name).toEqual(name);
expect(spec.main).toEqual(entrypoint);
expect(spec.compatibility_date).toEqual(worker.compatibilityDate);
expect(spec.compatibility_flags).toEqual(worker.compatibilityFlags);
expect(spec).toMatchObject({
name,
main: entrypoint,
compatibility_date: worker.compatibilityDate,
compatibility_flags: worker.compatibilityFlags,
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
Expand Down Expand Up @@ -188,9 +193,12 @@ describe("WranglerJson Resource", () => {
{ worker },
);

expect(spec.name).toEqual(name);
expect(spec.browser).toBeDefined();
expect(spec.browser?.binding).toEqual("browser");
expect(spec).toMatchObject({
name,
browser: {
binding: "browser",
},
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
Expand Down Expand Up @@ -222,9 +230,12 @@ describe("WranglerJson Resource", () => {
{ worker },
);

expect(spec.name).toEqual(name);
expect(spec.ai).toBeDefined();
expect(spec.ai?.binding).toEqual("AI");
expect(spec).toMatchObject({
name,
ai: {
binding: "AI",
},
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
Expand Down Expand Up @@ -274,8 +285,10 @@ describe("WranglerJson Resource", () => {
);

// Verify the worker name and entrypoint
expect(spec.name).toEqual(name);
expect(spec.main).toEqual(entrypoint);
expect(spec).toMatchObject({
name,
main: entrypoint,
});

// Verify the durable object bindings
expect(spec.durable_objects).toBeDefined();
Expand All @@ -285,32 +298,29 @@ describe("WranglerJson Resource", () => {
const counterBinding = spec.durable_objects?.bindings.find(
(b) => b.class_name === "Counter",
);
expect(counterBinding).toBeDefined();
expect(counterBinding?.name).toEqual("COUNTER");
expect(counterBinding?.script_name).toEqual(name);
expect(counterBinding).toMatchObject({
name: "COUNTER",
script_name: name,
class_name: "Counter",
});

// Find SqliteCounter binding
const sqliteCounterBinding = spec.durable_objects?.bindings.find(
(b) => b.class_name === "SqliteCounter",
);
expect(sqliteCounterBinding).toBeDefined();
expect(sqliteCounterBinding?.name).toEqual("SQLITE_COUNTER");
expect(sqliteCounterBinding?.script_name).toEqual(name);
expect(sqliteCounterBinding).toMatchObject({
name: "SQLITE_COUNTER",
script_name: name,
class_name: "SqliteCounter",
});

// Verify migrations
expect(spec.migrations).toBeDefined();
expect(spec.migrations?.length).toEqual(1);
expect(spec.migrations?.[0].tag).toEqual("v1");

// Verify new_classes contains Counter
expect(spec.migrations?.[0].new_classes).toContain("Counter");
expect(spec.migrations?.[0].new_classes?.length).toEqual(1);

// Verify new_sqlite_classes contains SqliteCounter
expect(spec.migrations?.[0].new_sqlite_classes).toContain(
"SqliteCounter",
);
expect(spec.migrations?.[0].new_sqlite_classes?.length).toEqual(1);
expect(spec.migrations).toHaveLength(1);
expect(spec.migrations?.[0]).toMatchObject({
tag: "v1",
new_classes: ["Counter"],
new_sqlite_classes: ["SqliteCounter"],
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
Expand Down Expand Up @@ -349,12 +359,13 @@ describe("WranglerJson Resource", () => {
{ worker },
);

expect(spec.workflows).toBeDefined();
expect(spec.workflows?.length).toEqual(1);
expect(spec.workflows?.[0].name).toEqual("test-workflow");
expect(spec.workflows?.[0].binding).toEqual("WF");
expect(spec.workflows?.[0].class_name).toEqual("TestWorkflow");
expect(spec.workflows?.[0].script_name).toEqual("other-script");
expect(spec.workflows).toHaveLength(1);
expect(spec.workflows?.[0]). 5D32 toMatchObject({
name: "test-workflow",
binding: "WF",
class_name: "TestWorkflow",
script_name: "other-script",
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
Expand Down Expand Up @@ -383,8 +394,127 @@ describe("WranglerJson Resource", () => {
{ worker },
);

expect(spec.triggers).toBeDefined();
expect(spec.triggers?.crons).toEqual(worker.crons!);
expect(spec.triggers).toMatchObject({
crons: worker.crons!,
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
}
});

test("with KV namespace - includes preview_id", async (scope) => {
const name = `${BRANCH_PREFIX}-test-worker-kv-preview`;
const tempDir = path.join(".out", "alchemy-kv-preview-test");
const entrypoint = path.join(tempDir, "worker.ts");

try {
await fs.rm(tempDir, { recursive: true, force: true });
await fs.mkdir(tempDir, { recursive: true });
await fs.writeFile(entrypoint, esmWorkerScript);

const kvNamespace = await KVNamespace(`${BRANCH_PREFIX}-test-kv-ns`, {
title: "test-kv-namespace",
adopt: true,
});

const worker = await Worker(name, {
format: "esm",
entrypoint,
bindings: {
KV: kvNamespace,
},
adopt: true,
});

const { spec } = await WranglerJson(
`${BRANCH_PREFIX}-test-wrangler-json-kv-preview`,
{ worker },
);

expect(spec.kv_namespaces).toHaveLength(1);
expect(spec.kv_namespaces?.[0]).toMatchObject({
binding: "KV",
id: kvNamespace.namespaceId,
preview_id: kvNamespace.namespaceId,
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
}
});

test("with D1 database - includes preview_database_id", async (scope) => {
const name = `${BRANCH_PREFIX}-test-worker-d1-preview`;
const tempDir = path.join(".out", "alchemy-d1-preview-test");
const entrypoint = path.join(tempDir, "worker.ts");

try {
await fs.rm(tempDir, { recursive: true, force: true });
await fs.mkdir(tempDir, { recursive: true });
await fs.writeFile(entrypoint, esmWorkerScript);

const d1Database = await D1Database(`${BRANCH_PREFIX}-test-d1-db`);

const worker = await Worker(name, {
format: "esm",
entrypoint,
bindings: {
DB: d1Database,
},
adopt: true,
});

const { spec } = await WranglerJson(
`${BRANCH_PREFIX}-test-wrangler-json-d1-preview`,
{ worker },
);

expect(spec.d1_databases).toHaveLength(1);
expect(spec.d1_databases?.[0]).toMatchObject({
binding: "DB",
database_id: d1Database.id,
database_name: d1Database.name,
preview_database_id: d1Database.id,
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
}
});

test("with R2 bucket - includes preview_bucket_name", async (scope) => {
const name = `${BRANCH_PREFIX}-test-worker-r2-preview`;
const tempDir = path.join(".out", "alchemy-r2-preview-test");
const entrypoint = path.join(tempDir, "worker.ts");

try {
await fs.rm(tempDir, { recursive: true, force: true });
await fs.mkdir(tempDir, { recursive: true });
await fs.writeFile(entrypoint, esmWorkerScript);

const r2Bucket = await R2Bucket(`${BRANCH_PREFIX}-test-r2-bucket`);

const worker = await Worker(name, {
format: "esm",
entrypoint,
bindings: {
BUCKET: r2Bucket,
},
adopt: true,
});

const { spec } = await WranglerJson(
`${BRANCH_PREFIX}-test-wrangler-json-r2-preview`,
{ worker },
);

expect(spec.r2_buckets).toHaveLength(1);
expect(spec.r2_buckets?.[0]).toMatchObject({
binding: "BUCKET",
bucket_name: r2Bucket.name,
preview_bucket_name: r2Bucket.name,
});
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
await destroy(scope);
Expand Down
0