-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathalchemy.run.ts
More file actions
166 lines (150 loc) · 6.07 KB
/
alchemy.run.ts
File metadata and controls
166 lines (150 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* Infrastructure provisioning with **Alchemy** for Cloudflare platform
*
* By default (when running locally) the stage will be your username ($USER, or $USERNAME on Windows).
* We can also specify a stage with the `--stage` flag.
*
* Available stages:
* - `production` (https://kit.devsantara.com) using CloudflareStateStore.
* - `preview-<pr-number>` (https://kit--preview-1.devsantara.com) using CloudflareStateStore.
* - `staging-<name>` (https://kit--staging-feature-xyz.devsantara.com) using CloudflareStateStore.
* - `<other-custom-stage>` (http://localhost:3000) using FileSystemStateStore.
*
* @example
* ```bash
* pnpm alchemy:deploy --stage staging-feature-xyz
* ```
*/
import alchemy, { type Scope } from 'alchemy';
import { D1Database, KVNamespace, TanStackStart } from 'alchemy/cloudflare';
import { GitHubComment } from 'alchemy/github';
import { CloudflareStateStore, FileSystemStateStore } from 'alchemy/state';
import { parse as parseURL } from 'tldts';
import { alchemyEnv } from './src/lib/env/alchemy.ts';
import { serverEnv } from './src/lib/env/server.ts';
const ALCHEMY_SECRET = alchemyEnv.ALCHEMY_SECRET;
const ALCHEMY_STATE_TOKEN = alchemy.secret(alchemyEnv.ALCHEMY_STATE_TOKEN);
const AUTH_SECRET = alchemy.secret(serverEnv.AUTH_SECRET);
const AUTH_GITHUB_CLIENT_ID = alchemy.secret(serverEnv.AUTH_GITHUB_CLIENT_ID);
const AUTH_GITHUB_CLIENT_SECRET = alchemy.secret(
serverEnv.AUTH_GITHUB_CLIENT_SECRET,
);
const AUTH_GOOGLE_CLIENT_ID = alchemy.secret(serverEnv.AUTH_GOOGLE_CLIENT_ID);
const AUTH_GOOGLE_CLIENT_SECRET = alchemy.secret(
serverEnv.AUTH_GOOGLE_CLIENT_SECRET,
);
function isProductionStage(scope: Scope) {
return scope.stage === 'production';
}
function isPreviewStage(scope: Scope) {
return scope.stage.startsWith('preview');
}
function isStagingStage(scope: Scope) {
return scope.stage.startsWith('staging');
}
/**
* Determines the domain(s) to be used for the deployment based on the stage.
* - For production, it returns the main hostname.
* - For preview/staging stages with subdomains (e.g. `kit.devsantara.com`), it returns domains in the format of `<subdomain>--<stage>.<domain>`.
* - For preview/staging stages without subdomains, it returns domains in the format of `<stage>.<domain>`.
* - For other stages (like local development), it returns undefined, allowing for default localhost usage.
*/
function getDomain(scope: Scope) {
const { hostname, subdomain, domain } = parseURL(alchemyEnv.HOSTNAME);
if (isProductionStage(scope)) {
return [hostname ?? alchemyEnv.HOSTNAME];
}
if (isPreviewStage(scope) || isStagingStage(scope)) {
if (subdomain === null || subdomain === '') {
return [`${scope.stage}.${domain}`];
}
return [`${subdomain}--${scope.stage}.${domain}`];
}
return undefined;
}
/**
* Determines the base URL for the application based on the deployment stage.
* - For production, preview, and staging stages, it constructs the URL using the determined domain.
* - For other stages (like local development), it defaults to `http://localhost:3000`.
*/
function getBaseURL(scope: Scope) {
const domains = getDomain(scope);
if (!domains) {
return `http://localhost:3000`;
}
const domain = domains[0];
return `https://${domain}`;
}
/**
* Initialize Alchemy app with appropriate state store based on the deployment stage.
* - For production, preview, and staging stages, it uses CloudflareStateStore for distributed state management.
* - For other stages (like local development), it uses FileSystemStateStore for simplicity.
*/
const app = await alchemy('kit', {
password: ALCHEMY_SECRET,
stateStore: (scope) => {
if (
isProductionStage(scope) ||
isPreviewStage(scope) ||
isStagingStage(scope)
) {
return new CloudflareStateStore(scope, {
scriptName: 'alchemy-state-service',
stateToken: ALCHEMY_STATE_TOKEN,
});
}
return new FileSystemStateStore(scope);
},
});
/** Provision a D1 database for the application */
const database = await D1Database('database', {
adopt: true,
migrationsDir: './src/lib/database/migrations',
migrationsTable: 'drizzle_migrations',
readReplication: {
mode: isProductionStage(app) ? 'auto' : 'disabled',
},
});
/** Provision a KV namespace for key value storage */
const kvStore = await KVNamespace('kv', {
adopt: true,
});
/** Provision a TanStack Start worker for the application */
export const worker = await TanStackStart('website', {
adopt: true,
wrangler: { main: 'src/entry.server.ts' },
observability: isProductionStage(app) ? { enabled: true } : undefined,
url: isProductionStage(app) ? false : true,
domains: getDomain(app),
placement: isProductionStage(app) ? { mode: 'smart' } : undefined,
bindings: {
// Services
DATABASE: database,
KV_STORE: kvStore,
// Environment variables
VITE_PUBLIC_BASE_URL: getBaseURL(app),
AUTH_SECRET: AUTH_SECRET,
AUTH_GITHUB_CLIENT_ID: AUTH_GITHUB_CLIENT_ID,
AUTH_GITHUB_CLIENT_SECRET: AUTH_GITHUB_CLIENT_SECRET,
AUTH_GOOGLE_CLIENT_ID: AUTH_GOOGLE_CLIENT_ID,
AUTH_GOOGLE_CLIENT_SECRET: AUTH_GOOGLE_CLIENT_SECRET,
},
});
console.info({ worker: worker.name, url: getBaseURL(app) });
if (process.env.PULL_REQUEST) {
// If this is a PR, add a comment to the PR with the preview URL
// It will auto-update with each push
await GitHubComment('preview-comment', {
owner: 'devsantara',
repository: 'kit',
issueNumber: Number(process.env.PULL_REQUEST),
body: `## 🚀 Preview Deployment (${process.env.PULL_REQUEST})
Your changes have been deployed to a preview environment:
| Name | Preview URL | Commit | Updated (UTC) |
| :----------------- | :---------------------------------- | :------------------------------------- | :------------------------------------------ |
| **${worker.name}** | [Visit Preview](${getBaseURL(app)}) | ${process.env.GITHUB_SHA?.slice(0, 7)} | ${new Date(worker.updatedAt).toUTCString()} |
---
<sub>🏗️ This comment updates automatically with each push.</sub>`,
});
}
await app.finalize();