feat: add selectable forwarding modes and optional preview deployment

默认使用可信同源路径转发,支持界面切换隔离预览并按需部署。
This commit is contained in:
gdz66601 committed 2026-09-24 20:41:44 +08:00
1 parent fc7a0caf81
commit 5d65b34907
24 files changed
+680 -102

No files matched your search

+16 -11
View File
@@ -5,7 +5,7 @@ import { parse, stringify } from 'smol-toml';
import { ensureDatabase } from './cloudflare-d1.ts';
import { createDeploymentConfig, createPreviewDeploymentConfig, readDeploymentSettings } from './deployment-config.ts';
import { CloudflareApi, resolveAccountId } from './cloudflare-api.ts';
import { resolveWorkersDevSubdomain } from './cloudflare-access.ts';
import { resolveWorkerHostname, resolveWorkersDevSubdomain } from './cloudflare-access.ts';
import { previewOrigin } from '../src/forwarding/security.ts';
import { maskSecrets, prepareEncryptionSecret, readWorkerSecretNames, readWorkerVariable } from './deployment-secrets.ts';
import { prepareAuthentication, requiredAuthSecrets } from './deployment-auth.ts';
@@ -44,10 +44,13 @@ async function main(): Promise<void> {
settings.accountId = await resolveAccountId(api, settings.accountId);
process.env.CLOUDFLARE_ACCOUNT_ID = settings.accountId;
const existingSecrets = await readWorkerSecretNames(api, settings);
const workersSubdomain = await resolveWorkersDevSubdomain(api, settings);
const hostname = settings.customDomain || `${settings.workerName}.${workersSubdomain}.workers.dev`;
const previewHostname = settings.previewDomain || `${settings.workerName}-preview.${workersSubdomain}.workers.dev`;
const preview = previewOrigin(`https://${previewHostname}`, `https://${hostname}`);
const hostname = settings.customDomain || await resolveWorkerHostname(api, settings);
let preview = await readWorkerVariable(api, settings, 'PREVIEW_ORIGIN');
let previewHostname: string | undefined;
if (settings.deployPreview) {
previewHostname = settings.previewDomain || `${settings.workerName}-preview.${await resolveWorkersDevSubdomain(api, settings)}.workers.dev`;
preview = previewOrigin(`https://${previewHostname}`, `https://${hostname}`);
}
const database = await ensureDatabase(settings);
const workspace = await readWorkspaceState(api, settings, database);
const deployedProvider = workspace.authProvider
@@ -68,7 +71,7 @@ async function main(): Promise<void> {
const secrets = { ...authentication.secrets, ...await prepareEncryptionSecret(api, settings, database, existingSecrets) };
maskSecrets(secrets);
const config = createDeploymentConfig(template, settings, database, {
hostname, previewOrigin: preview, githubAdminId: authentication.githubAdminId,
hostname, ...(preview ? { previewOrigin: preview } : {}), githubAdminId: authentication.githubAdminId,
});
// 临时配置放在仓库根目录,保持 assets、main、migrations_dir 的相对路径语义。
await writeFile(new URL(`../${generatedConfig}`, import.meta.url), stringify(config), 'utf8');
@@ -84,13 +87,15 @@ async function main(): Promise<void> {
if (!deployedSecrets.has(name)) throw new Error(`部署后缺少 Worker Secret:${name}。`);
}
// 预览 Worker 只做代理入口和 DO 绑定,不执行迁移、不写入任何 Secret。
const previewConfig = createPreviewDeploymentConfig(template, settings, preview);
await writeFile(new URL(`../${previewGeneratedConfig}`, import.meta.url), stringify(previewConfig), 'utf8');
await runWrangler(['deploy'], undefined, previewGeneratedConfig);
console.log(`部署完成:https://${hostname},预览:https://${previewHostname}`);
if (settings.deployPreview) {
const previewConfig = createPreviewDeploymentConfig(template, settings, preview!);
await writeFile(new URL(`../${previewGeneratedConfig}`, import.meta.url), stringify(previewConfig), 'utf8');
await runWrangler(['deploy'], undefined, previewGeneratedConfig);
}
console.log(`部署完成:https://${hostname}${settings.deployPreview ? `,预览:https://${previewHostname}` : '(仅主 Worker)'}`);
if (process.env.GITHUB_STEP_SUMMARY) {
await appendFile(process.env.GITHUB_STEP_SUMMARY,
`## EdgeSSH 部署完成\n\n入口:https://${hostname}\n\n登录方式:${settings.authProvider}\n\n`
`## EdgeSSH 部署完成\n\n入口:https://${hostname}\n\n${settings.deployPreview ? `预览:https://${previewHostname}\n\n` : ''}登录方式:${settings.authProvider}\n\n`
+ (settings.authProvider === 'github' ? `GitHub OAuth 回调地址:https://${hostname}/auth/callback\n\n` : '')
+ 'D1 已迁移,运行时 Secret 已保存在 Cloudflare。后续部署保留原加密密钥与管理员资料。\n');
}
+9 -5
View File
@@ -11,6 +11,7 @@ export interface DeploymentSettings {
databaseId?: string;
customDomain?: string;
previewDomain?: string;
deployPreview?: boolean;
authProvider: 'cloudflare' | 'github';
githubClientId?: string;
githubAdmin?: string;
@@ -39,7 +40,10 @@ export function readDeploymentSettings(
if (accountId && !/^[a-f0-9]{32}$/i.test(accountId)) throw new Error('CLOUDFLARE_ACCOUNT_ID 必须是 32 位十六进制账户 ID。');
const workerName = env.WORKER_NAME?.trim() || String(template.name);
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(workerName)) throw new Error('WORKER_NAME 必须是 1–63 位小写字母、数字或连字符,且不能以连字符开头。');
if (`${workerName}-preview`.length > 63) throw new Error('WORKER_NAME 加上 -preview 后不能超过 63 位。');
const deployPreviewValue = env.DEPLOY_PREVIEW_WORKER?.trim().toLowerCase() || 'false';
if (deployPreviewValue !== 'true' && deployPreviewValue !== 'false') throw new Error('DEPLOY_PREVIEW_WORKER 只能是 true 或 false。');
const deployPreview = deployPreviewValue === 'true';
if (deployPreview && `${workerName}-preview`.length > 63) throw new Error('WORKER_NAME 加上 -preview 后不能超过 63 位。');
const databases = template.d1_databases as TomlTable[] | undefined;
const database = databases?.find((binding) => binding.binding === 'DB');
@@ -59,11 +63,11 @@ export function readDeploymentSettings(
if (customDomain?.toLowerCase().endsWith('.workers.dev')) {
throw new Error('CUSTOM_DOMAIN 不能填写 workers.dev 地址;使用 Worker 自带域名时请删除或留空该配置。');
}
const previewDomain = env.PREVIEW_DOMAIN?.trim();
if (previewDomain && !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i.test(previewDomain)) {
const previewDomain = deployPreview ? env.PREVIEW_DOMAIN?.trim() : undefined;
if (deployPreview && previewDomain && !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i.test(previewDomain)) {
throw new Error('PREVIEW_DOMAIN 只能填写完整域名,不能包含协议、路径或通配符。');
}
if (previewDomain?.toLowerCase().endsWith('.workers.dev')) {
if (deployPreview && previewDomain?.toLowerCase().endsWith('.workers.dev')) {
throw new Error('PREVIEW_DOMAIN 不能填写 workers.dev 地址;请使用自定义跨站域名,或留空自动生成预览地址。');
}
@@ -107,7 +111,7 @@ export function readDeploymentSettings(
return {
accountId, apiToken: env.CLOUDFLARE_API_TOKEN!.trim(), workerName,
databaseName, databaseId, customDomain, previewDomain, authProvider, githubClientId, githubAdmin, githubAdminId,
databaseName, databaseId, customDomain, previewDomain, deployPreview, authProvider, githubClientId, githubAdmin, githubAdminId,
adminEmail, identityProviderIds, secrets,
};
}