Merge pull request #34 from sdrpsps/chore/deploy-script

chore: Update deploy script
This commit is contained in:
BeilunYang
2025-03-09 10:08:07 +08:00
committed by GitHub
14 changed files with 1095 additions and 189 deletions

View File

@@ -1,3 +1,10 @@
AUTH_GITHUB_ID = ""
AUTH_GITHUB_SECRET = ""
AUTH_SECRET = ""
AUTH_SECRET = ""
CLOUDFLARE_API_TOKEN = ""
CLOUDFLARE_ACCOUNT_ID = ""
DATABASE_NAME = ""
KV_NAMESPACE_NAME = ""
CUSTOM_DOMAIN = ""

View File

@@ -5,22 +5,6 @@ on:
tags:
- 'v*'
workflow_dispatch:
inputs:
run_migrations:
description: 'Run database migrations'
type: boolean
required: true
default: false
deploy_email_worker:
description: 'Deploy email Worker'
type: boolean
required: true
default: false
deploy_cleanup_worker:
description: 'Deploy cleanup Worker'
type: boolean
required: true
default: false
jobs:
deploy:
@@ -51,112 +35,21 @@ jobs:
- name: Install Dependencies
run: pnpm install --frozen-lockfile
# Check if database migrations have changes
- name: Check migrations changes
id: check_migrations
if: github.event_name == 'push'
- name: Run deploy script
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
PROJECT_NAME: ${{ secrets.PROJECT_NAME }}
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
KV_NAMESPACE_NAME: ${{ secrets.KV_NAMESPACE_NAME }}
CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN }}
AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }}
AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }}
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
run: pnpm dlx tsx scripts/deploy/index.ts
# Clean up
- name: Post deployment cleanup
run: |
if [ -z "${{ steps.previoustag.outputs.tag }}" ]; then
echo "migrations_changed=true" >> $GITHUB_OUTPUT
else
if git diff ${{ steps.previoustag.outputs.tag }}..HEAD --name-only | grep -q "^drizzle/"; then
echo "migrations_changed=true" >> $GITHUB_OUTPUT
else
echo "migrations_changed=false" >> $GITHUB_OUTPUT
fi
fi
# Process configuration files
- name: Process configuration files
run: |
# Process wrangler.example.toml
if [ -f wrangler.example.toml ]; then
cp wrangler.example.toml wrangler.toml
sed -i "s/database_name = \".*\"/database_name = \"${{ secrets.DATABASE_NAME }}\"/" wrangler.toml
sed -i "s/database_id = \".*\"/database_id = \"${{ secrets.DATABASE_ID }}\"/" wrangler.toml
sed -i "s/id = \"\"/id = \"${{ secrets.KV_NAMESPACE_ID }}\"/" wrangler.toml
fi
# Process wrangler.email.example.toml
if [ -f wrangler.email.example.toml ]; then
cp wrangler.email.example.toml wrangler.email.toml
sed -i "s/database_name = \".*\"/database_name = \"${{ secrets.DATABASE_NAME }}\"/" wrangler.email.toml
sed -i "s/database_id = \".*\"/database_id = \"${{ secrets.DATABASE_ID }}\"/" wrangler.email.toml
fi
# Process wrangler.cleanup.example.toml
if [ -f wrangler.cleanup.example.toml ]; then
cp wrangler.cleanup.example.toml wrangler.cleanup.toml
sed -i "s/database_name = \".*\"/database_name = \"${{ secrets.DATABASE_NAME }}\"/" wrangler.cleanup.toml
sed -i "s/database_id = \".*\"/database_id = \"${{ secrets.DATABASE_ID }}\"/" wrangler.cleanup.toml
fi
# Run database migrations if needed
- name: Run database migrations
if: |
github.event_name == 'push' && steps.check_migrations.outputs.migrations_changed == 'true' ||
github.event_name == 'workflow_dispatch' && github.event.inputs.run_migrations == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm db:migrate-remote
# Check if workers have changes
- name: Check workers changes
id: check_changes
if: github.event_name == 'push'
run: |
if [ -z "${{ steps.previoustag.outputs.tag }}" ]; then
# Check email worker and its dependencies
if git ls-files | grep -q -E "workers/email-receiver.ts|app/lib/schema.ts|app/lib/webhook.ts|app/config/webhook.ts"; then
echo "email_worker_changed=true" >> $GITHUB_OUTPUT
else
echo "email_worker_changed=false" >> $GITHUB_OUTPUT
fi
# Check cleanup worker
if git ls-files | grep -q "workers/cleanup.ts"; then
echo "cleanup_worker_changed=true" >> $GITHUB_OUTPUT
else
echo "cleanup_worker_changed=false" >> $GITHUB_OUTPUT
fi
else
# Check email worker and its dependencies changes
if git diff ${{ steps.previoustag.outputs.tag }}..HEAD --name-only | grep -q -E "workers/email-receiver.ts|app/lib/schema.ts|app/lib/webhook.ts|app/config/webhook.ts"; then
echo "email_worker_changed=true" >> $GITHUB_OUTPUT
else
echo "email_worker_changed=false" >> $GITHUB_OUTPUT
fi
# Check cleanup worker changes
if git diff ${{ steps.previoustag.outputs.tag }}..HEAD --name-only | grep -q "workers/cleanup.ts"; then
echo "cleanup_worker_changed=true" >> $GITHUB_OUTPUT
else
echo "cleanup_worker_changed=false" >> $GITHUB_OUTPUT
fi
fi
# Deploy Pages application
- name: Deploy Pages
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm run deploy:pages
# Deploy email worker if changed or manually triggered
- name: Deploy Email Worker
if: |
github.event_name == 'push' && steps.check_changes.outputs.email_worker_changed == 'true' ||
github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_email_worker == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm run deploy:email
# Deploy cleanup worker if changed or manually triggered
- name: Deploy Cleanup Worker
if: |
github.event_name == 'push' && steps.check_changes.outputs.cleanup_worker_changed == 'true' ||
github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_cleanup_worker == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: pnpm run deploy:cleanup
rm -f .env*.*
rm -f wrangler*.json

6
.gitignore vendored
View File

@@ -46,4 +46,8 @@ wrangler.email.toml
wrangler.cleanup.toml
public/workbox-*.js
public/sw.js
public/sw.js
wrangler.json
wrangler.cleanup.json
wrangler.email.json

View File

@@ -8,14 +8,14 @@
"start": "next start",
"lint": "next lint",
"build:pages": "npx @cloudflare/next-on-pages",
"db:migrate-local": "bun run scripts/migrate.ts local",
"db:migrate-remote": "bun run scripts/migrate.ts remote",
"db:migrate-local": "tsx scripts/migrate.ts local",
"db:migrate-remote": "tsx scripts/migrate.ts remote",
"webhook-test-server": "bun run scripts/webhook-test-server.ts",
"generate-test-data": "wrangler dev scripts/generate-test-data.ts",
"dev:cleanup": "wrangler dev --config wrangler.cleanup.toml --test-scheduled",
"dev:cleanup": "wrangler dev --config wrangler.cleanup.json --test-scheduled",
"test:cleanup": "curl http://localhost:8787/__scheduled",
"deploy:email": "wrangler deploy --config wrangler.email.toml",
"deploy:cleanup": "wrangler deploy --config wrangler.cleanup.toml",
"deploy:email": "wrangler deploy --config wrangler.email.json",
"deploy:cleanup": "wrangler deploy --config wrangler.cleanup.json",
"deploy:pages": "npm run build:pages && wrangler pages deploy .vercel/output/static --branch main"
},
"type": "module",
@@ -53,18 +53,20 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20241127.0",
"@iarna/toml": "^3.0.0",
"@types/bun": "^1.1.14",
"@types/next-pwa": "^5.6.9",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"bun": "^1.1.39",
"cloudflare": "^4.1.0",
"dotenv": "^16.4.7",
"drizzle-kit": "^0.28.1",
"eslint": "^8",
"eslint-config-next": "15.0.3",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"tsx": "^4.19.3",
"typescript": "^5",
"vercel": "39.1.1",
"wrangler": "^3.91.0"

480
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
import Cloudflare from "cloudflare";
import "dotenv/config";
const CF_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID!;
const CF_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN;
const PROJECT_NAME = process.env.PROJECT_NAME || "moemail";
const DATABASE_NAME = process.env.DATABASE_NAME || "moemail-db";
const KV_NAMESPACE_NAME = process.env.KV_NAMESPACE_NAME || "moemail-kv";
const client = new Cloudflare({
apiKey: CF_API_TOKEN,
});
export const getPages = async () => {
const projectInfo = await client.pages.projects.get(PROJECT_NAME, {
account_id: CF_ACCOUNT_ID,
});
return projectInfo;
};
export const createPages = async () => {
console.log(`🆕 Creating new Cloudflare Pages project: "${PROJECT_NAME}"`);
const project = await client.pages.projects.create({
account_id: CF_ACCOUNT_ID,
name: PROJECT_NAME,
production_branch: "main",
});
if (CUSTOM_DOMAIN) {
console.log("🔗 Setting pages domain...");
await client.pages.projects.domains.create(PROJECT_NAME, {
account_id: CF_ACCOUNT_ID,
name: CUSTOM_DOMAIN?.split("://")[1],
});
console.log("✅ Pages domain set successfully");
}
console.log("✅ Project created successfully");
return project;
};
export const getDatabase = async () => {
const database = await client.d1.database.get(DATABASE_NAME, {
account_id: CF_ACCOUNT_ID,
});
return database;
};
export const createDatabase = async () => {
console.log(`🆕 Creating new D1 database: "${DATABASE_NAME}"`);
const database = await client.d1.database.create({
account_id: CF_ACCOUNT_ID,
name: DATABASE_NAME,
});
console.log("✅ Database created successfully");
return database;
};
export const getKVNamespaceList = async () => {
const kvNamespaces = [];
for await (const namespace of client.kv.namespaces.list({
account_id: CF_ACCOUNT_ID,
})) {
kvNamespaces.push(namespace);
}
return kvNamespaces;
};
export const createKVNamespace = async () => {
console.log(`🆕 Creating new KV namespace: "${KV_NAMESPACE_NAME}"`);
const kvNamespace = await client.kv.namespaces.create({
account_id: CF_ACCOUNT_ID,
title: KV_NAMESPACE_NAME,
});
console.log("✅ KV namespace created successfully");
return kvNamespace;
};

432
scripts/deploy/index.ts Normal file
View File

@@ -0,0 +1,432 @@
import { NotFoundError } from "cloudflare";
import "dotenv/config";
import { execSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { resolve } from "node:path";
import {
createDatabase,
createKVNamespace,
createPages,
getDatabase,
getKVNamespaceList,
getPages,
} from "./cloudflare";
const PROJECT_NAME = process.env.PROJECT_NAME || "moemail";
const DATABASE_NAME = process.env.DATABASE_NAME || "moemail-db";
const KV_NAMESPACE_NAME = process.env.KV_NAMESPACE_NAME || "moemail-kv";
const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN;
/**
* 验证必要的环境变量
*/
const validateEnvironment = () => {
const requiredEnvVars = ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"];
const missing = requiredEnvVars.filter((varName) => !process.env[varName]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(", ")}`
);
}
};
/**
* 处理JSON配置文件
*/
const setupConfigFile = (examplePath: string, targetPath: string) => {
try {
// 如果目标文件已存在,则跳过
if (existsSync(targetPath)) {
console.log(`✨ Configuration ${targetPath} already exists.`);
return;
}
if (!existsSync(examplePath)) {
console.log(`⚠️ Example file ${examplePath} does not exist, skipping...`);
return;
}
const configContent = readFileSync(examplePath, "utf-8");
const json = JSON.parse(configContent);
// 处理数据库配置
if (json.d1_databases && json.d1_databases.length > 0) {
json.d1_databases[0].database_name = DATABASE_NAME;
}
// 写入配置文件
writeFileSync(targetPath, JSON.stringify(json, null, 2));
console.log(`✅ Configuration ${targetPath} setup successfully.`);
} catch (error) {
console.error(`❌ Failed to setup ${targetPath}:`, error);
throw error;
}
};
/**
* 设置所有Wrangler配置文件
*/
const setupWranglerConfigs = () => {
console.log("🔧 Setting up Wrangler configuration files...");
const configs = [
{ example: "wrangler.example.json", target: "wrangler.json" },
{ example: "wrangler.email.example.json", target: "wrangler.email.json" },
{ example: "wrangler.cleanup.example.json", target: "wrangler.cleanup.json" },
];
// 处理每个配置文件
for (const config of configs) {
setupConfigFile(
resolve(config.example),
resolve(config.target)
);
}
};
/**
* 更新数据库ID到所有配置文件
*/
const updateDatabaseConfig = (dbId: string) => {
console.log(`📝 Updating database ID (${dbId}) in configurations...`);
// 更新所有配置文件
const configFiles = [
"wrangler.json",
"wrangler.email.json",
"wrangler.cleanup.json",
];
for (const filename of configFiles) {
const configPath = resolve(filename);
if (!existsSync(configPath)) continue;
try {
const json = JSON.parse(readFileSync(configPath, "utf-8"));
if (json.d1_databases && json.d1_databases.length > 0) {
json.d1_databases[0].database_id = dbId;
}
writeFileSync(configPath, JSON.stringify(json, null, 2));
console.log(`✅ Updated database ID in ${filename}`);
} catch (error) {
console.error(`❌ Failed to update ${filename}:`, error);
}
}
};
/**
* 更新KV命名空间ID到所有配置文件
*/
const updateKVConfig = (namespaceId: string) => {
console.log(`📝 Updating KV namespace ID (${namespaceId}) in configurations...`);
// KV命名空间只在主wrangler.json中使用
const wranglerPath = resolve("wrangler.json");
if (existsSync(wranglerPath)) {
try {
const json = JSON.parse(readFileSync(wranglerPath, "utf-8"));
if (json.kv_namespaces && json.kv_namespaces.length > 0) {
json.kv_namespaces[0].id = namespaceId;
}
writeFileSync(wranglerPath, JSON.stringify(json, null, 2));
console.log(`✅ Updated KV namespace ID in wrangler.json`);
} catch (error) {
console.error(`❌ Failed to update wrangler.json:`, error);
}
}
};
/**
* 检查并创建数据库
*/
const checkAndCreateDatabase = async () => {
console.log(`🔍 Checking if database "${DATABASE_NAME}" exists...`);
try {
const database = await getDatabase();
if (!database || !database.uuid) {
throw new Error('Database object is missing a valid UUID');
}
updateDatabaseConfig(database.uuid);
console.log(`✅ Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})`);
} catch (error) {
if (error instanceof NotFoundError) {
console.log(`⚠️ Database not found, creating new database...`);
try {
const database = await createDatabase();
if (!database || !database.uuid) {
throw new Error('Database object is missing a valid UUID');
}
updateDatabaseConfig(database.uuid);
console.log(`✅ Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`);
} catch (createError) {
console.error(`❌ Failed to create database:`, createError);
throw createError;
}
} else {
console.error(`❌ An error occurred while checking the database:`, error);
throw error;
}
}
};
/**
* 迁移数据库
*/
const migrateDatabase = () => {
console.log("📝 Migrating remote database...");
try {
execSync("pnpm run db:migrate-remote", { stdio: "inherit" });
console.log("✅ Database migration completed successfully");
} catch (error) {
console.error("❌ Database migration failed:", error);
throw error;
}
};
/**
* 检查并创建KV命名空间
*/
const checkAndCreateKVNamespace = async () => {
console.log(`🔍 Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`);
try {
let namespace;
const namespaceList = await getKVNamespaceList();
namespace = namespaceList.find(ns => ns.title === KV_NAMESPACE_NAME);
if (namespace && namespace.id) {
updateKVConfig(namespace.id);
console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${namespace.id})`);
} else {
console.log("⚠️ KV namespace not found by name, creating new KV namespace...");
namespace = await createKVNamespace();
updateKVConfig(namespace.id);
console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`);
}
} catch (error) {
console.error(`❌ An error occurred while checking the KV namespace:`, error);
throw error;
}
};
/**
* 检查并创建Pages项目
*/
const checkAndCreatePages = async () => {
console.log(`🔍 Checking if project "${PROJECT_NAME}" exists...`);
try {
await getPages();
console.log("✅ Project already exists, proceeding with update...");
} catch (error) {
if (error instanceof NotFoundError) {
console.log("⚠️ Project not found, creating new project...");
const pages = await createPages();
if (!CUSTOM_DOMAIN && pages.subdomain) {
console.log("⚠️ CUSTOM_DOMAIN is empty, using pages default domain...");
console.log("📝 Updating environment variables...");
// 更新环境变量为默认的Pages域名
const appUrl = `https://${pages.subdomain}`;
updateEnvVar("CUSTOM_DOMAIN", appUrl);
}
} else {
console.error(`❌ An error occurred while checking the project:`, error);
throw error;
}
}
};
/**
* 推送Pages密钥
*/
const pushPagesSecret = () => {
console.log("🔐 Pushing environment secrets to Pages...");
try {
// 确保.env文件存在
if (!existsSync(resolve('.env'))) {
setupEnvFile();
}
// 创建一个临时文件,只包含运行时所需的环境变量
const envContent = readFileSync(resolve('.env'), 'utf-8');
const runtimeEnvFile = resolve('.env.runtime');
// 定义运行时所需的环境变量列表
const runtimeEnvVars = ['AUTH_GITHUB_ID', 'AUTH_GITHUB_SECRET', 'AUTH_SECRET'];
// 从.env文件中提取运行时变量
const runtimeEnvContent = envContent
.split('\n')
.filter(line => {
const trimmedLine = line.trim();
// 跳过注释和空行
if (!trimmedLine || trimmedLine.startsWith('#')) return false;
// 检查是否为运行时所需的环境变量
for (const varName of runtimeEnvVars) {
if (line.startsWith(`${varName} =`) || line.startsWith(`${varName}=`)) {
return true;
}
}
return false;
})
.join('\n');
// 写入临时文件
writeFileSync(runtimeEnvFile, runtimeEnvContent);
// 使用临时文件推送secrets
execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" });
// 清理临时文件
execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" });
console.log("✅ Secrets pushed successfully");
} catch (error) {
console.error("❌ Failed to push secrets:", error);
throw error;
}
};
/**
* 部署Pages应用
*/
const deployPages = () => {
console.log("🚧 Deploying to Cloudflare Pages...");
try {
execSync("pnpm run build:pages && pnpm dlx wrangler pages deploy .vercel/output/static --branch main", { stdio: "inherit" });
console.log("✅ Pages deployment completed successfully");
} catch (error) {
console.error("❌ Pages deployment failed:", error);
throw error;
}
};
/**
* 部署Email Worker
*/
const deployEmailWorker = () => {
console.log("🚧 Deploying Email Worker...");
try {
execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { stdio: "inherit" });
console.log("✅ Email Worker deployed successfully");
} catch (error) {
console.error("❌ Email Worker deployment failed:", error);
// 继续执行而不中断
}
};
/**
* 部署Cleanup Worker
*/
const deployCleanupWorker = () => {
console.log("🚧 Deploying Cleanup Worker...");
try {
execSync("pnpm dlx wrangler deploy --config wrangler.cleanup.json", { stdio: "inherit" });
console.log("✅ Cleanup Worker deployed successfully");
} catch (error) {
console.error("❌ Cleanup Worker deployment failed:", error);
// 继续执行而不中断
}
};
/**
* 创建或更新环境变量文件
*/
const setupEnvFile = () => {
console.log("📄 Setting up environment file...");
const envFilePath = resolve(".env");
const envExamplePath = resolve(".env.example");
// 如果.env文件不存在则从.env.example复制创建
if (!existsSync(envFilePath) && existsSync(envExamplePath)) {
console.log("⚠️ .env file does not exist, creating from example...");
// 从示例文件复制
let envContent = readFileSync(envExamplePath, "utf-8");
// 填充当前的环境变量
const envVarMatches = envContent.match(/^([A-Z_]+)\s*=\s*".*?"/gm);
if (envVarMatches) {
for (const match of envVarMatches) {
const varName = match.split("=")[0].trim();
if (process.env[varName]) {
const regex = new RegExp(`${varName}\\s*=\\s*".*?"`, "g");
envContent = envContent.replace(regex, `${varName} = "${process.env[varName]}"`);
}
}
}
writeFileSync(envFilePath, envContent);
console.log("✅ .env file created from example");
} else if (existsSync(envFilePath)) {
console.log("✨ .env file already exists");
} else {
console.error("❌ .env.example file not found!");
throw new Error(".env.example file not found");
}
};
/**
* 更新环境变量
*/
const updateEnvVar = (name: string, value: string) => {
// 首先更新进程环境变量
process.env[name] = value;
// 然后尝试更新.env文件
const envFilePath = resolve(".env");
if (!existsSync(envFilePath)) {
setupEnvFile();
}
let envContent = readFileSync(envFilePath, "utf-8");
const regex = new RegExp(`^${name}\\s*=\\s*".*?"`, "m");
if (envContent.match(regex)) {
envContent = envContent.replace(regex, `${name} = "${value}"`);
} else {
envContent += `\n${name} = "${value}"`;
}
writeFileSync(envFilePath, envContent);
console.log(`✅ Updated ${name} in .env file`);
};
/**
* 主函数
*/
const main = async () => {
try {
console.log("🚀 Starting deployment process...");
validateEnvironment();
setupEnvFile();
setupWranglerConfigs();
await checkAndCreateDatabase();
migrateDatabase();
await checkAndCreateKVNamespace();
await checkAndCreatePages();
pushPagesSecret();
deployPages();
deployEmailWorker();
deployCleanupWorker();
console.log("🎉 Deployment completed successfully");
} catch (error) {
console.error("❌ Deployment failed:", error);
process.exit(1);
}
};
main();

View File

@@ -1,4 +1,3 @@
import { parse } from '@iarna/toml'
import { readFileSync } from 'fs'
import { exec } from 'child_process'
import { promisify } from 'util'
@@ -27,23 +26,23 @@ async function migrate() {
process.exit(1)
}
// Read wrangler.toml
const wranglerPath = join(process.cwd(), 'wrangler.toml')
// Read wrangler.json
const wranglerPath = join(process.cwd(), 'wrangler.json')
let wranglerContent: string
try {
wranglerContent = readFileSync(wranglerPath, 'utf-8')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
console.error('Error: wrangler.toml not found')
console.error('Error: wrangler.json not found')
process.exit(1)
}
// Parse wrangler.toml
const config = parse(wranglerContent) as unknown as WranglerConfig
// Parse wrangler.json
const config = JSON.parse(wranglerContent) as WranglerConfig
if (!config.d1_databases?.[0]?.database_name) {
console.error('Error: Database name not found in wrangler.toml')
console.error('Error: Database name not found in wrangler.json')
process.exit(1)
}
@@ -53,7 +52,7 @@ async function migrate() {
console.log('Generating migrations...')
await execAsync('drizzle-kit generate')
// Apply migrations
// Applying migrations
console.log(`Applying migrations to ${mode} database: ${dbName}`)
await execAsync(`wrangler d1 migrations apply ${dbName} --${mode}`)
@@ -64,4 +63,4 @@ async function migrate() {
}
}
migrate()
migrate()

View File

@@ -0,0 +1,18 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "cleanup-worker",
"main": "workers/cleanup.ts",
"compatibility_date": "2024-03-20",
"compatibility_flags": ["nodejs_compat"],
"triggers": {
"crons": ["0 * * * *"]
},
"d1_databases": [
{
"binding": "DB",
"migrations_dir": "drizzle",
"database_name": "${DATABASE_NAME}",
"database_id": "${DATABASE_ID}"
}
]
}

View File

@@ -1,14 +0,0 @@
name = "cleanup-worker"
main = "workers/cleanup.ts"
compatibility_date = "2024-03-20"
compatibility_flags = ["nodejs_compat"]
# 每 1 小时运行一次
[triggers]
crons = ["0 * * * *"]
[[d1_databases]]
binding = "DB"
migrations_dir = "drizzle"
database_name = ""
database_id = ""

View File

@@ -0,0 +1,15 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "email-receiver-worker",
"compatibility_date": "2024-03-20",
"compatibility_flags": ["nodejs_compat"],
"main": "workers/email-receiver.ts",
"d1_databases": [
{
"binding": "DB",
"migrations_dir": "drizzle",
"database_name": "${DATABASE_NAME}",
"database_id": "${DATABASE_ID}"
}
]
}

View File

@@ -1,11 +0,0 @@
name = "email-receiver-worker"
compatibility_date = "2024-03-20"
compatibility_flags = ["nodejs_compat"]
main = "workers/email-receiver.ts"
[[d1_databases]]
binding = "DB"
migrations_dir = "drizzle"
database_name = ""
database_id = ""

21
wrangler.example.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "moemail",
"compatibility_date": "2024-03-20",
"compatibility_flags": ["nodejs_compat"],
"pages_build_output_dir": ".vercel/output/static",
"d1_databases": [
{
"binding": "DB",
"database_name": "${DATABASE_NAME}",
"database_id": "${DATABASE_ID}",
"migrations_dir": "drizzle"
}
],
"kv_namespaces": [
{
"binding": "SITE_CONFIG",
"id": "${KV_NAMESPACE_ID}"
}
]
}

View File

@@ -1,14 +0,0 @@
name = "moemail"
compatibility_date = "2024-03-20"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
[[d1_databases]]
binding = "DB"
migrations_dir = "drizzle"
database_name = ""
database_id = ""
[[kv_namespaces]]
binding = "SITE_CONFIG"
id = ""