Skip to content

feat: deploy with alchemy — typed alchemy.run.ts replacing deployment.jsonc + deploy.mjs - #1

Draft
sam-goodwin wants to merge 1 commit into
cloudflare:mainfrom
sam-goodwin:sam/alchemy-deploy
Draft

feat: deploy with alchemy — typed alchemy.run.ts replacing deployment.jsonc + deploy.mjs#1
sam-goodwin wants to merge 1 commit into
cloudflare:mainfrom
sam-goodwin:sam/alchemy-deploy

Conversation

@sam-goodwin

@sam-goodwin sam-goodwin commented Aug 5, 2026

Copy link
Copy Markdown

Delete deployment.jsonc, scripts/deploy.mjs, and the wrangler.jsonc files; the deployment is one typed alchemy.run.ts. Configuration is code, and the resources (KV, R2, AI Gateway, token, stored provider keys) are provisioned — not referenced.

// alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Config from "effect/Config";
import * as Effect from "effect/Effect";
import { OperatingSystem, OperatingSystemData } from "cloudflare-os";
import Context from "cloudflare-os/context";
import Custom from "custom-gatekeeper";

export default Alchemy.Stack(
  "MyCompanyOS",
  { providers: Cloudflare.providers(), state: Cloudflare.state() },
  Effect.gen(function* () {
    // Private error-reporting Worker owned by this stack.
    const errorReporter = yield* Cloudflare.Worker("ErrorReporter", {
      main: "./packages/error-reporter/src/index.ts",
      compatibility: { date: "2026-08-04" },
      workersDev: false,
    });

    // User data (KV, R2): separate and protected — `alchemy destroy`
    // retains it, so the OS can be destroyed and recreated freely.
    const data = yield* OperatingSystemData("Data");

    const os = yield* OperatingSystem("OS", {
      domain: "os.example.com",
      admins: ["<ADMIN_EMAIL>"],
      data,
      auth: {
        access: { aud: "<ACCESS_AUDIENCE>", iss: "https://<TEAM>.cloudflareaccess.com" },
      },
      gatekeepers: [
        Context({ sharingDomain: "production" }),
        Custom({ name: "<ORG_DISPLAY_NAME>", message: "<ORG_GUIDANCE>" }),
        // scheduler and mcp deploy by default; Mcp({ enabled: false }) opts out
      ],
      ai: {
        providers: {
          anthropic: Config.redacted("ANTHROPIC_API_KEY"),
          openai: Config.redacted("OPENAI_API_KEY"),
        },
      },
      backend: {
        services: [{
          binding: "ERROR_REPORTER",
          worker: errorReporter,
          entrypoint: "ErrorReporter",
          props: { service: "workshop", environment: "production" },
        }],
      },
      frontend: { env: { VITE_FRONTEND_ERROR_REPORTING: "true" } },
    });

    return { url: os.url };
  }),
);

The wrapper-owned integration exports its deployment as code, exactly like the built-in gatekeepers:

// packages/custom-gatekeeper/src/alchemy.ts — the package's root export
export const workerConfig = {
  name: "custom-gatekeeper",
  id: "Custom",
  entry: "index.ts",
  compatibilityDate: "2026-08-04",
  compatibilityFlags: ["allow_irrevocable_stub_storage", "nodejs_compat"],
  migrations: [{ tag: "v0", newSqliteClasses: ["CustomGatekeeper"] }],
} as const satisfies WorkerConfig;

export const Custom = (config: CustomConfig = {}): Gatekeeper =>
  Gatekeeper({
    name: "custom",
    worker: Gatekeeper.Worker(workerConfig, {
      dir: packageRoot(import.meta),
      env: {
        CUSTOM_NAME: config.name ?? "Example organization",
        CUSTOM_MESSAGE: config.message ?? "…",
      },
    }),
  });

export default Custom;

A custom gatekeeper can also be any bare alchemy Worker passed directly in gatekeepers: [...].

Status

Draft: gated on cloudflare/cloudflare-os#24 (the cloudflare-os package) merging, an alchemy npm release with Cloudflare.WorkerEntrypoint, and a submodule bump to match.

🤖 Generated with Claude Code

….jsonc + deploy.mjs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant