From 522ab282b3f9563dc2c1b0ce5a0cf21417b60d2d Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sat, 30 May 2026 18:24:23 +0800 Subject: [PATCH 01/20] Update deploy.yml --- .github/workflows/deploy.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e29c58ae..07313aad 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,13 +12,12 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 # Get full git history for checking file changes + fetch-depth: 0 - name: Get previous tag id: previoustag if: github.event_name == 'push' - run: | - echo "tag=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_OUTPUT + run: echo "tag=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_OUTPUT continue-on-error: true - name: Setup pnpm @@ -35,6 +34,28 @@ jobs: - name: Install Dependencies run: pnpm install --frozen-lockfile + - name: Setup D1 Migrations + run: | + # 安装 wrangler@4 作为开发依赖 + pnpm add -D wrangler@4 + + # 创建 migrations 目录 + mkdir -p migrations + + # 如果没有迁移文件,创建一个初始示例(根据你的实际 schema 修改) + if [ ! "$(ls -A migrations 2>/dev/null)" ]; then + cat > migrations/0001_init.sql << 'EOF' +-- Initial schema for your D1 database +-- Replace with your actual CREATE TABLE statements +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT +); +-- Add other tables as needed +EOF + echo "Created initial migration file at migrations/0001_init.sql" + fi + - name: Run deploy script env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -52,8 +73,5 @@ jobs: AUTH_SECRET: ${{ secrets.AUTH_SECRET }} run: pnpm dlx tsx scripts/deploy/index.ts - # Clean up - name: Post deployment cleanup - run: | - rm -f .env*.* - rm -f wrangler*.json + run: rm -f .env*.* wrangler*.json From c1f192f3d733d2e119b2a3736d981b933fc3b545 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sat, 30 May 2026 18:28:11 +0800 Subject: [PATCH 02/20] Update index.ts --- scripts/deploy/index.ts | 116 +++++++++++++++------------------------- 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 79a0316c..671b9d93 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -1,7 +1,7 @@ import { NotFoundError } from "cloudflare"; import "dotenv/config"; import { execSync } from "node:child_process"; -import { readFileSync, writeFileSync, existsSync } from "node:fs"; +import { existsSync, mkdirSync, readdirSync, writeFileSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; import { createDatabase, @@ -37,7 +37,6 @@ const validateEnvironment = () => { */ const setupConfigFile = (examplePath: string, targetPath: string) => { try { - // 如果目标文件已存在,则跳过 if (existsSync(targetPath)) { console.log(`✨ Configuration ${targetPath} already exists.`); return; @@ -51,10 +50,8 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { const configContent = readFileSync(examplePath, "utf-8"); const json = JSON.parse(configContent); - // 处理自定义项目名称 if (PROJECT_NAME !== "moemail") { const wranglerFileName = targetPath.split("/").at(-1); - switch (wranglerFileName) { case "wrangler.json": json.name = PROJECT_NAME; @@ -70,12 +67,10 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { } } - // 处理数据库配置 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) { @@ -96,12 +91,8 @@ const setupWranglerConfigs = () => { { example: "wrangler.cleanup.example.json", target: "wrangler.cleanup.json" }, ]; - // 处理每个配置文件 for (const config of configs) { - setupConfigFile( - resolve(config.example), - resolve(config.target) - ); + setupConfigFile(resolve(config.example), resolve(config.target)); } }; @@ -111,12 +102,7 @@ const setupWranglerConfigs = () => { const updateDatabaseConfig = (dbId: string) => { console.log(`📝 Updating database ID (${dbId}) in configurations...`); - // 更新所有配置文件 - const configFiles = [ - "wrangler.json", - "wrangler.email.json", - "wrangler.cleanup.json", - ]; + const configFiles = ["wrangler.json", "wrangler.email.json", "wrangler.cleanup.json"]; for (const filename of configFiles) { const configPath = resolve(filename); @@ -141,7 +127,6 @@ const updateDatabaseConfig = (dbId: string) => { 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 { @@ -165,11 +150,9 @@ const checkAndCreateDatabase = async () => { 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) { @@ -177,11 +160,9 @@ const checkAndCreateDatabase = async () => { 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) { @@ -196,12 +177,38 @@ const checkAndCreateDatabase = async () => { }; /** - * 迁移数据库 + * 迁移数据库(修复版:自动创建 migrations 目录和初始文件,使用正确的一行命令) */ const migrateDatabase = () => { + console.log("📝 Preparing D1 migrations..."); + + // 1. 确保 migrations 目录存在 + const migrationsDir = resolve("migrations"); + if (!existsSync(migrationsDir)) { + mkdirSync(migrationsDir, { recursive: true }); + console.log(`✅ Created migrations directory: ${migrationsDir}`); + } + + // 2. 如果没有迁移文件,创建一个初始占位文件(可根据实际 schema 修改) + const sqlFiles = readdirSync(migrationsDir).filter(f => f.endsWith(".sql")); + if (sqlFiles.length === 0) { + const initSqlPath = resolve(migrationsDir, "0001_init.sql"); + const initSql = `-- Initial schema for D1 database +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT +); +-- 在此添加你的其他表结构 +`; + writeFileSync(initSqlPath, initSql); + console.log(`✅ Created initial migration file: ${initSqlPath}`); + } + + // 3. 执行迁移(确保命令在一行,没有换行符) console.log("📝 Migrating remote database..."); + const cmd = `npx wrangler d1 migrations apply ${DATABASE_NAME} --remote`; try { - execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); + execSync(cmd, { stdio: "inherit" }); console.log("✅ Database migration completed successfully"); } catch (error) { console.error("❌ Database migration failed:", error); @@ -223,7 +230,6 @@ const checkAndCreateKVNamespace = async () => { try { let namespace; - const namespaceList = await getKVNamespaceList(); namespace = namespaceList.find(ns => ns.title === KV_NAMESPACE_NAME); @@ -255,12 +261,9 @@ const checkAndCreatePages = async () => { 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); } @@ -277,71 +280,49 @@ const checkAndCreatePages = async () => { const pushPagesSecret = () => { console.log("🔐 Pushing environment secrets to Pages..."); - // 定义运行时所需的环境变量列表 const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', + 'AUTH_GITHUB_ID', + 'AUTH_GITHUB_SECRET', + 'AUTH_GOOGLE_ID', + 'AUTH_GOOGLE_SECRET', 'AUTH_SECRET' ]; try { - // 确保.env文件存在 if (!existsSync(resolve('.env'))) { setupEnvFile(); } - // 读取.env文件内容 const envContent = readFileSync(resolve('.env'), 'utf-8'); - - // 解析环境变量为对象 const secrets: Record = {}; - + envContent.split('\n').forEach(line => { const trimmedLine = line.trim(); - - // 跳过注释和空行 - if (!trimmedLine || trimmedLine.startsWith('#')) { - return; - } - - // 解析键值对 + if (!trimmedLine || trimmedLine.startsWith('#')) return; + const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) { - return; - } - + if (equalIndex === -1) return; + const key = trimmedLine.substring(0, equalIndex).trim(); let value = trimmedLine.substring(equalIndex + 1).trim(); - - // 移除引号 value = value.replace(/^["']|["']$/g, ''); - - // 只保留运行时所需的环境变量,且值不为空 + if (runtimeEnvVars.includes(key) && value.length > 0) { secrets[key] = value; } }); - // 检查是否有需要推送的secrets if (Object.keys(secrets).length === 0) { console.log("⚠️ No runtime secrets found to push"); return; } - // 创建JSON格式的临时文件 const runtimeEnvFile = resolve('.env.runtime.json'); writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - // 使用临时文件推送secrets - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { - stdio: "inherit" - }); + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); - // 清理临时文件 if (existsSync(runtimeEnvFile)) { execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); } @@ -349,8 +330,6 @@ const pushPagesSecret = () => { console.log("✅ Secrets pushed successfully"); } catch (error) { console.error("❌ Failed to push secrets:", error); - - // 确保清理临时文件 const runtimeEnvFile = resolve('.env.runtime.json'); if (existsSync(runtimeEnvFile)) { try { @@ -359,7 +338,6 @@ const pushPagesSecret = () => { console.error("⚠️ Failed to cleanup temporary file:", cleanupError); } } - throw error; } }; @@ -388,7 +366,6 @@ const deployEmailWorker = () => { console.log("✅ Email Worker deployed successfully"); } catch (error) { console.error("❌ Email Worker deployment failed:", error); - // 继续执行而不中断 } }; @@ -402,7 +379,6 @@ const deployCleanupWorker = () => { console.log("✅ Cleanup Worker deployed successfully"); } catch (error) { console.error("❌ Cleanup Worker deployment failed:", error); - // 继续执行而不中断 } }; @@ -414,14 +390,10 @@ const setupEnvFile = () => { 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) { @@ -447,10 +419,8 @@ const setupEnvFile = () => { * 更新环境变量 */ const updateEnvVar = (name: string, value: string) => { - // 首先更新进程环境变量 process.env[name] = value; - // 然后尝试更新.env文件 const envFilePath = resolve(".env"); if (!existsSync(envFilePath)) { setupEnvFile(); @@ -480,7 +450,7 @@ const main = async () => { setupEnvFile(); setupWranglerConfigs(); await checkAndCreateDatabase(); - migrateDatabase(); + migrateDatabase(); // <-- 使用修复后的迁移函数 await checkAndCreateKVNamespace(); await checkAndCreatePages(); pushPagesSecret(); From 9954839957793aeea814d5ff0f07cb1ad87b4eac Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sat, 30 May 2026 18:29:32 +0800 Subject: [PATCH 03/20] Update deploy.yml --- .github/workflows/deploy.yml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 07313aad..a7d58f45 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,26 +34,21 @@ jobs: - name: Install Dependencies run: pnpm install --frozen-lockfile - - name: Setup D1 Migrations + - name: Upgrade Wrangler & Prepare Migrations run: | # 安装 wrangler@4 作为开发依赖 pnpm add -D wrangler@4 - # 创建 migrations 目录 mkdir -p migrations - - # 如果没有迁移文件,创建一个初始示例(根据你的实际 schema 修改) + # 如果没有迁移文件,创建一个占位(避免 wrangler 报错) if [ ! "$(ls -A migrations 2>/dev/null)" ]; then - cat > migrations/0001_init.sql << 'EOF' --- Initial schema for your D1 database --- Replace with your actual CREATE TABLE statements -CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - name TEXT -); --- Add other tables as needed -EOF - echo "Created initial migration file at migrations/0001_init.sql" + echo "-- Initial schema for D1 database" > migrations/0001_init.sql + echo "CREATE TABLE IF NOT EXISTS users (" >> migrations/0001_init.sql + echo " id INTEGER PRIMARY KEY," >> migrations/0001_init.sql + echo " name TEXT" >> migrations/0001_init.sql + echo ");" >> migrations/0001_init.sql + echo "-- 在此添加你的其他表结构" >> migrations/0001_init.sql + echo "Created initial migration file." fi - name: Run deploy script From e087d9c62745ad1bce720a4aa7316d72033778d6 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sat, 30 May 2026 18:55:33 +0800 Subject: [PATCH 04/20] Update index.ts --- scripts/deploy/index.ts | 116 +++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 43 deletions(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 671b9d93..79a0316c 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -1,7 +1,7 @@ import { NotFoundError } from "cloudflare"; import "dotenv/config"; import { execSync } from "node:child_process"; -import { existsSync, mkdirSync, readdirSync, writeFileSync, readFileSync } from "node:fs"; +import { readFileSync, writeFileSync, existsSync } from "node:fs"; import { resolve } from "node:path"; import { createDatabase, @@ -37,6 +37,7 @@ const validateEnvironment = () => { */ const setupConfigFile = (examplePath: string, targetPath: string) => { try { + // 如果目标文件已存在,则跳过 if (existsSync(targetPath)) { console.log(`✨ Configuration ${targetPath} already exists.`); return; @@ -50,8 +51,10 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { const configContent = readFileSync(examplePath, "utf-8"); const json = JSON.parse(configContent); + // 处理自定义项目名称 if (PROJECT_NAME !== "moemail") { const wranglerFileName = targetPath.split("/").at(-1); + switch (wranglerFileName) { case "wrangler.json": json.name = PROJECT_NAME; @@ -67,10 +70,12 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { } } + // 处理数据库配置 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) { @@ -91,8 +96,12 @@ const setupWranglerConfigs = () => { { example: "wrangler.cleanup.example.json", target: "wrangler.cleanup.json" }, ]; + // 处理每个配置文件 for (const config of configs) { - setupConfigFile(resolve(config.example), resolve(config.target)); + setupConfigFile( + resolve(config.example), + resolve(config.target) + ); } }; @@ -102,7 +111,12 @@ const setupWranglerConfigs = () => { const updateDatabaseConfig = (dbId: string) => { console.log(`📝 Updating database ID (${dbId}) in configurations...`); - const configFiles = ["wrangler.json", "wrangler.email.json", "wrangler.cleanup.json"]; + // 更新所有配置文件 + const configFiles = [ + "wrangler.json", + "wrangler.email.json", + "wrangler.cleanup.json", + ]; for (const filename of configFiles) { const configPath = resolve(filename); @@ -127,6 +141,7 @@ const updateDatabaseConfig = (dbId: string) => { 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 { @@ -150,9 +165,11 @@ const checkAndCreateDatabase = async () => { 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) { @@ -160,9 +177,11 @@ const checkAndCreateDatabase = async () => { 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) { @@ -177,38 +196,12 @@ const checkAndCreateDatabase = async () => { }; /** - * 迁移数据库(修复版:自动创建 migrations 目录和初始文件,使用正确的一行命令) + * 迁移数据库 */ const migrateDatabase = () => { - console.log("📝 Preparing D1 migrations..."); - - // 1. 确保 migrations 目录存在 - const migrationsDir = resolve("migrations"); - if (!existsSync(migrationsDir)) { - mkdirSync(migrationsDir, { recursive: true }); - console.log(`✅ Created migrations directory: ${migrationsDir}`); - } - - // 2. 如果没有迁移文件,创建一个初始占位文件(可根据实际 schema 修改) - const sqlFiles = readdirSync(migrationsDir).filter(f => f.endsWith(".sql")); - if (sqlFiles.length === 0) { - const initSqlPath = resolve(migrationsDir, "0001_init.sql"); - const initSql = `-- Initial schema for D1 database -CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - name TEXT -); --- 在此添加你的其他表结构 -`; - writeFileSync(initSqlPath, initSql); - console.log(`✅ Created initial migration file: ${initSqlPath}`); - } - - // 3. 执行迁移(确保命令在一行,没有换行符) console.log("📝 Migrating remote database..."); - const cmd = `npx wrangler d1 migrations apply ${DATABASE_NAME} --remote`; try { - execSync(cmd, { stdio: "inherit" }); + execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); console.log("✅ Database migration completed successfully"); } catch (error) { console.error("❌ Database migration failed:", error); @@ -230,6 +223,7 @@ const checkAndCreateKVNamespace = async () => { try { let namespace; + const namespaceList = await getKVNamespaceList(); namespace = namespaceList.find(ns => ns.title === KV_NAMESPACE_NAME); @@ -261,9 +255,12 @@ const checkAndCreatePages = async () => { 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); } @@ -280,49 +277,71 @@ const checkAndCreatePages = async () => { const pushPagesSecret = () => { console.log("🔐 Pushing environment secrets to Pages..."); + // 定义运行时所需的环境变量列表 const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', + 'AUTH_GITHUB_ID', + 'AUTH_GITHUB_SECRET', + 'AUTH_GOOGLE_ID', + 'AUTH_GOOGLE_SECRET', 'AUTH_SECRET' ]; try { + // 确保.env文件存在 if (!existsSync(resolve('.env'))) { setupEnvFile(); } + // 读取.env文件内容 const envContent = readFileSync(resolve('.env'), 'utf-8'); + + // 解析环境变量为对象 const secrets: Record = {}; - + envContent.split('\n').forEach(line => { const trimmedLine = line.trim(); - if (!trimmedLine || trimmedLine.startsWith('#')) return; - + + // 跳过注释和空行 + if (!trimmedLine || trimmedLine.startsWith('#')) { + return; + } + + // 解析键值对 const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) return; - + if (equalIndex === -1) { + return; + } + const key = trimmedLine.substring(0, equalIndex).trim(); let value = trimmedLine.substring(equalIndex + 1).trim(); + + // 移除引号 value = value.replace(/^["']|["']$/g, ''); - + + // 只保留运行时所需的环境变量,且值不为空 if (runtimeEnvVars.includes(key) && value.length > 0) { secrets[key] = value; } }); + // 检查是否有需要推送的secrets if (Object.keys(secrets).length === 0) { console.log("⚠️ No runtime secrets found to push"); return; } + // 创建JSON格式的临时文件 const runtimeEnvFile = resolve('.env.runtime.json'); writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); + // 使用临时文件推送secrets + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { + stdio: "inherit" + }); + // 清理临时文件 if (existsSync(runtimeEnvFile)) { execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); } @@ -330,6 +349,8 @@ const pushPagesSecret = () => { console.log("✅ Secrets pushed successfully"); } catch (error) { console.error("❌ Failed to push secrets:", error); + + // 确保清理临时文件 const runtimeEnvFile = resolve('.env.runtime.json'); if (existsSync(runtimeEnvFile)) { try { @@ -338,6 +359,7 @@ const pushPagesSecret = () => { console.error("⚠️ Failed to cleanup temporary file:", cleanupError); } } + throw error; } }; @@ -366,6 +388,7 @@ const deployEmailWorker = () => { console.log("✅ Email Worker deployed successfully"); } catch (error) { console.error("❌ Email Worker deployment failed:", error); + // 继续执行而不中断 } }; @@ -379,6 +402,7 @@ const deployCleanupWorker = () => { console.log("✅ Cleanup Worker deployed successfully"); } catch (error) { console.error("❌ Cleanup Worker deployment failed:", error); + // 继续执行而不中断 } }; @@ -390,10 +414,14 @@ const setupEnvFile = () => { 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) { @@ -419,8 +447,10 @@ const setupEnvFile = () => { * 更新环境变量 */ const updateEnvVar = (name: string, value: string) => { + // 首先更新进程环境变量 process.env[name] = value; + // 然后尝试更新.env文件 const envFilePath = resolve(".env"); if (!existsSync(envFilePath)) { setupEnvFile(); @@ -450,7 +480,7 @@ const main = async () => { setupEnvFile(); setupWranglerConfigs(); await checkAndCreateDatabase(); - migrateDatabase(); // <-- 使用修复后的迁移函数 + migrateDatabase(); await checkAndCreateKVNamespace(); await checkAndCreatePages(); pushPagesSecret(); From e54bb1f7b6d9ff75999a8c87b20032825af317e9 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sat, 30 May 2026 19:00:02 +0800 Subject: [PATCH 05/20] Update deploy.yml --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a7d58f45..7d3c0185 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,6 +36,7 @@ jobs: - name: Upgrade Wrangler & Prepare Migrations run: | + pnpm add -D @cloudflare/next-on-pages@latest # 安装 wrangler@4 作为开发依赖 pnpm add -D wrangler@4 # 创建 migrations 目录 From 20b3025651330c872b5a13db6e7d9fe85adb1e8c Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sat, 30 May 2026 19:03:28 +0800 Subject: [PATCH 06/20] Update wrangler.example.json --- wrangler.example.json | 1 + 1 file changed, 1 insertion(+) diff --git a/wrangler.example.json b/wrangler.example.json index a34d017e..8e6ce8f2 100644 --- a/wrangler.example.json +++ b/wrangler.example.json @@ -18,4 +18,5 @@ "id": "${KV_NAMESPACE_ID}" } ] + "pages_build_output_dir": ".vercel/output/static" } From 7adeb4c1ec9872b83e7f80d48b3a28f0c06926d2 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:00:50 +0800 Subject: [PATCH 07/20] Update index.ts --- scripts/deploy/index.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 79a0316c..4ee4b5ab 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -18,17 +18,23 @@ const KV_NAMESPACE_NAME = process.env.KV_NAMESPACE_NAME || "moemail-kv"; const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID; -/** - * 验证必要的环境变量 - */ -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(", ")}` - ); +const deployPages = () => { + console.log("🚧 Deploying to Cloudflare Pages..."); + try { + const output = execSync("pnpm run deploy:pages", { + stdio: "pipe", + encoding: "utf-8", + env: process.env, // 正常传递,不会打印 + }); + console.log(output); + console.log("✅ Pages deployment completed successfully"); + } catch (error: any) { + // 只打印命令的 stdout/stderr,这些通常不包含 secret(除非你刻意输出) + console.error("❌ Pages deployment failed:"); + console.error("stdout:", error.stdout?.toString()); + console.error("stderr:", error.stderr?.toString()); + console.error("exit code:", error.status); + throw error; } }; From 61130655a986f2543189ab1b65f37831cb66f4f9 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:05:56 +0800 Subject: [PATCH 08/20] Update index.ts --- scripts/deploy/index.ts | 309 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 295 insertions(+), 14 deletions(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 4ee4b5ab..bcc0daba 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -18,24 +18,305 @@ const KV_NAMESPACE_NAME = process.env.KV_NAMESPACE_NAME || "moemail-kv"; const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID; -const deployPages = () => { - console.log("🚧 Deploying to Cloudflare Pages..."); +/** + * 验证必要的环境变量 + */ +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 (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + switch (wranglerFileName) { + case "wrangler.json": + json.name = PROJECT_NAME; + break; + case "wrangler.email.json": + json.name = `${PROJECT_NAME}-email-receiver-worker`; + break; + case "wrangler.cleanup.json": + json.name = `${PROJECT_NAME}-cleanup-worker`; + break; + default: + break; + } + } + + // 处理数据库配置 + 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...`); + 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...`); + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + 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..."); + const appUrl = `https://${pages.subdomain}`; + updateEnvVar("CUSTOM_DOMAIN", appUrl); + } + } else { + console.error(`❌ An error occurred while checking the project:`, error); + throw error; + } + } +}; + +/** + * 推送Pages密钥(在 CI 环境中暂时禁用,以避免缺少 .env.example) + */ +const pushPagesSecret = () => { + console.log("⚠️ pushPagesSecret is temporarily disabled in CI environment."); + console.log("🔐 To push secrets, please run this script locally with .env file."); + // 原有的实现被注释,如需启用请取消注释并确保 .env.example 存在 + /* + console.log("🔐 Pushing environment secrets to Pages..."); + const runtimeEnvVars = [ + 'AUTH_GITHUB_ID', + 'AUTH_GITHUB_SECRET', + 'AUTH_GOOGLE_ID', + 'AUTH_GOOGLE_SECRET', + 'AUTH_SECRET' + ]; try { - const output = execSync("pnpm run deploy:pages", { - stdio: "pipe", - encoding: "utf-8", - env: process.env, // 正常传递,不会打印 + if (!existsSync(resolve('.env'))) { + setupEnvFile(); + } + const envContent = readFileSync(resolve('.env'), 'utf-8'); + const secrets: Record = {}; + envContent.split('\n').forEach(line => { + const trimmedLine = line.trim(); + if (!trimmedLine || trimmedLine.startsWith('#')) return; + const equalIndex = trimmedLine.indexOf('='); + if (equalIndex === -1) return; + const key = trimmedLine.substring(0, equalIndex).trim(); + let value = trimmedLine.substring(equalIndex + 1).trim(); + value = value.replace(/^["']|["']$/g, ''); + if (runtimeEnvVars.includes(key) && value.length > 0) { + secrets[key] = value; + } }); - console.log(output); - console.log("✅ Pages deployment completed successfully"); - } catch (error: any) { - // 只打印命令的 stdout/stderr,这些通常不包含 secret(除非你刻意输出) - console.error("❌ Pages deployment failed:"); - console.error("stdout:", error.stdout?.toString()); - console.error("stderr:", error.stderr?.toString()); - console.error("exit code:", error.status); + if (Object.keys(secrets).length === 0) { + console.log("⚠️ No runtime secrets found to push"); + return; + } + const runtimeEnvFile = resolve('.env.runtime.json'); + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); + if (existsSync(runtimeEnvFile)) { + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + } + console.log("✅ Secrets pushed successfully"); + } catch (error) { + console.error("❌ Failed to push secrets:", error); + const runtimeEnvFile = resolve('.env.runtime.json'); + if (existsSync(runtimeEnvFile)) { + try { + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + } catch (cleanupError) { + console.error("⚠️ Failed to cleanup temporary file:", cleanupError); + } + } throw error; } + */ +}; + +/** + * 增强版部署Pages应用(可捕获详细错误输出) + * throw error; + } }; /** From a4e1da803a165b8f6bafb275752ca27a792c50d2 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:11:12 +0800 Subject: [PATCH 09/20] Update index.ts --- scripts/deploy/index.ts | 320 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index bcc0daba..b1e0b2e1 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -51,6 +51,326 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { const configContent = readFileSync(examplePath, "utf-8"); const json = JSON.parse(configContent); + // 处理自定义项目名称 + if (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + + switch (wranglerFileName) { + case "wrangler.json": + json.name = PROJECT_NAME; + break; + case "wrangler.email.json": + json.name = `${PROJECT_NAME}-email-receiver-worker`; + break; + case "wrangler.cleanup.json": + json.name = `${PROJECT_NAME}-cleanup-worker`; + break; + default: + break; + } + } + + // 处理数据库配置 + 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...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + 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..."); + + // 定义运行时所需的环境变量列表 + const runtimeEnvVars = [ + 'AUTH_GITHUB_ID', + 'AUTH_GITHUB_SECRET', + 'AUTH_GOOGLE_ID', + 'AUTH_GOOGLE_SECRET', + 'AUTH_SECRET' + ]; + + try { + // 确保.env文件存在 + if (!existsSync(resolve('.env'))) { + setupEnvFile(); + } + + // 读取.env文件内容 + const envContent = readFileSync(resolve('.env'), 'utf-8'); + + // 解析环境变量为对象 + const secrets: Record = {}; + + envContent.split('\n').forEach(line => { + const trimmedLine = line.trim(); + + // 跳过注释和空行 + if (!trimmedLine || trimmedLine.startsWith('#')) { + return; + } + + // 解析键值对 + const equalIndex = trimmedLine.indexOf('='); + if (equalIndex === -1) { + return; + } + + const key = trimmedLine.substring(0, equalIndex).trim(); + let value = trimmedLine.substring(equalIndex + 1).trim(); + + // 移除引号 + value = value.replace(/^["']|["']$/g, ''); + + // 只保留运行时所需的环境变量,且值不为空 + if (runtimeEnvVars.includes(key) && value.length > 0) { + secrets[key] = value; + } + }); + + // 检查是否有需要推送的secrets + if (Object.keys(secrets).length === 0) { + console.log("⚠️ No runtime secrets found to push"); + return; + } + + // 创建JSON格式的临时文件 + const runtimeEnvFile = resolve('.env.runtime.json'); + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + + console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); + + // 使用临时文件推送secrets + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { + stdio: "inherit" + }); + + // 清理临时文件 + if (existsSync(runtimeEnvFile)) { + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + } + + console.log("✅ Secrets pushed successfully"); + } catch (error) { + console.error("❌ Failed to push secrets:", error); + + // 确保清理临时文件 + const runtimeEnvFile = resolve('.env.runtime.json'); + if (existsSync(runtimeEnvFile)) { + try { + execSync(`rm ${runtimeEnvFile}` */ +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 (PROJECT_NAME !== "moemail") { const wranglerFileName = targetPath.split("/").at(-1); From 6f87b2746f6268340ab843f11c438d9a0b417092 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:12:17 +0800 Subject: [PATCH 10/20] . --- wrangler.example.json | 1 - 1 file changed, 1 deletion(-) diff --git a/wrangler.example.json b/wrangler.example.json index 8e6ce8f2..a34d017e 100644 --- a/wrangler.example.json +++ b/wrangler.example.json @@ -18,5 +18,4 @@ "id": "${KV_NAMESPACE_ID}" } ] - "pages_build_output_dir": ".vercel/output/static" } From b401f6e8dcf8a5acb13be0da4cd8fccc8387729b Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:15:16 +0800 Subject: [PATCH 11/20] Update deploy.yml --- .github/workflows/deploy.yml | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7d3c0185..e29c58ae 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,12 +12,13 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 0 # Get full git history for checking file changes - name: Get previous tag id: previoustag if: github.event_name == 'push' - run: echo "tag=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_OUTPUT + run: | + echo "tag=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_OUTPUT continue-on-error: true - name: Setup pnpm @@ -34,24 +35,6 @@ jobs: - name: Install Dependencies run: pnpm install --frozen-lockfile - - name: Upgrade Wrangler & Prepare Migrations - run: | - pnpm add -D @cloudflare/next-on-pages@latest - # 安装 wrangler@4 作为开发依赖 - pnpm add -D wrangler@4 - # 创建 migrations 目录 - mkdir -p migrations - # 如果没有迁移文件,创建一个占位(避免 wrangler 报错) - if [ ! "$(ls -A migrations 2>/dev/null)" ]; then - echo "-- Initial schema for D1 database" > migrations/0001_init.sql - echo "CREATE TABLE IF NOT EXISTS users (" >> migrations/0001_init.sql - echo " id INTEGER PRIMARY KEY," >> migrations/0001_init.sql - echo " name TEXT" >> migrations/0001_init.sql - echo ");" >> migrations/0001_init.sql - echo "-- 在此添加你的其他表结构" >> migrations/0001_init.sql - echo "Created initial migration file." - fi - - name: Run deploy script env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -69,5 +52,8 @@ jobs: AUTH_SECRET: ${{ secrets.AUTH_SECRET }} run: pnpm dlx tsx scripts/deploy/index.ts + # Clean up - name: Post deployment cleanup - run: rm -f .env*.* wrangler*.json + run: | + rm -f .env*.* + rm -f wrangler*.json From 171be760df984651db8bdef86e14da131c71dd66 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:33:16 +0800 Subject: [PATCH 12/20] Update index.ts --- scripts/deploy/index.ts | 303 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index b1e0b2e1..09ef51c4 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -271,6 +271,309 @@ const checkAndCreatePages = async () => { } }; +/** + * 推送Pages密钥 + */ +const pushPagesSecret = () => { + console.log("🔐 Pushing environment secrets to Pages..."); + + // 定义运行时所需的环境变量列表 + const runtimeEnvVars = [ + 'AUTH_GITHUB_ID', + 'AUTH_GITHUB_SECRET', + 'AUTH_GOOGLE_ID', + 'AUTH_GOOGLE_SECRET', + 'AUTH_SECRET' + ]; + + try { + // 确保.env文件存在 + if (!existsSync(resolve('.env'))) { + setupEnvFile(); + } + + // 读取.env文件内容 + const envContent = readFileSync(resolve('.env'), 'utf-8'); + + // 解析环境变量为对象 + const secrets: Record = {}; + + envContent.split('\n').forEach(line => { + const trimmedLine = line.trim(); + + // 跳过注释和空行 + if (!trimmedLine || trimmedLine.startsWith('#')) { + return; + } + + // 解析键值对 + const equalIndex = trimmedLine.indexOf('='); + if (equalIndex === -1) { + return; + } + + const key = trimmedLine.substring(0, equalIndex).trim(); + let value = trimmedLine.substring(equalIndex + 1).trim(); + + // 移除引号 + value = value.replace(/^["']|["']$/g, ''); + + // 只保留运行时所需的环境变量,且值不为空 + if (runtimeEnvVars.includes(key) && value.length > 0) { + secrets[key] = value; + } + }); + + // 检查是否有需要推送的secrets + if (Object.keys(secrets).length === 0) { + console.log("⚠️ No runtime secrets found to push"); + return; + } + + // 创建JSON格式的临时文件 + const runtimeEnvFile = resolve('.env.runtime.json'); + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + + console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); + + // 使用临时文件推送secrets + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { + stdio: "inherit" + }); + + // 清理临时文件 + if (existsSync(runtimeEnvFile)) { + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + } + + console.log("✅ Secrets pushed successfully"); + } catch (error) { + console.error("❌ Failed to push secrets:", error); + + // 确保清理临时文件 + const runtimeEnvFile = resolve('.env.runtime.json'); + if (existsSync(runtimeEnvFile)) { + try { + execSync(`rm ${runtimeEnvFile}` // 处理自定义项目名称 + if (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + + switch (wranglerFileName) { + case "wrangler.json": + json.name = PROJECT_NAME; + break; + case "wrangler.email.json": + json.name = `${PROJECT_NAME}-email-receiver-worker`; + break; + case "wrangler.cleanup.json": + json.name = `${PROJECT_NAME}-cleanup-worker`; + break; + default: + break; + } + } + + // 处理数据库配置 + 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...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + 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密钥 */ From 1c4272421f43e44be2c5347a9690560116045d14 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:37:25 +0800 Subject: [PATCH 13/20] Update deploy.yml --- .github/workflows/deploy.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e29c58ae..eb29326c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -10,19 +10,14 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Get full git history for checking file changes - - - name: Get previous tag - id: previoustag - if: github.event_name == 'push' - run: | - echo "tag=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_OUTPUT - continue-on-error: true + - name: Checkout repository + uses: actions/checkout@v4 + # 如果不需要对比历史 tag,fetch-depth: 1 (默认) 即可,能加快 checkout 速度 + # with: + # fetch-depth: 0 - name: Setup pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v4 # [修复] 升级到 v4 with: version: 9 @@ -50,10 +45,11 @@ jobs: AUTH_GOOGLE_ID: ${{ secrets.AUTH_GOOGLE_ID }} AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} AUTH_SECRET: ${{ secrets.AUTH_SECRET }} - run: pnpm dlx tsx scripts/deploy/index.ts + # [修复] 改用 pnpm exec,避免 dlx 每次重新下载 tsx 和 esbuild + run: pnpm exec tsx scripts/deploy/index.ts - # Clean up - name: Post deployment cleanup + if: always() # [优化] 即使上一步失败也执行清理 run: | - rm -f .env*.* - rm -f wrangler*.json + # [修复] 更严谨的清理规则,确保 .env 本身也被清理 + rm -f .env .env.* wrangler*.json From 1e04c9f5caf94f8497c79ea202cab7f0d2001bb6 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:44:06 +0800 Subject: [PATCH 14/20] Update index.ts --- scripts/deploy/index.ts | 312 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 09ef51c4..caa34562 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -35,6 +35,318 @@ const validateEnvironment = () => { /** * 处理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 (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + + switch (wranglerFileName) { + case "wrangler.json": + json.name = PROJECT_NAME; + break; + case "wrangler.email.json": + json.name = `${PROJECT_NAME}-email-receiver-worker`; + break; + case "wrangler.cleanup.json": + json.name = `${PROJECT_NAME}-cleanup-worker`; + break; + default: + break; + } + } + + 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...`); + + 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...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + 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..."); + + 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..."); + + const runtimeEnvVars = [ + 'AUTH_GITHUB_ID', + 'AUTH_GITHUB_SECRET', + 'AUTH_GOOGLE_ID', + 'AUTH_GOOGLE_SECRET', + 'AUTH_SECRET' + ]; + + try { + if (!existsSync(resolve('.env'))) { + setupEnvFile(); + } + + const envContent = readFileSync(resolve('.env'), 'utf-8'); + const secrets: Record = {}; + + envContent.split('\n').forEach(line => { + const trimmedLine = line.trim(); + + if (!trimmedLine || trimmedLine.startsWith('#')) { + return; + } + + const equalIndex = trimmedLine.indexOf('='); + if (equalIndex === -1) { + return; + } + + const key = trimmedLine.substring(0, equalIndex).trim(); + let value = trimmedLine.substring(equalIndex + 1).trim(); + + value = value.replace(/^["']|["']$/g, ''); + + if (runtimeEnvVars.includes(key) && value.length > 0) { + secrets[key] = value; + } + }); + + if (Object.keys(secrets).length === 0) { + console.log("⚠️ No runtime secrets found to push"); + return; + } + + const runtimeEnvFile = resolve('.env.runtime.json'); + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + + console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); + + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { + stdio: "inherit" + }); + + if (existsSync(runtimeEnvFile)) { + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + } + + console.log("✅ Secrets pushed successfully"); + } catch (error) { + console.error("❌ Failed to push secrets:", error); + + const runtimeEnvFile = resolve('.env.runtime.json'); + if (existsSync(runtimeEnvFile)) { + try { + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + } catch (cleanupError) { + console.error("⚠️ Failed to cleanup temporary file:", cleanupError); + } + } + throw error; + } +}; + +/** + * 部署Pages应用 + */ +const deployPages = () => { + console.log("🚧 Deploying to Cloudflare Pages..."); + try { + execSync("pnpm run deploy:pages", { stdio: "inherit" }); + console.log("✅ Pages deployment completed successfully"); + } catch (error) { */ const setupConfigFile = (examplePath: string, targetPath: string) => { try { // 如果目标文件已存在,则跳过 From 79d90af7420a86b6ff37576ec2c4739b536fd76f Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:52:27 +0800 Subject: [PATCH 15/20] Update index.ts --- scripts/deploy/index.ts | 224 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 1 deletion(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index caa34562..427545d2 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -15,7 +15,7 @@ import { 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; +let CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID; /** @@ -32,6 +32,40 @@ const validateEnvironment = () => { } }; +/** + * 更新环境变量到 .env 文件 + */ +const updateEnvVar = (key: string, value: string) => { + const envPath = resolve(".env"); + let content = ""; + if (existsSync(envPath)) { + content = readFileSync(envPath, "utf-8"); + const regex = new RegExp(`^${key}=.*$`, "m"); + if (regex.test(content)) { + content = content.replace(regex, `${key}=${value}`); + } else { + content += `\n${key}=${value}`; + } + } else { + content = `${key}=${value}`; + } + writeFileSync(envPath, content); +}; + +/** + * 创建 .env 文件示例 + */ +const setupEnvFile = () => { + const envPath = resolve(".env"); + if (!existsSync(envPath)) { + writeFileSync( + envPath, + `# Environment variables for ${PROJECT_NAME}\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\nCUSTOM_DOMAIN=\n` + ); + console.log("✅ Created .env file"); + } +}; + /** * 处理JSON配置文件 */ @@ -50,6 +84,194 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { const configContent = readFileSync(examplePath, "utf-8"); const json = JSON.parse(configContent); + if (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + + switch (wranglerFileName) { + case "wrangler.json": + json.name = PROJECT_NAME; + break; + case "wrangler.email.json": + json.name = `${PROJECT_NAME}-email-receiver-worker`; + break; + case "wrangler.cleanup.json": + json.name = `${PROJECT_NAME}-cleanup-worker`; + break; + } + } + + 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...`); + + 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...`); + const database = await createDatabase(); + updateDatabaseConfig(database.uuid); + console.log(`✅ Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); + } else { + console.error(`❌ An error occurred while checking the database:`, error); + throw error; + } + } +}; + +/** + * 迁移数据库 + */ +const migrateDatabase = () => { + console.log("📝 Migrating remote database..."); + execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); + console.log("✅ Database migration completed successfully"); +}; + +/** + * 检查并创建KV命名空间 + */ +const checkAndCreateKVNamespace = async () => { + console.log(`🔍 Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + const namespaceList = await getKVNamespaceList(); + let 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, creating new KV namespace..."); + namespace = await createKVNamespace(); + updateKVConfig(namespace.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); + } +}; + +/** + * 检查并创建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..."); + const appUrl = `https://${pages.subdomain}`; */ +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 (PROJECT_NAME !== "moemail") { const wranglerFileName = targetPath.split("/").at(-1); From 9f7c50308f002e1875d4d40bc75677326c12b6b9 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 09:59:33 +0800 Subject: [PATCH 16/20] Update index.ts --- scripts/deploy/index.ts | 317 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 314 insertions(+), 3 deletions(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 427545d2..81ded6a3 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -1,7 +1,7 @@ import { NotFoundError } from "cloudflare"; import "dotenv/config"; import { execSync } from "node:child_process"; -import { readFileSync, writeFileSync, existsSync } from "node:fs"; +import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs"; import { resolve } from "node:path"; import { createDatabase, @@ -15,9 +15,18 @@ import { 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"; -let CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; +const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID; +// 运行时所需的环境变量列表(集中管理,避免分散) +const RUNTIME_ENV_VARS = [ + "AUTH_GITHUB_ID", + "AUTH_GITHUB_SECRET", + "AUTH_GOOGLE_ID", + "AUTH_GOOGLE_SECRET", + "AUTH_SECRET", +] as const; + /** * 验证必要的环境变量 */ @@ -33,8 +42,310 @@ const validateEnvironment = () => { }; /** - * 更新环境变量到 .env 文件 + * 解析 .env 文件内容为键值对对象 + * 支持无引号、单引号、双引号三种格式 + */ +const parseEnvContent = (content: string): Record => { + const result: Record = {}; + + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + const equalIndex = trimmed.indexOf("="); + if (equalIndex === -1) continue; + + const key = trimmed.substring(0, equalIndex).trim(); + const raw = trimmed.substring(equalIndex + 1).trim(); + // 统一去除首尾的单引号或双引号 + const value = raw.replace(/^(["'])(.*)\1$/, "$2"); + + if (key) result[key] = value; + } + + return result; +}; + +/** + * 处理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 json = JSON.parse(readFileSync(examplePath, "utf-8")); + + if (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + const nameMap: Record = { + "wrangler.json": PROJECT_NAME, + "wrangler.email.json": `${PROJECT_NAME}-email-receiver-worker`, + "wrangler.cleanup.json": `${PROJECT_NAME}-cleanup-worker`, + }; + if (wranglerFileName && nameMap[wranglerFileName]) { + json.name = nameMap[wranglerFileName]; + } + } + + if (json.d1_databases?.[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 { example, target } of configs) { + setupConfigFile(resolve(example), resolve(target)); + } +}; + +/** + * 更新指定 JSON 配置文件中的字段(通用) + */ +const updateJsonConfig = ( + filePath: string, + updater: (json: Record) => void +) => { + if (!existsSync(filePath)) return; + try { + const json = JSON.parse(readFileSync(filePath, "utf-8")); + updater(json); + writeFileSync(filePath, JSON.stringify(json, null, 2)); + console.log(`✅ Updated ${filePath}`); + } catch (error) { + console.error(`❌ Failed to update ${filePath}:`, error); + } +}; + +/** + * 更新数据库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) { + updateJsonConfig(resolve(filename), (json) => { + const db = (json.d1_databases as Array>)?.[0]; + if (db) db.database_id = dbId; + }); + } +}; + +/** + * 更新KV命名空间ID到所有配置文件 + */ +const updateKVConfig = (namespaceId: string) => { + console.log(`📝 Updating KV namespace ID (${namespaceId}) in configurations...`); + + updateJsonConfig(resolve("wrangler.json"), (json) => { + const kv = (json.kv_namespaces as Array>)?.[0]; + if (kv) kv.id = namespaceId; + }); +}; + +/** + * 检查并创建数据库 + */ +const checkAndCreateDatabase = async () => { + console.log(`🔍 Checking if database "${DATABASE_NAME}" exists...`); + + try { + const database = await getDatabase(); + if (!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.error("❌ An error occurred while checking the database:", error); + throw error; + } + + console.log("⚠️ Database not found, creating new database..."); + const database = await createDatabase(); + if (!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})`); + } +}; + +/** + * 迁移数据库 + */ +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...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + try { + const namespaceList = await getKVNamespaceList(); + const existing = namespaceList.find((ns) => ns.title === KV_NAMESPACE_NAME); + + if (existing?.id) { + updateKVConfig(existing.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})`); + } else { + console.log("⚠️ KV namespace not found by name, creating new KV namespace..."); + const 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.error("❌ An error occurred while checking the project:", error); + throw error; + } + + 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..."); + updateEnvVar("CUSTOM_DOMAIN", `https://${pages.subdomain}`); + } + } +}; + +/** + * 推送Pages密钥 */ +const pushPagesSecret = () => { + console.log("🔐 Pushing environment secrets to Pages..."); + + const envFilePath = resolve(".env"); + if (!existsSync(envFilePath)) setupEnvFile(); + + const envVars = parseEnvContent(readFileSync(envFilePath, "utf-8")); + const secrets: Record = {}; + + for (const key of RUNTIME_ENV_VARS) { + if (envVars[key]) secrets[key] = envVars[key]; + } + + if (Object.keys(secrets).length === 0) { + console.log("⚠️ No runtime secrets found to push"); + return; + } + + console.log( + `📝 Found ${Object.keys(secrets).length} secrets to push:`, + Object.keys(secrets).join(", ") + ); + + const runtimeEnvFile = resolve(".env.runtime.json"); + try { + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); + console.log("✅ Secrets pushed successfully"); + } finally { + // 使用 unlinkSync 替代 rm 命令,避免跨平台问题 + if (existsSync(runtimeEnvFile)) { + try { + unlinkSync(runtimeEnvFile); + } catch (cleanupError) { + console.error("⚠️ Failed to cleanup temporary file:", cleanupError); + } + } + } +}; + +/** + * 部署Pages应用 + */ +const deployPages = () => { + console.log("🚧 Deploying to Cloudflare Pages..."); + try { + execSync("pnpm run deploy:pages", { 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 */ const updateEnvVar = (key: string, value: string) => { const envPath = resolve(".env"); let content = ""; From 927f692a2850493ea7202dc25ec4a556a38adeb4 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 10:06:00 +0800 Subject: [PATCH 17/20] Refactor configuration setup and deployment functions --- scripts/deploy/index.ts | 308 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 81ded6a3..57ab474e 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -66,6 +66,314 @@ const parseEnvContent = (content: string): Record => { return result; }; +/** + * 处理JSON配置文件 + */ +const setupConfigFile = (examplePath: string, targetPath: string) => { + try { + if (existsSync(targetPath)) { + console.log(`[skip] Configuration ${targetPath} already exists.`); + return; + } + + if (!existsSync(examplePath)) { + console.log(`[warn] Example file ${examplePath} does not exist, skipping...`); + return; + } + + const json = JSON.parse(readFileSync(examplePath, "utf-8")); + + if (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + const nameMap: Record = { + "wrangler.json": PROJECT_NAME, + "wrangler.email.json": `${PROJECT_NAME}-email-receiver-worker`, + "wrangler.cleanup.json": `${PROJECT_NAME}-cleanup-worker`, + }; + if (wranglerFileName && nameMap[wranglerFileName]) { + json.name = nameMap[wranglerFileName]; + } + } + + if (json.d1_databases?.[0]) { + json.d1_databases[0].database_name = DATABASE_NAME; + } + + writeFileSync(targetPath, JSON.stringify(json, null, 2)); + console.log(`[ok] Configuration ${targetPath} setup successfully.`); + } catch (error) { + console.error(`[error] Failed to setup ${targetPath}:`, error); + throw error; + } +}; + +/** + * 设置所有Wrangler配置文件 + */ +const setupWranglerConfigs = () => { + console.log("[setup] 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 { example, target } of configs) { + setupConfigFile(resolve(example), resolve(target)); + } +}; + +/** + * 更新指定 JSON 配置文件中的字段(通用) + */ +const updateJsonConfig = ( + filePath: string, + updater: (json: Record) => void +) => { + if (!existsSync(filePath)) return; + try { + const json = JSON.parse(readFileSync(filePath, "utf-8")); + updater(json); + writeFileSync(filePath, JSON.stringify(json, null, 2)); + console.log(`[ok] Updated ${filePath}`); + } catch (error) { + console.error(`[error] Failed to update ${filePath}:`, error); + } +}; + +/** + * 更新数据库ID到所有配置文件 + */ +const updateDatabaseConfig = (dbId: string) => { + console.log(`[info] Updating database ID (${dbId}) in configurations...`); + + const configFiles = ["wrangler.json", "wrangler.email.json", "wrangler.cleanup.json"]; + for (const filename of configFiles) { + updateJsonConfig(resolve(filename), (json) => { + const db = (json.d1_databases as Array>)?.[0]; + if (db) db.database_id = dbId; + }); + } +}; + +/** + * 更新KV命名空间ID到所有配置文件 + */ +const updateKVConfig = (namespaceId: string) => { + console.log(`[info] Updating KV namespace ID (${namespaceId}) in configurations...`); + + updateJsonConfig(resolve("wrangler.json"), (json) => { + const kv = (json.kv_namespaces as Array>)?.[0]; + if (kv) kv.id = namespaceId; + }); +}; + +/** + * 检查并创建数据库 + */ +const checkAndCreateDatabase = async () => { + console.log(`[check] Checking if database "${DATABASE_NAME}" exists...`); + + try { + const database = await getDatabase(); + if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); + + updateDatabaseConfig(database.uuid); + console.log(`[ok] Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})`); + } catch (error) { + if (!(error instanceof NotFoundError)) { + console.error("[error] An error occurred while checking the database:", error); + throw error; + } + + console.log("[warn] Database not found, creating new database..."); + const database = await createDatabase(); + if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); + + updateDatabaseConfig(database.uuid); + console.log(`[ok] Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); + } +}; + +/** + * 迁移数据库 + */ +const migrateDatabase = () => { + console.log("[info] Migrating remote database..."); + try { + execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); + console.log("[ok] Database migration completed successfully"); + } catch (error) { + console.error("[error] Database migration failed:", error); + throw error; + } +}; + +/** + * 检查并创建KV命名空间 + */ +const checkAndCreateKVNamespace = async () => { + console.log(`[check] Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`[ok] User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + try { + const namespaceList = await getKVNamespaceList(); + const existing = namespaceList.find((ns) => ns.title === KV_NAMESPACE_NAME); + + if (existing?.id) { + updateKVConfig(existing.id); + console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})`); + } else { + console.log("[warn] KV namespace not found by name, creating new KV namespace..."); + const namespace = await createKVNamespace(); + updateKVConfig(namespace.id); + console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); + } + } catch (error) { + console.error("[error] An error occurred while checking the KV namespace:", error); + throw error; + } +}; + +/** + * 检查并创建Pages项目 + */ +const checkAndCreatePages = async () => { + console.log(`[check] Checking if project "${PROJECT_NAME}" exists...`); + + try { + await getPages(); + console.log("[ok] Project already exists, proceeding with update..."); + } catch (error) { + if (!(error instanceof NotFoundError)) { + console.error("[error] An error occurred while checking the project:", error); + throw error; + } + + console.log("[warn] Project not found, creating new project..."); + const pages = await createPages(); + + if (!CUSTOM_DOMAIN && pages.subdomain) { + console.log("[warn] CUSTOM_DOMAIN is empty, using pages default domain..."); + console.log("[info] Updating environment variables..."); + updateEnvVar("CUSTOM_DOMAIN", `https://${pages.subdomain}`); + } + } +}; + +/** + * 推送Pages密钥 + */ +const pushPagesSecret = () => { + console.log("[secret] Pushing environment secrets to Pages..."); + + const envFilePath = resolve(".env"); + if (!existsSync(envFilePath)) setupEnvFile(); + + const envVars = parseEnvContent(readFileSync(envFilePath, "utf-8")); + const secrets: Record = {}; + + for (const key of RUNTIME_ENV_VARS) { + if (envVars[key]) secrets[key] = envVars[key]; + } + + if (Object.keys(secrets).length === 0) { + console.log("[warn] No runtime secrets found to push"); + return; + } + + console.log( + `[info] Found ${Object.keys(secrets).length} secrets to push:`, + Object.keys(secrets).join(", ") + ); + + const runtimeEnvFile = resolve(".env.runtime.json"); + try { + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); + console.log("[ok] Secrets pushed successfully"); + } finally { + // 使用 unlinkSync 替代 rm 命令,避免跨平台问题 + if (existsSync(runtimeEnvFile)) { + try { + unlinkSync(runtimeEnvFile); + } catch (cleanupError) { + console.error("[warn] Failed to cleanup temporary file:", cleanupError); + } + } + } +}; + +/** + * 部署Pages应用 + */ +const deployPages = () => { + console.log("[deploy] Deploying to Cloudflare Pages..."); + try { + execSync("pnpm run deploy:pages", { stdio: "inherit" }); + console.log("[ok] Pages deployment completed successfully"); + } catch (error) { + console.error("[error] Pages deployment failed:", error); + throw error; + } +}; + +/** + * 部署Email Worker + */ +const deployEmailWorker = () => { + console.log("[deploy] Deploying Email Worker..."); + try { + execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { stdio: "inherit" }); + console.log("[ok] Email Worker deployed successfully"); + } catch (error) { + console.error("[error] Email Worker deployment failed:", error); + // 继续执行而不中断 + } +}; + +/** + * 部署Cleanup Worker + */ +const deployCleanupWorker = () => { + co if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(", ")}` + ); + } +}; + +/** + * 解析 .env 文件内容为键值对对象 + * 支持无引号、单引号、双引号三种格式 + */ +const parseEnvContent = (content: string): Record => { + const result: Record = {}; + + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + const equalIndex = trimmed.indexOf("="); + if (equalIndex === -1) continue; + + const key = trimmed.substring(0, equalIndex).trim(); + const raw = trimmed.substring(equalIndex + 1).trim(); + // 统一去除首尾的单引号或双引号 + const value = raw.replace(/^(["'])(.*)\1$/, "$2"); + + if (key) result[key] = value; + } + + return result; +}; + /** * 处理JSON配置文件 */ From 6a5cf53b614a7b56f4682740c456a3ec307270f5 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 10:09:27 +0800 Subject: [PATCH 18/20] Update index.ts --- scripts/deploy/index.ts | 304 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 57ab474e..7c330bf9 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -267,6 +267,310 @@ const checkAndCreatePages = async () => { } }; +/** + * 推送Pages密钥 + */ +const pushPagesSecret = () => { + console.log("[secret] Pushing environment secrets to Pages..."); + + const envFilePath = resolve(".env"); + if (!existsSync(envFilePath)) setupEnvFile(); + + const envVars = parseEnvContent(readFileSync(envFilePath, "utf-8")); + const secrets: Record = {}; + + for (const key of RUNTIME_ENV_VARS) { + if (envVars[key]) secrets[key] = envVars[key]; + } + + if (Object.keys(secrets).length === 0) { + console.log("[warn] No runtime secrets found to push"); + return; + } + + console.log( + `[info] Found ${Object.keys(secrets).length} secrets to push:`, + Object.keys(secrets).join(", ") + ); + + const runtimeEnvFile = resolve(".env.runtime.json"); + // 提取为具名函数,规避 esbuild 0.25.x 对 finally{if{try}} 嵌套结构的解析 bug + const cleanupRuntimeEnvFile = () => { + if (!existsSync(runtimeEnvFile)) return; + try { + unlinkSync(runtimeEnvFile); + } catch (cleanupError) { + console.error("[warn] Failed to cleanup temporary file:", cleanupError); + } + }; + + try { + writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); + console.log("[ok] Secrets pushed successfully"); + cleanupRuntimeEnvFile(); + } catch (err) { + cleanupRuntimeEnvFile(); + throw err; + } +}; + +/** + * 部署Pages应用 + */ +const deployPages = () => { + console.log("[deploy] Deploying to Cloudflare Pages..."); + try { + execSync("pnpm run deploy:pages", { stdio: "inherit" }); + console.log("[ok] Pages deployment completed successfully"); + } catch (error) { + console.error("[error] Pages deployment failed:", error); + throw error; + } +}; + +/** + * 部署Email Worker + */ +const deployEmailWorker = () => { + console.log("[deploy] Deploying Email Worker..."); + try { + execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { stdio: "inherit" }); + console.log("[ok] Email Worker deployed successfully"); + } catch (error) { + console.error("[err if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(", ")}` + ); + } +}; + +/** + * 解析 .env 文件内容为键值对对象 + * 支持无引号、单引号、双引号三种格式 + */ +const parseEnvContent = (content: string): Record => { + const result: Record = {}; + + for (const line of content.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + const equalIndex = trimmed.indexOf("="); + if (equalIndex === -1) continue; + + const key = trimmed.substring(0, equalIndex).trim(); + const raw = trimmed.substring(equalIndex + 1).trim(); + // 统一去除首尾的单引号或双引号 + const value = raw.replace(/^(["'])(.*)\1$/, "$2"); + + if (key) result[key] = value; + } + + return result; +}; + +/** + * 处理JSON配置文件 + */ +const setupConfigFile = (examplePath: string, targetPath: string) => { + try { + if (existsSync(targetPath)) { + console.log(`[skip] Configuration ${targetPath} already exists.`); + return; + } + + if (!existsSync(examplePath)) { + console.log(`[warn] Example file ${examplePath} does not exist, skipping...`); + return; + } + + const json = JSON.parse(readFileSync(examplePath, "utf-8")); + + if (PROJECT_NAME !== "moemail") { + const wranglerFileName = targetPath.split("/").at(-1); + const nameMap: Record = { + "wrangler.json": PROJECT_NAME, + "wrangler.email.json": `${PROJECT_NAME}-email-receiver-worker`, + "wrangler.cleanup.json": `${PROJECT_NAME}-cleanup-worker`, + }; + if (wranglerFileName && nameMap[wranglerFileName]) { + json.name = nameMap[wranglerFileName]; + } + } + + if (json.d1_databases?.[0]) { + json.d1_databases[0].database_name = DATABASE_NAME; + } + + writeFileSync(targetPath, JSON.stringify(json, null, 2)); + console.log(`[ok] Configuration ${targetPath} setup successfully.`); + } catch (error) { + console.error(`[error] Failed to setup ${targetPath}:`, error); + throw error; + } +}; + +/** + * 设置所有Wrangler配置文件 + */ +const setupWranglerConfigs = () => { + console.log("[setup] 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 { example, target } of configs) { + setupConfigFile(resolve(example), resolve(target)); + } +}; + +/** + * 更新指定 JSON 配置文件中的字段(通用) + */ +const updateJsonConfig = ( + filePath: string, + updater: (json: Record) => void +) => { + if (!existsSync(filePath)) return; + try { + const json = JSON.parse(readFileSync(filePath, "utf-8")); + updater(json); + writeFileSync(filePath, JSON.stringify(json, null, 2)); + console.log(`[ok] Updated ${filePath}`); + } catch (error) { + console.error(`[error] Failed to update ${filePath}:`, error); + } +}; + +/** + * 更新数据库ID到所有配置文件 + */ +const updateDatabaseConfig = (dbId: string) => { + console.log(`[info] Updating database ID (${dbId}) in configurations...`); + + const configFiles = ["wrangler.json", "wrangler.email.json", "wrangler.cleanup.json"]; + for (const filename of configFiles) { + updateJsonConfig(resolve(filename), (json) => { + const db = (json.d1_databases as Array>)?.[0]; + if (db) db.database_id = dbId; + }); + } +}; + +/** + * 更新KV命名空间ID到所有配置文件 + */ +const updateKVConfig = (namespaceId: string) => { + console.log(`[info] Updating KV namespace ID (${namespaceId}) in configurations...`); + + updateJsonConfig(resolve("wrangler.json"), (json) => { + const kv = (json.kv_namespaces as Array>)?.[0]; + if (kv) kv.id = namespaceId; + }); +}; + +/** + * 检查并创建数据库 + */ +const checkAndCreateDatabase = async () => { + console.log(`[check] Checking if database "${DATABASE_NAME}" exists...`); + + try { + const database = await getDatabase(); + if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); + + updateDatabaseConfig(database.uuid); + console.log(`[ok] Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})`); + } catch (error) { + if (!(error instanceof NotFoundError)) { + console.error("[error] An error occurred while checking the database:", error); + throw error; + } + + console.log("[warn] Database not found, creating new database..."); + const database = await createDatabase(); + if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); + + updateDatabaseConfig(database.uuid); + console.log(`[ok] Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); + } +}; + +/** + * 迁移数据库 + */ +const migrateDatabase = () => { + console.log("[info] Migrating remote database..."); + try { + execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); + console.log("[ok] Database migration completed successfully"); + } catch (error) { + console.error("[error] Database migration failed:", error); + throw error; + } +}; + +/** + * 检查并创建KV命名空间 + */ +const checkAndCreateKVNamespace = async () => { + console.log(`[check] Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); + + if (KV_NAMESPACE_ID) { + updateKVConfig(KV_NAMESPACE_ID); + console.log(`[ok] User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + return; + } + + try { + const namespaceList = await getKVNamespaceList(); + const existing = namespaceList.find((ns) => ns.title === KV_NAMESPACE_NAME); + + if (existing?.id) { + updateKVConfig(existing.id); + console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})`); + } else { + console.log("[warn] KV namespace not found by name, creating new KV namespace..."); + const namespace = await createKVNamespace(); + updateKVConfig(namespace.id); + console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); + } + } catch (error) { + console.error("[error] An error occurred while checking the KV namespace:", error); + throw error; + } +}; + +/** + * 检查并创建Pages项目 + */ +const checkAndCreatePages = async () => { + console.log(`[check] Checking if project "${PROJECT_NAME}" exists...`); + + try { + await getPages(); + console.log("[ok] Project already exists, proceeding with update..."); + } catch (error) { + if (!(error instanceof NotFoundError)) { + console.error("[error] An error occurred while checking the project:", error); + throw error; + } + + console.log("[warn] Project not found, creating new project..."); + const pages = await createPages(); + + if (!CUSTOM_DOMAIN && pages.subdomain) { + console.log("[warn] CUSTOM_DOMAIN is empty, using pages default domain..."); + console.log("[info] Updating environment variables..."); + updateEnvVar("CUSTOM_DOMAIN", `https://${pages.subdomain}`); + } + } +}; + /** * 推送Pages密钥 */ From f6ecafcb034a4919aa72ff4fe34fcff2c55d0d1b Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 10:13:18 +0800 Subject: [PATCH 19/20] refactor: clean up and improve deployment script - remove duplicates, add error handling, and improve cross-platform compatibility --- scripts/deploy/index.ts | 2753 +++------------------------------------ 1 file changed, 192 insertions(+), 2561 deletions(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 7c330bf9..4558560a 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -12,13 +12,16 @@ import { 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 KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID; -// 运行时所需的环境变量列表(集中管理,避免分散) const RUNTIME_ENV_VARS = [ "AUTH_GITHUB_ID", "AUTH_GITHUB_SECRET", @@ -27,10 +30,14 @@ const RUNTIME_ENV_VARS = [ "AUTH_SECRET", ] as const; +// ============================================ +// 工具函数 +// ============================================ + /** * 验证必要的环境变量 */ -const validateEnvironment = () => { +const validateEnvironment = (): void => { const requiredEnvVars = ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"]; const missing = requiredEnvVars.filter((varName) => !process.env[varName]); @@ -57,7 +64,6 @@ const parseEnvContent = (content: string): Record => { const key = trimmed.substring(0, equalIndex).trim(); const raw = trimmed.substring(equalIndex + 1).trim(); - // 统一去除首尾的单引号或双引号 const value = raw.replace(/^(["'])(.*)\1$/, "$2"); if (key) result[key] = value; @@ -66,43 +72,75 @@ const parseEnvContent = (content: string): Record => { return result; }; +/** + * 更新指定 JSON 配置文件中的字段(通用) + */ +const updateJsonConfig = ( + filePath: string, + updater: (json: Record) => void +): void => { + if (!existsSync(filePath)) return; + + try { + const json = JSON.parse(readFileSync(filePath, "utf-8")); + updater(json); + writeFileSync(filePath, JSON.stringify(json, null, 2)); + console.log(`✅ Updated ${filePath}`); + } catch (error) { + console.error(`❌ Failed to update ${filePath}:`, error); + } +}; + +// ============================================ +// 配置管理函数 +// ============================================ + /** * 处理JSON配置文件 */ -const setupConfigFile = (examplePath: string, targetPath: string) => { +const setupConfigFile = (examplePath: string, targetPath: string): void => { try { if (existsSync(targetPath)) { - console.log(`[skip] Configuration ${targetPath} already exists.`); + console.log(`✨ Configuration ${targetPath} already exists.`); return; } if (!existsSync(examplePath)) { - console.log(`[warn] Example file ${examplePath} does not exist, skipping...`); + console.log( + `⚠️ Example file ${examplePath} does not exist, skipping...` + ); return; } - const json = JSON.parse(readFileSync(examplePath, "utf-8")); + const configContent = readFileSync(examplePath, "utf-8"); + const json = JSON.parse(configContent); + // 处理自定义项目名称 if (PROJECT_NAME !== "moemail") { const wranglerFileName = targetPath.split("/").at(-1); - const nameMap: Record = { - "wrangler.json": PROJECT_NAME, - "wrangler.email.json": `${PROJECT_NAME}-email-receiver-worker`, - "wrangler.cleanup.json": `${PROJECT_NAME}-cleanup-worker`, - }; - if (wranglerFileName && nameMap[wranglerFileName]) { - json.name = nameMap[wranglerFileName]; + + switch (wranglerFileName) { + case "wrangler.json": + json.name = PROJECT_NAME; + break; + case "wrangler.email.json": + json.name = `${PROJECT_NAME}-email-receiver-worker`; + break; + case "wrangler.cleanup.json": + json.name = `${PROJECT_NAME}-cleanup-worker`; + break; } } - if (json.d1_databases?.[0]) { + // 处理数据库配置 + 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(`[ok] Configuration ${targetPath} setup successfully.`); + console.log(`✅ Configuration ${targetPath} setup successfully.`); } catch (error) { - console.error(`[error] Failed to setup ${targetPath}:`, error); + console.error(`❌ Failed to setup ${targetPath}:`, error); throw error; } }; @@ -110,45 +148,35 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { /** * 设置所有Wrangler配置文件 */ -const setupWranglerConfigs = () => { - console.log("[setup] Setting up Wrangler configuration files..."); +const setupWranglerConfigs = (): void => { + 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" }, + { + example: "wrangler.cleanup.example.json", + target: "wrangler.cleanup.json", + }, ]; - for (const { example, target } of configs) { - setupConfigFile(resolve(example), resolve(target)); - } -}; - -/** - * 更新指定 JSON 配置文件中的字段(通用) - */ -const updateJsonConfig = ( - filePath: string, - updater: (json: Record) => void -) => { - if (!existsSync(filePath)) return; - try { - const json = JSON.parse(readFileSync(filePath, "utf-8")); - updater(json); - writeFileSync(filePath, JSON.stringify(json, null, 2)); - console.log(`[ok] Updated ${filePath}`); - } catch (error) { - console.error(`[error] Failed to update ${filePath}:`, error); + for (const config of configs) { + setupConfigFile(resolve(config.example), resolve(config.target)); } }; /** * 更新数据库ID到所有配置文件 */ -const updateDatabaseConfig = (dbId: string) => { - console.log(`[info] Updating database ID (${dbId}) in configurations...`); +const updateDatabaseConfig = (dbId: string): void => { + console.log(`📝 Updating database ID (${dbId}) in configurations...`); + + const configFiles = [ + "wrangler.json", + "wrangler.email.json", + "wrangler.cleanup.json", + ]; - const configFiles = ["wrangler.json", "wrangler.email.json", "wrangler.cleanup.json"]; for (const filename of configFiles) { updateJsonConfig(resolve(filename), (json) => { const db = (json.d1_databases as Array>)?.[0]; @@ -160,8 +188,8 @@ const updateDatabaseConfig = (dbId: string) => { /** * 更新KV命名空间ID到所有配置文件 */ -const updateKVConfig = (namespaceId: string) => { - console.log(`[info] Updating KV namespace ID (${namespaceId}) in configurations...`); +const updateKVConfig = (namespaceId: string): void => { + console.log(`📝 Updating KV namespace ID (${namespaceId}) in configurations...`); updateJsonConfig(resolve("wrangler.json"), (json) => { const kv = (json.kv_namespaces as Array>)?.[0]; @@ -169,56 +197,70 @@ const updateKVConfig = (namespaceId: string) => { }); }; +// ============================================ +// 数据库操作函数 +// ============================================ + /** * 检查并创建数据库 */ -const checkAndCreateDatabase = async () => { - console.log(`[check] Checking if database "${DATABASE_NAME}" exists...`); +const checkAndCreateDatabase = async (): Promise => { + console.log(`🔍 Checking if database "${DATABASE_NAME}" exists...`); try { const database = await getDatabase(); if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); updateDatabaseConfig(database.uuid); - console.log(`[ok] Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})`); + console.log( + `✅ Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})` + ); } catch (error) { if (!(error instanceof NotFoundError)) { - console.error("[error] An error occurred while checking the database:", error); + console.error("❌ An error occurred while checking the database:", error); throw error; } - console.log("[warn] Database not found, creating new database..."); + console.log("⚠️ Database not found, creating new database..."); const database = await createDatabase(); if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); updateDatabaseConfig(database.uuid); - console.log(`[ok] Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); + console.log( + `✅ Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})` + ); } }; /** * 迁移数据库 */ -const migrateDatabase = () => { - console.log("[info] Migrating remote database..."); +const migrateDatabase = (): void => { + console.log("📝 Migrating remote database..."); try { execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); - console.log("[ok] Database migration completed successfully"); + console.log("✅ Database migration completed successfully"); } catch (error) { - console.error("[error] Database migration failed:", error); + console.error("❌ Database migration failed:", error); throw error; } }; +// ============================================ +// KV 命名空间操作函数 +// ============================================ + /** * 检查并创建KV命名空间 */ -const checkAndCreateKVNamespace = async () => { - console.log(`[check] Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); +const checkAndCreateKVNamespace = async (): Promise => { + console.log( + `🔍 Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...` + ); if (KV_NAMESPACE_ID) { updateKVConfig(KV_NAMESPACE_ID); - console.log(`[ok] User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); + console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); return; } @@ -228,40 +270,55 @@ const checkAndCreateKVNamespace = async () => { if (existing?.id) { updateKVConfig(existing.id); - console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})`); + console.log( + `✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})` + ); } else { - console.log("[warn] KV namespace not found by name, creating new KV namespace..."); + console.log( + "⚠️ KV namespace not found by name, creating new KV namespace..." + ); const namespace = await createKVNamespace(); updateKVConfig(namespace.id); - console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); + console.log( + `✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})` + ); } } catch (error) { - console.error("[error] An error occurred while checking the KV namespace:", error); + console.error( + "❌ An error occurred while checking the KV namespace:", + error + ); throw error; } }; +// ============================================ +// Pages 项目操作函数 +// ============================================ + /** * 检查并创建Pages项目 */ -const checkAndCreatePages = async () => { - console.log(`[check] Checking if project "${PROJECT_NAME}" exists...`); +const checkAndCreatePages = async (): Promise => { + console.log(`🔍 Checking if project "${PROJECT_NAME}" exists...`); try { await getPages(); - console.log("[ok] Project already exists, proceeding with update..."); + console.log("✅ Project already exists, proceeding with update..."); } catch (error) { if (!(error instanceof NotFoundError)) { - console.error("[error] An error occurred while checking the project:", error); + console.error("❌ An error occurred while checking the project:", error); throw error; } - console.log("[warn] Project not found, creating new project..."); + console.log("⚠️ Project not found, creating new project..."); const pages = await createPages(); if (!CUSTOM_DOMAIN && pages.subdomain) { - console.log("[warn] CUSTOM_DOMAIN is empty, using pages default domain..."); - console.log("[info] Updating environment variables..."); + console.log( + "⚠️ CUSTOM_DOMAIN is empty, using pages default domain..." + ); + console.log("📝 Updating environment variables..."); updateEnvVar("CUSTOM_DOMAIN", `https://${pages.subdomain}`); } } @@ -270,8 +327,8 @@ const checkAndCreatePages = async () => { /** * 推送Pages密钥 */ -const pushPagesSecret = () => { - console.log("[secret] Pushing environment secrets to Pages..."); +const pushPagesSecret = (): void => { + console.log("🔐 Pushing environment secrets to Pages..."); const envFilePath = resolve(".env"); if (!existsSync(envFilePath)) setupEnvFile(); @@ -284,30 +341,31 @@ const pushPagesSecret = () => { } if (Object.keys(secrets).length === 0) { - console.log("[warn] No runtime secrets found to push"); + console.log("⚠️ No runtime secrets found to push"); return; } console.log( - `[info] Found ${Object.keys(secrets).length} secrets to push:`, + `📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(", ") ); const runtimeEnvFile = resolve(".env.runtime.json"); - // 提取为具名函数,规避 esbuild 0.25.x 对 finally{if{try}} 嵌套结构的解析 bug - const cleanupRuntimeEnvFile = () => { + const cleanupRuntimeEnvFile = (): void => { if (!existsSync(runtimeEnvFile)) return; try { unlinkSync(runtimeEnvFile); } catch (cleanupError) { - console.error("[warn] Failed to cleanup temporary file:", cleanupError); + console.error("⚠️ Failed to cleanup temporary file:", cleanupError); } }; try { writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); - console.log("[ok] Secrets pushed successfully"); + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { + stdio: "inherit", + }); + console.log("✅ Secrets pushed successfully"); cleanupRuntimeEnvFile(); } catch (err) { cleanupRuntimeEnvFile(); @@ -315,16 +373,20 @@ const pushPagesSecret = () => { } }; +// ============================================ +// 部署函数 +// ============================================ + /** * 部署Pages应用 */ -const deployPages = () => { - console.log("[deploy] Deploying to Cloudflare Pages..."); +const deployPages = (): void => { + console.log("🚧 Deploying to Cloudflare Pages..."); try { execSync("pnpm run deploy:pages", { stdio: "inherit" }); - console.log("[ok] Pages deployment completed successfully"); + console.log("✅ Pages deployment completed successfully"); } catch (error) { - console.error("[error] Pages deployment failed:", error); + console.error("❌ Pages deployment failed:", error); throw error; } }; @@ -332,2514 +394,83 @@ const deployPages = () => { /** * 部署Email Worker */ -const deployEmailWorker = () => { - console.log("[deploy] Deploying Email Worker..."); - try { - execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { stdio: "inherit" }); - console.log("[ok] Email Worker deployed successfully"); - } catch (error) { - console.error("[err if (missing.length > 0) { - throw new Error( - `Missing required environment variables: ${missing.join(", ")}` - ); - } -}; - -/** - * 解析 .env 文件内容为键值对对象 - * 支持无引号、单引号、双引号三种格式 - */ -const parseEnvContent = (content: string): Record => { - const result: Record = {}; - - for (const line of content.split("\n")) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) continue; - - const equalIndex = trimmed.indexOf("="); - if (equalIndex === -1) continue; - - const key = trimmed.substring(0, equalIndex).trim(); - const raw = trimmed.substring(equalIndex + 1).trim(); - // 统一去除首尾的单引号或双引号 - const value = raw.replace(/^(["'])(.*)\1$/, "$2"); - - if (key) result[key] = value; - } - - return result; -}; - -/** - * 处理JSON配置文件 - */ -const setupConfigFile = (examplePath: string, targetPath: string) => { +const deployEmailWorker = (): void => { + console.log("🚧 Deploying Email Worker..."); try { - if (existsSync(targetPath)) { - console.log(`[skip] Configuration ${targetPath} already exists.`); - return; - } - - if (!existsSync(examplePath)) { - console.log(`[warn] Example file ${examplePath} does not exist, skipping...`); - return; - } - - const json = JSON.parse(readFileSync(examplePath, "utf-8")); - - if (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - const nameMap: Record = { - "wrangler.json": PROJECT_NAME, - "wrangler.email.json": `${PROJECT_NAME}-email-receiver-worker`, - "wrangler.cleanup.json": `${PROJECT_NAME}-cleanup-worker`, - }; - if (wranglerFileName && nameMap[wranglerFileName]) { - json.name = nameMap[wranglerFileName]; - } - } - - if (json.d1_databases?.[0]) { - json.d1_databases[0].database_name = DATABASE_NAME; - } - - writeFileSync(targetPath, JSON.stringify(json, null, 2)); - console.log(`[ok] Configuration ${targetPath} setup successfully.`); + execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { + stdio: "inherit", + }); + console.log("✅ Email Worker deployed successfully"); } catch (error) { - console.error(`[error] Failed to setup ${targetPath}:`, error); - throw error; - } -}; - -/** - * 设置所有Wrangler配置文件 - */ -const setupWranglerConfigs = () => { - console.log("[setup] 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 { example, target } of configs) { - setupConfigFile(resolve(example), resolve(target)); + console.error("❌ Email Worker deployment failed:", error); + // 继续执行而不中断 } }; /** - * 更新指定 JSON 配置文件中的字段(通用) + * 部署Cleanup Worker */ -const updateJsonConfig = ( - filePath: string, - updater: (json: Record) => void -) => { - if (!existsSync(filePath)) return; +const deployCleanupWorker = (): void => { + console.log("🚧 Deploying Cleanup Worker..."); try { - const json = JSON.parse(readFileSync(filePath, "utf-8")); - updater(json); - writeFileSync(filePath, JSON.stringify(json, null, 2)); - console.log(`[ok] Updated ${filePath}`); - } catch (error) { - console.error(`[error] Failed to update ${filePath}:`, error); - } -}; - -/** - * 更新数据库ID到所有配置文件 - */ -const updateDatabaseConfig = (dbId: string) => { - console.log(`[info] Updating database ID (${dbId}) in configurations...`); - - const configFiles = ["wrangler.json", "wrangler.email.json", "wrangler.cleanup.json"]; - for (const filename of configFiles) { - updateJsonConfig(resolve(filename), (json) => { - const db = (json.d1_databases as Array>)?.[0]; - if (db) db.database_id = dbId; + 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); + // 继续执行而不中断 } }; -/** - * 更新KV命名空间ID到所有配置文件 - */ -const updateKVConfig = (namespaceId: string) => { - console.log(`[info] Updating KV namespace ID (${namespaceId}) in configurations...`); - - updateJsonConfig(resolve("wrangler.json"), (json) => { - const kv = (json.kv_namespaces as Array>)?.[0]; - if (kv) kv.id = namespaceId; - }); -}; +// ============================================ +// 环境变量管理函数 +// ============================================ /** - * 检查并创建数据库 + * 更新环境变量 */ -const checkAndCreateDatabase = async () => { - console.log(`[check] Checking if database "${DATABASE_NAME}" exists...`); - - try { - const database = await getDatabase(); - if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); +const updateEnvVar = (key: string, value: string): void => { + const envPath = resolve(".env"); + let content = ""; - updateDatabaseConfig(database.uuid); - console.log(`[ok] Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})`); - } catch (error) { - if (!(error instanceof NotFoundError)) { - console.error("[error] An error occurred while checking the database:", error); - throw error; + if (existsSync(envPath)) { + content = readFileSync(envPath, "utf-8"); + const regex = new RegExp(`^${key}=.*$`, "m"); + if (regex.test(content)) { + content = content.replace(regex, `${key}=${value}`); + } else { + content += `\n${key}=${value}`; } - - console.log("[warn] Database not found, creating new database..."); - const database = await createDatabase(); - if (!database?.uuid) throw new Error("Database object is missing a valid UUID"); - - updateDatabaseConfig(database.uuid); - console.log(`[ok] Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); + } else { + content = `${key}=${value}`; } -}; -/** - * 迁移数据库 - */ -const migrateDatabase = () => { - console.log("[info] Migrating remote database..."); - try { - execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); - console.log("[ok] Database migration completed successfully"); - } catch (error) { - console.error("[error] Database migration failed:", error); - throw error; - } + writeFileSync(envPath, content); }; /** - * 检查并创建KV命名空间 + * 创建 .env 文件 */ -const checkAndCreateKVNamespace = async () => { - console.log(`[check] Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`[ok] User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; +const setupEnvFile = (): void => { + const envPath = resolve(".env"); + if (!existsSync(envPath)) { + writeFileSync( + envPath, + `# Environment variables for ${PROJECT_NAME}\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\nCUSTOM_DOMAIN=\n` + ); + console.log("✅ Created .env file"); } +}; - try { - const namespaceList = await getKVNamespaceList(); - const existing = namespaceList.find((ns) => ns.title === KV_NAMESPACE_NAME); - - if (existing?.id) { - updateKVConfig(existing.id); - console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})`); - } else { - console.log("[warn] KV namespace not found by name, creating new KV namespace..."); - const namespace = await createKVNamespace(); - updateKVConfig(namespace.id); - console.log(`[ok] KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); - } - } catch (error) { - console.error("[error] An error occurred while checking the KV namespace:", error); - throw error; - } -}; - -/** - * 检查并创建Pages项目 - */ -const checkAndCreatePages = async () => { - console.log(`[check] Checking if project "${PROJECT_NAME}" exists...`); - - try { - await getPages(); - console.log("[ok] Project already exists, proceeding with update..."); - } catch (error) { - if (!(error instanceof NotFoundError)) { - console.error("[error] An error occurred while checking the project:", error); - throw error; - } - - console.log("[warn] Project not found, creating new project..."); - const pages = await createPages(); - - if (!CUSTOM_DOMAIN && pages.subdomain) { - console.log("[warn] CUSTOM_DOMAIN is empty, using pages default domain..."); - console.log("[info] Updating environment variables..."); - updateEnvVar("CUSTOM_DOMAIN", `https://${pages.subdomain}`); - } - } -}; - -/** - * 推送Pages密钥 - */ -const pushPagesSecret = () => { - console.log("[secret] Pushing environment secrets to Pages..."); - - const envFilePath = resolve(".env"); - if (!existsSync(envFilePath)) setupEnvFile(); - - const envVars = parseEnvContent(readFileSync(envFilePath, "utf-8")); - const secrets: Record = {}; - - for (const key of RUNTIME_ENV_VARS) { - if (envVars[key]) secrets[key] = envVars[key]; - } - - if (Object.keys(secrets).length === 0) { - console.log("[warn] No runtime secrets found to push"); - return; - } - - console.log( - `[info] Found ${Object.keys(secrets).length} secrets to push:`, - Object.keys(secrets).join(", ") - ); - - const runtimeEnvFile = resolve(".env.runtime.json"); - try { - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); - console.log("[ok] Secrets pushed successfully"); - } finally { - // 使用 unlinkSync 替代 rm 命令,避免跨平台问题 - if (existsSync(runtimeEnvFile)) { - try { - unlinkSync(runtimeEnvFile); - } catch (cleanupError) { - console.error("[warn] Failed to cleanup temporary file:", cleanupError); - } - } - } -}; - -/** - * 部署Pages应用 - */ -const deployPages = () => { - console.log("[deploy] Deploying to Cloudflare Pages..."); - try { - execSync("pnpm run deploy:pages", { stdio: "inherit" }); - console.log("[ok] Pages deployment completed successfully"); - } catch (error) { - console.error("[error] Pages deployment failed:", error); - throw error; - } -}; - -/** - * 部署Email Worker - */ -const deployEmailWorker = () => { - console.log("[deploy] Deploying Email Worker..."); - try { - execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { stdio: "inherit" }); - console.log("[ok] Email Worker deployed successfully"); - } catch (error) { - console.error("[error] Email Worker deployment failed:", error); - // 继续执行而不中断 - } -}; - -/** - * 部署Cleanup Worker - */ -const deployCleanupWorker = () => { - co if (missing.length > 0) { - throw new Error( - `Missing required environment variables: ${missing.join(", ")}` - ); - } -}; - -/** - * 解析 .env 文件内容为键值对对象 - * 支持无引号、单引号、双引号三种格式 - */ -const parseEnvContent = (content: string): Record => { - const result: Record = {}; - - for (const line of content.split("\n")) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) continue; - - const equalIndex = trimmed.indexOf("="); - if (equalIndex === -1) continue; - - const key = trimmed.substring(0, equalIndex).trim(); - const raw = trimmed.substring(equalIndex + 1).trim(); - // 统一去除首尾的单引号或双引号 - const value = raw.replace(/^(["'])(.*)\1$/, "$2"); - - if (key) result[key] = value; - } - - return result; -}; - -/** - * 处理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 json = JSON.parse(readFileSync(examplePath, "utf-8")); - - if (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - const nameMap: Record = { - "wrangler.json": PROJECT_NAME, - "wrangler.email.json": `${PROJECT_NAME}-email-receiver-worker`, - "wrangler.cleanup.json": `${PROJECT_NAME}-cleanup-worker`, - }; - if (wranglerFileName && nameMap[wranglerFileName]) { - json.name = nameMap[wranglerFileName]; - } - } - - if (json.d1_databases?.[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 { example, target } of configs) { - setupConfigFile(resolve(example), resolve(target)); - } -}; - -/** - * 更新指定 JSON 配置文件中的字段(通用) - */ -const updateJsonConfig = ( - filePath: string, - updater: (json: Record) => void -) => { - if (!existsSync(filePath)) return; - try { - const json = JSON.parse(readFileSync(filePath, "utf-8")); - updater(json); - writeFileSync(filePath, JSON.stringify(json, null, 2)); - console.log(`✅ Updated ${filePath}`); - } catch (error) { - console.error(`❌ Failed to update ${filePath}:`, error); - } -}; - -/** - * 更新数据库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) { - updateJsonConfig(resolve(filename), (json) => { - const db = (json.d1_databases as Array>)?.[0]; - if (db) db.database_id = dbId; - }); - } -}; - -/** - * 更新KV命名空间ID到所有配置文件 - */ -const updateKVConfig = (namespaceId: string) => { - console.log(`📝 Updating KV namespace ID (${namespaceId}) in configurations...`); - - updateJsonConfig(resolve("wrangler.json"), (json) => { - const kv = (json.kv_namespaces as Array>)?.[0]; - if (kv) kv.id = namespaceId; - }); -}; - -/** - * 检查并创建数据库 - */ -const checkAndCreateDatabase = async () => { - console.log(`🔍 Checking if database "${DATABASE_NAME}" exists...`); - - try { - const database = await getDatabase(); - if (!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.error("❌ An error occurred while checking the database:", error); - throw error; - } - - console.log("⚠️ Database not found, creating new database..."); - const database = await createDatabase(); - if (!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})`); - } -}; - -/** - * 迁移数据库 - */ -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...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - try { - const namespaceList = await getKVNamespaceList(); - const existing = namespaceList.find((ns) => ns.title === KV_NAMESPACE_NAME); - - if (existing?.id) { - updateKVConfig(existing.id); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${existing.id})`); - } else { - console.log("⚠️ KV namespace not found by name, creating new KV namespace..."); - const 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.error("❌ An error occurred while checking the project:", error); - throw error; - } - - 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..."); - updateEnvVar("CUSTOM_DOMAIN", `https://${pages.subdomain}`); - } - } -}; - -/** - * 推送Pages密钥 - */ -const pushPagesSecret = () => { - console.log("🔐 Pushing environment secrets to Pages..."); - - const envFilePath = resolve(".env"); - if (!existsSync(envFilePath)) setupEnvFile(); - - const envVars = parseEnvContent(readFileSync(envFilePath, "utf-8")); - const secrets: Record = {}; - - for (const key of RUNTIME_ENV_VARS) { - if (envVars[key]) secrets[key] = envVars[key]; - } - - if (Object.keys(secrets).length === 0) { - console.log("⚠️ No runtime secrets found to push"); - return; - } - - console.log( - `📝 Found ${Object.keys(secrets).length} secrets to push:`, - Object.keys(secrets).join(", ") - ); - - const runtimeEnvFile = resolve(".env.runtime.json"); - try { - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); - console.log("✅ Secrets pushed successfully"); - } finally { - // 使用 unlinkSync 替代 rm 命令,避免跨平台问题 - if (existsSync(runtimeEnvFile)) { - try { - unlinkSync(runtimeEnvFile); - } catch (cleanupError) { - console.error("⚠️ Failed to cleanup temporary file:", cleanupError); - } - } - } -}; - -/** - * 部署Pages应用 - */ -const deployPages = () => { - console.log("🚧 Deploying to Cloudflare Pages..."); - try { - execSync("pnpm run deploy:pages", { 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 */ -const updateEnvVar = (key: string, value: string) => { - const envPath = resolve(".env"); - let content = ""; - if (existsSync(envPath)) { - content = readFileSync(envPath, "utf-8"); - const regex = new RegExp(`^${key}=.*$`, "m"); - if (regex.test(content)) { - content = content.replace(regex, `${key}=${value}`); - } else { - content += `\n${key}=${value}`; - } - } else { - content = `${key}=${value}`; - } - writeFileSync(envPath, content); -}; - -/** - * 创建 .env 文件示例 - */ -const setupEnvFile = () => { - const envPath = resolve(".env"); - if (!existsSync(envPath)) { - writeFileSync( - envPath, - `# Environment variables for ${PROJECT_NAME}\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_API_TOKEN=\nCUSTOM_DOMAIN=\n` - ); - console.log("✅ Created .env file"); - } -}; - -/** - * 处理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 (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - - switch (wranglerFileName) { - case "wrangler.json": - json.name = PROJECT_NAME; - break; - case "wrangler.email.json": - json.name = `${PROJECT_NAME}-email-receiver-worker`; - break; - case "wrangler.cleanup.json": - json.name = `${PROJECT_NAME}-cleanup-worker`; - break; - } - } - - 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...`); - - 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...`); - const database = await createDatabase(); - updateDatabaseConfig(database.uuid); - console.log(`✅ Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); - } else { - console.error(`❌ An error occurred while checking the database:`, error); - throw error; - } - } -}; - -/** - * 迁移数据库 - */ -const migrateDatabase = () => { - console.log("📝 Migrating remote database..."); - execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); - console.log("✅ Database migration completed successfully"); -}; - -/** - * 检查并创建KV命名空间 - */ -const checkAndCreateKVNamespace = async () => { - console.log(`🔍 Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - const namespaceList = await getKVNamespaceList(); - let 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, creating new KV namespace..."); - namespace = await createKVNamespace(); - updateKVConfig(namespace.id); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); - } -}; - -/** - * 检查并创建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..."); - const appUrl = `https://${pages.subdomain}`; */ -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 (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - - switch (wranglerFileName) { - case "wrangler.json": - json.name = PROJECT_NAME; - break; - case "wrangler.email.json": - json.name = `${PROJECT_NAME}-email-receiver-worker`; - break; - case "wrangler.cleanup.json": - json.name = `${PROJECT_NAME}-cleanup-worker`; - break; - default: - break; - } - } - - 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...`); - - 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...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - 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..."); - - 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..."); - - const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', - 'AUTH_SECRET' - ]; - - try { - if (!existsSync(resolve('.env'))) { - setupEnvFile(); - } - - const envContent = readFileSync(resolve('.env'), 'utf-8'); - const secrets: Record = {}; - - envContent.split('\n').forEach(line => { - const trimmedLine = line.trim(); - - if (!trimmedLine || trimmedLine.startsWith('#')) { - return; - } - - const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) { - return; - } - - const key = trimmedLine.substring(0, equalIndex).trim(); - let value = trimmedLine.substring(equalIndex + 1).trim(); - - value = value.replace(/^["']|["']$/g, ''); - - if (runtimeEnvVars.includes(key) && value.length > 0) { - secrets[key] = value; - } - }); - - if (Object.keys(secrets).length === 0) { - console.log("⚠️ No runtime secrets found to push"); - return; - } - - const runtimeEnvFile = resolve('.env.runtime.json'); - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - - console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { - stdio: "inherit" - }); - - if (existsSync(runtimeEnvFile)) { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } - - console.log("✅ Secrets pushed successfully"); - } catch (error) { - console.error("❌ Failed to push secrets:", error); - - const runtimeEnvFile = resolve('.env.runtime.json'); - if (existsSync(runtimeEnvFile)) { - try { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } catch (cleanupError) { - console.error("⚠️ Failed to cleanup temporary file:", cleanupError); - } - } - throw error; - } -}; - -/** - * 部署Pages应用 - */ -const deployPages = () => { - console.log("🚧 Deploying to Cloudflare Pages..."); - try { - execSync("pnpm run deploy:pages", { stdio: "inherit" }); - console.log("✅ Pages deployment completed successfully"); - } catch (error) { */ -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 (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - - switch (wranglerFileName) { - case "wrangler.json": - json.name = PROJECT_NAME; - break; - case "wrangler.email.json": - json.name = `${PROJECT_NAME}-email-receiver-worker`; - break; - case "wrangler.cleanup.json": - json.name = `${PROJECT_NAME}-cleanup-worker`; - break; - default: - break; - } - } - - // 处理数据库配置 - 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...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - 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..."); - - // 定义运行时所需的环境变量列表 - const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', - 'AUTH_SECRET' - ]; - - try { - // 确保.env文件存在 - if (!existsSync(resolve('.env'))) { - setupEnvFile(); - } - - // 读取.env文件内容 - const envContent = readFileSync(resolve('.env'), 'utf-8'); - - // 解析环境变量为对象 - const secrets: Record = {}; - - envContent.split('\n').forEach(line => { - const trimmedLine = line.trim(); - - // 跳过注释和空行 - if (!trimmedLine || trimmedLine.startsWith('#')) { - return; - } - - // 解析键值对 - const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) { - return; - } - - const key = trimmedLine.substring(0, equalIndex).trim(); - let value = trimmedLine.substring(equalIndex + 1).trim(); - - // 移除引号 - value = value.replace(/^["']|["']$/g, ''); - - // 只保留运行时所需的环境变量,且值不为空 - if (runtimeEnvVars.includes(key) && value.length > 0) { - secrets[key] = value; - } - }); - - // 检查是否有需要推送的secrets - if (Object.keys(secrets).length === 0) { - console.log("⚠️ No runtime secrets found to push"); - return; - } - - // 创建JSON格式的临时文件 - const runtimeEnvFile = resolve('.env.runtime.json'); - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - - console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - - // 使用临时文件推送secrets - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { - stdio: "inherit" - }); - - // 清理临时文件 - if (existsSync(runtimeEnvFile)) { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } - - console.log("✅ Secrets pushed successfully"); - } catch (error) { - console.error("❌ Failed to push secrets:", error); - - // 确保清理临时文件 - const runtimeEnvFile = resolve('.env.runtime.json'); - if (existsSync(runtimeEnvFile)) { - try { - execSync(`rm ${runtimeEnvFile}` // 处理自定义项目名称 - if (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - - switch (wranglerFileName) { - case "wrangler.json": - json.name = PROJECT_NAME; - break; - case "wrangler.email.json": - json.name = `${PROJECT_NAME}-email-receiver-worker`; - break; - case "wrangler.cleanup.json": - json.name = `${PROJECT_NAME}-cleanup-worker`; - break; - default: - break; - } - } - - // 处理数据库配置 - 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...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - 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..."); - - // 定义运行时所需的环境变量列表 - const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', - 'AUTH_SECRET' - ]; - - try { - // 确保.env文件存在 - if (!existsSync(resolve('.env'))) { - setupEnvFile(); - } - - // 读取.env文件内容 - const envContent = readFileSync(resolve('.env'), 'utf-8'); - - // 解析环境变量为对象 - const secrets: Record = {}; - - envContent.split('\n').forEach(line => { - const trimmedLine = line.trim(); - - // 跳过注释和空行 - if (!trimmedLine || trimmedLine.startsWith('#')) { - return; - } - - // 解析键值对 - const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) { - return; - } - - const key = trimmedLine.substring(0, equalIndex).trim(); - let value = trimmedLine.substring(equalIndex + 1).trim(); - - // 移除引号 - value = value.replace(/^["']|["']$/g, ''); - - // 只保留运行时所需的环境变量,且值不为空 - if (runtimeEnvVars.includes(key) && value.length > 0) { - secrets[key] = value; - } - }); - - // 检查是否有需要推送的secrets - if (Object.keys(secrets).length === 0) { - console.log("⚠️ No runtime secrets found to push"); - return; - } - - // 创建JSON格式的临时文件 - const runtimeEnvFile = resolve('.env.runtime.json'); - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - - console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - - // 使用临时文件推送secrets - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { - stdio: "inherit" - }); - - // 清理临时文件 - if (existsSync(runtimeEnvFile)) { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } - - console.log("✅ Secrets pushed successfully"); - } catch (error) { - console.error("❌ Failed to push secrets:", error); - - // 确保清理临时文件 - const runtimeEnvFile = resolve('.env.runtime.json'); - if (existsSync(runtimeEnvFile)) { - try { - execSync(`rm ${runtimeEnvFile}` */ -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 (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - switch (wranglerFileName) { - case "wrangler.json": - json.name = PROJECT_NAME; - break; - case "wrangler.email.json": - json.name = `${PROJECT_NAME}-email-receiver-worker`; - break; - case "wrangler.cleanup.json": - json.name = `${PROJECT_NAME}-cleanup-worker`; - break; - default: - break; - } - } - - // 处理数据库配置 - 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...`); - 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...`); - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - 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..."); - const appUrl = `https://${pages.subdomain}`; - updateEnvVar("CUSTOM_DOMAIN", appUrl); - } - } else { - console.error(`❌ An error occurred while checking the project:`, error); - throw error; - } - } -}; - -/** - * 推送Pages密钥(在 CI 环境中暂时禁用,以避免缺少 .env.example) - */ -const pushPagesSecret = () => { - console.log("⚠️ pushPagesSecret is temporarily disabled in CI environment."); - console.log("🔐 To push secrets, please run this script locally with .env file."); - // 原有的实现被注释,如需启用请取消注释并确保 .env.example 存在 - /* - console.log("🔐 Pushing environment secrets to Pages..."); - const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', - 'AUTH_SECRET' - ]; - try { - if (!existsSync(resolve('.env'))) { - setupEnvFile(); - } - const envContent = readFileSync(resolve('.env'), 'utf-8'); - const secrets: Record = {}; - envContent.split('\n').forEach(line => { - const trimmedLine = line.trim(); - if (!trimmedLine || trimmedLine.startsWith('#')) return; - const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) return; - const key = trimmedLine.substring(0, equalIndex).trim(); - let value = trimmedLine.substring(equalIndex + 1).trim(); - value = value.replace(/^["']|["']$/g, ''); - if (runtimeEnvVars.includes(key) && value.length > 0) { - secrets[key] = value; - } - }); - if (Object.keys(secrets).length === 0) { - console.log("⚠️ No runtime secrets found to push"); - return; - } - const runtimeEnvFile = resolve('.env.runtime.json'); - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); - if (existsSync(runtimeEnvFile)) { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } - console.log("✅ Secrets pushed successfully"); - } catch (error) { - console.error("❌ Failed to push secrets:", error); - const runtimeEnvFile = resolve('.env.runtime.json'); - if (existsSync(runtimeEnvFile)) { - try { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } catch (cleanupError) { - console.error("⚠️ Failed to cleanup temporary file:", cleanupError); - } - } - throw error; - } - */ -}; - -/** - * 增强版部署Pages应用(可捕获详细错误输出) - * throw error; - } -}; - -/** - * 处理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 (PROJECT_NAME !== "moemail") { - const wranglerFileName = targetPath.split("/").at(-1); - - switch (wranglerFileName) { - case "wrangler.json": - json.name = PROJECT_NAME; - break; - case "wrangler.email.json": - json.name = `${PROJECT_NAME}-email-receiver-worker`; - break; - case "wrangler.cleanup.json": - json.name = `${PROJECT_NAME}-cleanup-worker`; - break; - default: - break; - } - } - - // 处理数据库配置 - 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...`); - - if (KV_NAMESPACE_ID) { - updateKVConfig(KV_NAMESPACE_ID); - console.log(`✅ User specified KV namespace (ID: ${KV_NAMESPACE_ID})`); - return; - } - - 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..."); - - // 定义运行时所需的环境变量列表 - const runtimeEnvVars = [ - 'AUTH_GITHUB_ID', - 'AUTH_GITHUB_SECRET', - 'AUTH_GOOGLE_ID', - 'AUTH_GOOGLE_SECRET', - 'AUTH_SECRET' - ]; - - try { - // 确保.env文件存在 - if (!existsSync(resolve('.env'))) { - setupEnvFile(); - } - - // 读取.env文件内容 - const envContent = readFileSync(resolve('.env'), 'utf-8'); - - // 解析环境变量为对象 - const secrets: Record = {}; - - envContent.split('\n').forEach(line => { - const trimmedLine = line.trim(); - - // 跳过注释和空行 - if (!trimmedLine || trimmedLine.startsWith('#')) { - return; - } - - // 解析键值对 - const equalIndex = trimmedLine.indexOf('='); - if (equalIndex === -1) { - return; - } - - const key = trimmedLine.substring(0, equalIndex).trim(); - let value = trimmedLine.substring(equalIndex + 1).trim(); - - // 移除引号 - value = value.replace(/^["']|["']$/g, ''); - - // 只保留运行时所需的环境变量,且值不为空 - if (runtimeEnvVars.includes(key) && value.length > 0) { - secrets[key] = value; - } - }); - - // 检查是否有需要推送的secrets - if (Object.keys(secrets).length === 0) { - console.log("⚠️ No runtime secrets found to push"); - return; - } - - // 创建JSON格式的临时文件 - const runtimeEnvFile = resolve('.env.runtime.json'); - writeFileSync(runtimeEnvFile, JSON.stringify(secrets, null, 2)); - - console.log(`📝 Found ${Object.keys(secrets).length} secrets to push:`, Object.keys(secrets).join(', ')); - - // 使用临时文件推送secrets - execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { - stdio: "inherit" - }); - - // 清理临时文件 - if (existsSync(runtimeEnvFile)) { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } - - console.log("✅ Secrets pushed successfully"); - } catch (error) { - console.error("❌ Failed to push secrets:", error); - - // 确保清理临时文件 - const runtimeEnvFile = resolve('.env.runtime.json'); - if (existsSync(runtimeEnvFile)) { - try { - execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); - } catch (cleanupError) { - console.error("⚠️ Failed to cleanup temporary file:", cleanupError); - } - } - - throw error; - } -}; - -/** - * 部署Pages应用 - */ -const deployPages = () => { - console.log("🚧 Deploying to Cloudflare Pages..."); - try { - execSync("pnpm run deploy:pages", { 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 () => { +const main = async (): Promise => { try { console.log("🚀 Starting deployment process..."); From 85572d1b05b77f2c3a2d1d76e1f81dc66a2415e3 Mon Sep 17 00:00:00 2001 From: uefi233 <120770538+uefi233@users.noreply.github.com> Date: Sun, 31 May 2026 10:20:25 +0800 Subject: [PATCH 20/20] Fix import statement formatting in index.ts