Skip to content
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
4 changes: 4 additions & 0 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/workflows/deploy-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,5 @@ jobs:
FLY_IO_API_KEY: ${{ secrets.FLY_API_TOKEN }}
STAGE: ${{ github.ref_name == 'main' && 'production' || github.ref_name == 'develop' && 'staging' || format('pr-{0}', github.event.pull_request.number) }}
working-directory: apps/api
run: bunx alchemy-effect deploy --stage "$STAGE"
run: bunx alchemy deploy --stage "$STAGE"

8 changes: 4 additions & 4 deletions .github/workflows/deploy-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ jobs:
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
# Persist alchemy-effect's LocalState (.alchemy/state/<stack>/<stage>/) across
# Persist alchemy's LocalState (.alchemy/state/<stack>/<stage>/) across
# runners so the destroy job on PR close can find the resources to tear down.
# alchemy-effect currently ships only a filesystem state backend; without this
# alchemy currently ships only a filesystem state backend; without this
# cache, every runner starts blind and orphans Workers.
- name: Restore alchemy state
uses: actions/cache@v4
Expand All @@ -106,7 +106,7 @@ jobs:
SOPS_AGE_KEY: ${{ secrets.SECRETS_AGE_KEY_DEV }}
run: |
set -euo pipefail
bunx alchemy-effect deploy --stage ${{ needs.stage.outputs.stage }} --yes
bunx alchemy deploy --stage ${{ needs.stage.outputs.stage }} --yes
- name: Comment preview URL on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
SOPS_AGE_KEY: ${{ secrets.SECRETS_AGE_KEY_DEV }}
run: |
set -euo pipefail
bunx alchemy-effect destroy --stage ${{ needs.stage.outputs.stage }} --yes
bunx alchemy destroy --stage ${{ needs.stage.outputs.stage }} --yes
- name: Delete cached alchemy state
if: always()
env:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/deploy-web.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ jobs:
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
# Persist alchemy-effect's LocalState (.alchemy/state/<stack>/<stage>/) across
# Persist alchemy's LocalState (.alchemy/state/<stack>/<stage>/) across
# runners so the destroy job on PR close can find the resources to tear down.
# alchemy-effect currently ships only a filesystem state backend; without this
# alchemy currently ships only a filesystem state backend; without this
# cache, every runner starts blind and orphans Workers + Neon projects.
- name: Restore alchemy state
uses: actions/cache@v4
Expand All @@ -103,7 +103,7 @@ jobs:
SOPS_AGE_KEY: ${{ secrets.SECRETS_AGE_KEY_DEV }}
run: |
set -euo pipefail
bunx alchemy-effect deploy --stage ${{ needs.stage.outputs.stage }} --yes
bunx alchemy deploy --stage ${{ needs.stage.outputs.stage }} --yes
- name: Comment preview URL on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
Expand Down Expand Up @@ -140,7 +140,7 @@ jobs:
SOPS_AGE_KEY: ${{ secrets.SECRETS_AGE_KEY_DEV }}
run: |
set -euo pipefail
bunx alchemy-effect destroy --stage ${{ needs.stage.outputs.stage }} --yes
bunx alchemy destroy --stage ${{ needs.stage.outputs.stage }} --yes
- name: Delete cached alchemy state
if: always()
env:
Expand Down
20 changes: 17 additions & 3 deletions apps/api/alchemy.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@
// dance. Same Effect-native pattern as apps/web's domain binding via
// `@distilled.cloud/cloudflare` Workers.

import { loadDeployEnv, resolveDeployStage } from "@stackpanel/infra/lib/deploy";
import {
loadDeployEnv,
resolveDeployStage,
selectStateBackend,
} from "@stackpanel/infra/lib/deploy";
import {
AppCertificatesAcmeCreate,
AppIPAssignmentsList,
} from "@distilled.cloud/fly-io/Operations";
import { CredentialsFromEnv as FlyCredentialsFromEnv } from "@distilled.cloud/fly-io";
import * as DNS from "@distilled.cloud/cloudflare/dns";
import * as Stack from "alchemy-effect/Stack";
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";

Expand Down Expand Up @@ -116,4 +121,13 @@ const providers = Layer.mergeAll(FlyCredentialsFromEnv) as unknown as Layer.Laye
any
>;

export default Stack.make(`${PROJECT}-${SERVICE}`, providers)(program);
export default Alchemy.Stack(
`${PROJECT}-${SERVICE}`,
{
providers,
// dev/PR previews → filesystem state (no Cloudflare creds required);
// staging/prod → shared Cloudflare-hosted state store.
state: selectStateBackend(appEnv),
},
program,
);
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@distilled.cloud/cloudflare": "catalog:",
"@distilled.cloud/fly-io": "catalog:",
"@trpc/server": "catalog:",
"alchemy-effect": "catalog:",
"alchemy": "catalog:",
"effect": "catalog:",
"hono": "catalog:"
},
Expand Down
37 changes: 26 additions & 11 deletions apps/docs/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { loadDeployEnv, resolveDeployStage } from "@stackpanel/infra/lib/deploy";
import { Cloudflare, Output, Stage } from "alchemy-effect";
import * as Stack from "alchemy-effect/Stack";
import {
loadDeployEnv,
resolveDeployStage,
selectStateBackend,
} from "@stackpanel/infra/lib/deploy";
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Output from "alchemy/Output";
import * as Workers from "@distilled.cloud/cloudflare/workers";
import * as Effect from "effect/Effect";

Expand Down Expand Up @@ -29,7 +34,7 @@ const hostnameFor = (stage: string): string =>
stage === "production" ? "docs.stackpanel.com" : `docs.${stage}.stackpanel.com`;

const program = Effect.gen(function* () {
const stage = yield* Stage;
const stage = yield* Alchemy.Stage;

// OpenNext-on-Cloudflare emits the worker entrypoint and assets directory.
// The build is expected to have already run (`bun run build:worker`); this
Expand All @@ -47,12 +52,13 @@ const program = Effect.gen(function* () {
// trailing-slash handling for static MDX routes.
assets: {
directory: ".open-next/assets",
// OpenNext static incremental cache lives under `.open-next/cache`; preview
// copies it into assets, but CI `build:worker` does not. Mount the cache
// tree at the URL prefix OpenNext expects (`alchemy-effect` asset sources).
sources: [
{ directory: ".open-next/cache", prefix: "cdn-cgi/_next_cache" },
],
// TODO(stackpanel): re-enable the `.open-next/cache` overlay once
// `alchemy@2` natively supports `AssetsProps.sources` (or vendor a
// v2-compatible patch). The 0.12.x vendored overlay was removed during
// the alchemy-effect → alchemy@2 rename; see follow-up bd issue.
// Without it, OpenNext incremental cache misses for `cdn-cgi/_next_cache`
// paths fall back to ISR revalidation — degraded cache hit rate, not
// a hard breakage.
config: {
notFoundHandling: "none",
htmlHandling: "auto-trailing-slash",
Expand Down Expand Up @@ -113,4 +119,13 @@ const program = Effect.gen(function* () {
return { url };
});

export default Stack.make(`${PROJECT}-${SERVICE}`, Cloudflare.providers())(program);
export default Alchemy.Stack(
`${PROJECT}-${SERVICE}`,
{
providers: Cloudflare.providers(),
// dev/PR previews → filesystem state (cached across CI runs);
// staging/prod → shared Cloudflare-hosted state store.
state: selectStateBackend(appEnv),
},
program,
);
3 changes: 2 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"alchemy-effect": "catalog:",
"alchemy": "catalog:",
"effect": "catalog:",
"mdast-util-mdx-jsx": "^3.2.0",
"postcss": "^8.5.6",
"tailwindcss": "catalog:",
Expand Down
24 changes: 19 additions & 5 deletions apps/web/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { loadDeployEnv, resolveDeployStage } from "@stackpanel/infra/lib/deploy";
import {
loadDeployEnv,
resolveDeployStage,
selectStateBackend,
} from "@stackpanel/infra/lib/deploy";
import { NeonProject, neonProviders } from "@stackpanel/infra/resources/neon";
import { Cloudflare, Output, Stage } from "alchemy-effect";
import * as Stack from "alchemy-effect/Stack";
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Output from "alchemy/Output";
import * as Workers from "@distilled.cloud/cloudflare/workers";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
Expand All @@ -23,7 +28,7 @@ await loadDeployEnv(SERVICE, appEnv);
const STACKPANEL_ZONE = "d34628a3ab639230ff1f6dc1eb640eec";

const program = Effect.gen(function* () {
const stage = yield* Stage;
const stage = yield* Alchemy.Stage;

const db = yield* NeonProject("postgres", {
name: `${PROJECT}-${stage}`,
Expand Down Expand Up @@ -103,4 +108,13 @@ const providers = Layer.mergeAll(
neonProviders(),
) as Layer.Layer<any, never, any>;

export default Stack.make(`${PROJECT}-${SERVICE}`, providers)(program);
export default Alchemy.Stack(
`${PROJECT}-${SERVICE}`,
{
providers,
// dev/PR previews → filesystem state (cached across CI runs);
// staging/prod → shared Cloudflare-hosted state store.
state: selectStateBackend(appEnv),
},
program,
);
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "^5.0.4",
"@vitest/coverage-v8": "^4.0.16",
"alchemy-effect": "catalog:",
"alchemy": "catalog:",
"babel-plugin-react-compiler": "^1.0.0",
"happy-dom": "^20.1.0",
"jsdom": "^26.0.0",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Cloudflare from "alchemy-effect/Cloudflare"
import * as D1 from "alchemy-effect/Cloudflare/D1"
import * as Cloudflare from "alchemy/Cloudflare"
import * as D1 from "alchemy/Cloudflare/D1"
import * as Effect from "effect/Effect"
import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest"
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"
Expand Down
Loading
Loading