Skip to content
Closed
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
91 changes: 91 additions & 0 deletions alchemy-web/src/content/docs/blog/2025-08-15-alchemy-0-62.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: "Alchemy 0.62: Scoped AWS Credentials"
date: 2025-08-15
authors: Sam Goodwin
excerpt: Deploy AWS resources across multiple accounts and regions from a single alchemy.run.ts with global, scope, and resource-level credential overrides. Plus, alchemy create now keeps secrets out of git and configures CloudflareStateStore for CI.
---

Alchemy 0.62 lets you deploy AWS resources across multiple accounts and regions in a single deployment. Credentials can now be overridden at three levels — global environment variables, an entire scope, or an individual resource — so a multi-account architecture no longer needs multiple state files or wrapper scripts.

The `alchemy create` CLI also got smarter about the boring-but-important stuff: it appends `.alchemy` to your `.gitignore` so local state and secrets never get committed, and it configures `CloudflareStateStore` automatically when you choose GitHub Actions for CI.

<!-- excerpt -->

## Multi-account AWS deployments

AWS credentials now resolve through a three-tier hierarchy: resource-level overrides win over scope-level credentials, which win over the global environment (`AWS_PROFILE`, `AWS_REGION`, etc.).

Every AWS EC2 networking resource accepts credential properties directly, so two VPCs in two different accounts is just two resources:

```ts
import alchemy from "alchemy";
import { Vpc } from "alchemy/aws/ec2";

const app = await alchemy("multi-account");

// uses AWS_PROFILE / AWS_REGION from the environment
const mainVpc = await Vpc("main-vpc", {
cidrBlock: "10.0.0.0/16",
});

// deployed to a different account and region
const secondaryVpc = await Vpc("secondary-vpc", {
cidrBlock: "10.1.0.0/16",
profile: "secondary-account",
region: "us-east-1",
});

await app.finalize();
```

To apply credentials to a whole group of resources, set them on a scope with the `aws` option:

```ts
await alchemy.run(
"production",
{
aws: {
region: "us-east-1",
profile: "production-account",
},
},
async () => {
// everything in here uses the production-account profile
await Vpc("prod-vpc", {
cidrBlock: "10.0.0.0/16",
});
},
);
```

Cross-account role assumption works too — hand a resource a `roleArn` and Alchemy assumes it before making API calls:

```ts
const vpc = await Vpc("cross-account-vpc", {
cidrBlock: "10.0.0.0/16",
roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
roleSessionName: "alchemy-deployment",
region: "us-west-2",
});
```

Static credentials (`accessKeyId`, `secretAccessKey`, `sessionToken`) are typed as `Secret`, so if you do pass them explicitly, they're encrypted in your state file like any other Alchemy secret.

See the [AWS credential overrides docs](/providers/aws/credential-overrides) for the full property list and resolution rules.

## A more careful `alchemy create`

Two quality-of-life improvements landed in the CLI this release:

- **`.alchemy` is appended to `.gitignore`** during project init (and `.wrangler/` too, as of 0.62.3). Local state and dev secrets stay out of your repo without you having to remember.
- **Choosing GitHub Actions configures `CloudflareStateStore`.** CI needs shared state, so if you pick GitHub Actions in the create flow, your template is generated with `CloudflareStateStore` instead of the local filesystem store, the workflow sets `CLOUDFLARE_EMAIL`, `ALCHEMY_PASSWORD`, and `ALCHEMY_STATE_TOKEN`, and the workflow commands match whichever package manager you chose.

## Also in this release

- **Monorepo support.** Multiple instances of Alchemy (e.g. different package versions across workspaces) can now coexist in a monorepo without clobbering each other.
- **Template names are stage-aware.** Generated templates substitute the project name to construct stage-specific resource names, and no longer modify your app's stage.
- **Fixes.** Worker asset and no-bundle file paths now resolve correctly on Windows; large R2 buckets can be deleted; `CloudflareStateStore` retries transient errors; D1 phases use the correct `databaseId`; `spa: true` can be overridden in Vite apps; and `alchemy dev` spawns Vite idempotently and cleans up its processes on exit.

## Thanks

Thanks to **yehudacohen**, **Aman Varshney**, **Michael K**, **John Royal**, and **Rhayxz** for contributing to this release. See the [full changelog](https://github.com/alchemy-run/alchemy/compare/v0.61.0...v0.62.3) for everything in the 0.62 line.
100 changes: 100 additions & 0 deletions alchemy-web/src/content/docs/blog/2025-08-25-alchemy-0-63.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: "Alchemy 0.63: Automatic Physical Names"
date: 2025-08-25
authors: Sam Goodwin
excerpt: Resources without an explicit name now get one generated from your app, scope chain, and stage — so the same alchemy.run.ts deploys cleanly to any stage without collisions. Plus a --adopt CLI flag and R2 bucket lifecycle and lock rules.
---

Alchemy 0.63 changes how resources are named. If you don't give a resource an explicit `name`, Alchemy now generates one from your app name, scope chain, resource ID, and stage — which means the same `alchemy.run.ts` deploys to `dev`, `prod`, and a preview stage per PR without a single name collision.

This release also adds a `--adopt` flag to the CLI so you can adopt existing resources without sprinkling `adopt: true` everywhere, and R2 buckets picked up lifecycle and lock rules.

<!-- excerpt -->

## Physical names from app, stage, and resource ID

Previously, omitting `name` meant the resource ID was used directly as the physical name. That works until you deploy a second stage and Cloudflare tells you a Worker called `worker` already exists.

Now the generated name encodes where the resource lives:

```ts
import alchemy from "alchemy";
import { Worker } from "alchemy/cloudflare";

const app = await alchemy("my-app");

const worker = await Worker("worker");

console.log(worker.name); // "my-app-worker-${stage}"
```

Run `alchemy deploy --stage prod` and the Worker is named `my-app-worker-prod`. Nested scopes join the chain too:

```ts
await alchemy.run("backend", async () => {
await Worker("worker"); // my-app-backend-worker-prod
});
```

This applies across providers — Cloudflare, AWS, PlanetScale, Neon, Docker, Vercel, Upstash, Sentry, and more all default to `this.scope.createPhysicalName(id)` when no name is given.

Existing resources are unaffected: names resolve as `props.name ?? this.output?.name ?? generated`, so anything already deployed keeps the name it has. Only newly created resources pick up the new scheme.

See the [resource docs](/concepts/resource) for more on physical names.

## Blanket adoption with `--adopt`

If a resource already exists in your account (created by hand, or by a previous state file), Alchemy normally refuses to take it over unless you set `adopt: true` on the resource. That's the right default, but tedious when migrating a whole app.

The CLI now takes a flag that applies to every resource in the run:

```sh
alchemy deploy --adopt
alchemy dev --adopt
```

## R2 lifecycle and lock rules

`R2Bucket` now supports Cloudflare's object lifecycle and retention lock policies as plain props:

```ts
import { R2Bucket } from "alchemy/cloudflare";

const bucket = await R2Bucket("logs", {
lifecycle: [
// abort incomplete multipart uploads after 7 days
{
id: "abort-mpu-7d",
abortMultipartUploadsTransition: {
condition: { type: "Age", maxAge: 7 * 24 * 60 * 60 },
},
},
// delete archived objects after 30 days
{
id: "delete-30d",
conditions: { prefix: "archive/" },
deleteObjectsTransition: {
condition: { type: "Age", maxAge: 30 * 24 * 60 * 60 },
},
},
],
lock: [
// prevent deletion or overwrite for 90 days
{
id: "retain-90d",
condition: { type: "Age", maxAgeSeconds: 90 * 24 * 60 * 60 },
},
],
});
```

Rules support age and date conditions, prefix scoping, and storage-class transitions to Infrequent Access. See the [R2 Bucket docs](/providers/cloudflare/bucket) for the full set.

## Also in this release

- **Hono template.** `alchemy create` now offers a Hono starter template.
- **Fixes.** React Router apps can be deployed in SPA mode; Astro output type checks no longer trip on path validation and Astro correctly sets `spa: false`; a `WranglerJson` type-instantiation error is resolved; and `find-process` imports cleanly on plain Node.

## Thanks

Thanks to **Aman Varshney** and **John Royal** for contributing to this release. See the [full changelog](https://github.com/alchemy-run/alchemy/compare/v0.62.3...v0.63.1) for everything in the 0.63 line.
47 changes: 47 additions & 0 deletions alchemy-web/src/content/docs/blog/2025-08-27-alchemy-0-64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: "Alchemy 0.64: Flatter Website Scopes"
date: 2025-08-27
authors: Sam Goodwin
excerpt: Website no longer hides its Worker inside a nested scope, so moving between Website and a plain Worker is seamless. Plus R2 jurisdiction is now respected in bindings and wrangler.json.
---

Alchemy 0.64 flattens the `Website` scope hierarchy. `Website` (and the framework resources built on it — `Vite`, `Astro`, `Nuxt`, `ReactRouter`, and friends) used to create its Worker inside a nested scope via `alchemy.run`, which made it painful to refactor between a `Website` and a plain `Worker`: the resource lived at a different address in your state, so Alchemy would try to delete one and create the other.

<!-- excerpt -->

## One less layer of scope

The Worker created by a `Website` now lives directly in your scope, and the `WranglerJson` helper is a plain function instead of a Resource. In state terms:

```
before: my-app/prod/website/worker
after: my-app/prod/website
```

The upshot is that this refactor is now a non-event:

```ts
// before: a full-stack site
const site = await Vite("website", {
entrypoint: "./src/worker.ts",
});

// after: you outgrew the wrapper — same state, same Worker
const worker = await Worker("website", {
entrypoint: "./src/worker.ts",
});
```

The migration is handled automatically. When Alchemy encounters a Worker that used to live under a `Website` scope, it adopts the existing Worker on create and skips the delete of the old nested entry — so upgrading to 0.64 replaces nothing.

## Also in this release

- **R2 jurisdiction respected everywhere.** A bucket's `jurisdiction` (e.g. `eu`) is now drilled through to Worker bindings and the generated `wrangler.json`, and buckets without one send `undefined` instead of `"default"`.
- **Node compat synced with workers-sdk.** The `nodejs_compat` plugin is aligned with the latest Cloudflare workers-sdk behavior.
- **`cloudflare:*` modules marked external.** Imports like `cloudflare:workers` are no longer touched by the bundler.
- **Templates use generated names.** Starter templates no longer hard-code physical names — they lean on the physical name generation introduced in 0.63.
- **`@cloudflare/workers-types` peer dependency upgraded.**

## Thanks

Thanks to **John Royal** for contributing to this release. See the [full changelog](https://github.com/alchemy-run/alchemy/compare/v0.63.1...v0.64.0) for everything in 0.64.
104 changes: 104 additions & 0 deletions alchemy-web/src/content/docs/blog/2025-09-06-alchemy-0-65.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: "Alchemy 0.65: Debugging, Next.js, and PlanetScale Postgres"
date: 2025-09-06
authors: Sam Goodwin
excerpt: Attach a debugger to your alchemy.run.ts and your Workers with the new --inspect flags, deploy Next.js to Cloudflare with a single resource, and provision PlanetScale Postgres databases with scoped roles.
---

Alchemy 0.65 is a big one. You can now attach a real debugger — breakpoints, step-through, the works — to both your `alchemy.run.ts` and the Workers it deploys, straight from the CLI. There's a new `Nextjs` resource that deploys Next.js apps to Cloudflare via OpenNext, and PlanetScale support now covers their Postgres offering with a new `Role` resource for scoped credentials.

<!-- excerpt -->

## Debug via the CLI

All Alchemy runtime commands now accept `--inspect`, `--inspect-wait`, and `--inspect-brk`:

```sh
alchemy dev --inspect-wait
alchemy deploy --inspect
alchemy destroy --inspect
```

Under the hood, the CLI runs a Chrome DevTools Protocol management server that aggregates every debuggable process in your session — the `alchemy.run.ts` program itself and each Cloudflare Worker it runs — behind a single endpoint. Messages are tracked and replayable, so a debugger that attaches late still sees the full picture. It works with VSCode, the Bun and Node web debuggers, and Chrome DevTools.

`--inspect-wait` pauses until a debugger connects, and `--inspect-brk` breaks on the first line — handy when the bug is in your infrastructure code rather than your app.

The [debugging guide](/guides/debugging) walks through the VSCode setup: one task, one launch config, and breakpoints work across your infra and runtime code.

## Next.js on Cloudflare

The new `Nextjs` resource deploys a Next.js app to Cloudflare Workers using [OpenNext](https://opennext.js.org/cloudflare):

```ts
import alchemy from "alchemy";
import { Nextjs } from "alchemy/cloudflare";

const app = await alchemy("my-nextjs-app");

export const website = await Nextjs("website");

console.log({ url: website.url });

await app.finalize();
```

It takes the same props as `Website`, and defaults the whole OpenNext pipeline for you: `opennextjs-cloudflare build` as the build command, `next dev` for local dev, the `.open-next` entrypoint and assets, and the `nodejs_compat` compatibility flags. `alchemy create` also gained a Next.js template if you're starting fresh.

Follow the [Next.js guide](/guides/cloudflare-nextjs) to get started.

## PlanetScale Postgres

The PlanetScale provider now supports Postgres databases alongside MySQL. Set `kind: "postgresql"` on a `Database` and use the new `Role` resource to mint scoped credentials:

```ts
import alchemy from "alchemy";
import { Database, Role } from "alchemy/planetscale";

const app = await alchemy("my-app");

const database = await Database("db", {
name: "my-app-db",
clusterSize: "PS_10",
kind: "postgresql",
});

const role = await Role("db-role", {
database,
inheritedRoles: ["postgres"],
});

await app.finalize();
```

Roles support Postgres's built-in permission sets and an optional TTL for short-lived access:

```ts
const reader = await Role("reader", {
database,
inheritedRoles: ["pg_read_all_data", "pg_read_all_settings"],
ttl: 3600, // expires after an hour
});
```

Every role exposes `connectionUrl` and `connectionUrlPooled` as Secrets — pass one to a Cloudflare [Hyperdrive](/providers/cloudflare/hyperdrive) and bind it to a Worker:

```ts
import { Hyperdrive } from "alchemy/cloudflare";

const hyperdrive = await Hyperdrive("db-hyperdrive", {
origin: role.connectionUrl,
});
```

See the [Database](/providers/planetscale/database) and [Role](/providers/planetscale/role) docs for details.

## Also in this release

- **Hyperdrive adoption.** `Hyperdrive` now supports `adopt: true` when the name conflicts with an existing configuration.
- **`alchemy.run.mts`.** The CLI now picks up `.mts` entrypoints.
- **CloudflareStateStore validates `stateToken`.** Missing tokens now fail fast with a clear error instead of confusing API failures later.
- **Fixes.** Worker asset uploads retry with better error handling and use the correct `text/javascript` MIME type; producer-only queue bindings render correctly in `wrangler.json`; `?module` suffixes in WASM imports are handled; and function memoization uses deterministic cache keys.

## Thanks

Thanks to **Michael K**, **John Royal**, **Matt 'TK' Taylor**, and **Rahul Mishra** for contributing to this release. See the [full changelog](https://github.com/alchemy-run/alchemy/compare/v0.64.0...v0.65.1) for everything in the 0.65 line.
Loading
Loading