From 3e60b382e16da2e8d8779ab80979456317ebfb20 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Nov 2025 00:23:26 +0800 Subject: [PATCH] feat(builder): integrate GitHub repository synchronization plugin and update configuration - Added `githubRepoSyncPlugin` to the builder configuration, enabling synchronization with a GitHub repository. - Removed deprecated repository settings from user configuration and streamlined the plugin's integration. - Updated CLI and documentation to reflect changes in repository configuration handling, enhancing clarity for users. Signed-off-by: Innei --- builder.config.default.ts | 18 +-- packages/builder/src/builder/builder.ts | 4 - packages/builder/src/cli.ts | 13 +- packages/builder/src/config/index.ts | 19 --- .../builder/src/plugins/github-repo-sync.ts | 118 ++++++++++++++---- packages/builder/src/types/config.ts | 9 +- packages/docs/contents/deployment/docker.mdx | 20 ++- 7 files changed, 122 insertions(+), 79 deletions(-) diff --git a/builder.config.default.ts b/builder.config.default.ts index 8d37a278..3d72805d 100644 --- a/builder.config.default.ts +++ b/builder.config.default.ts @@ -1,15 +1,10 @@ import os from 'node:os' -import { defineBuilderConfig } from '@afilmory/builder' +import { defineBuilderConfig, githubRepoSyncPlugin } from '@afilmory/builder' import { env } from './env.js' export default defineBuilderConfig(() => ({ - repo: { - enable: false, - url: process.env.BUILDER_REPO_URL ?? '', - token: env.GIT_TOKEN, - }, storage: { // "provider": "local", // "basePath": "./apps/web/public/photos", @@ -61,5 +56,14 @@ export default defineBuilderConfig(() => ({ }, }, // plugins: [thumbnailStoragePlugin()], - plugins: [], + plugins: [ + githubRepoSyncPlugin({ + repo: { + enable: false, + url: process.env.BUILDER_REPO_URL ?? '', + token: env.GIT_TOKEN, + branch: process.env.BUILDER_REPO_BRANCH ?? 'main', + }, + }), + ], })) diff --git a/packages/builder/src/builder/builder.ts b/packages/builder/src/builder/builder.ts index b18f87dd..69163233 100644 --- a/packages/builder/src/builder/builder.ts +++ b/packages/builder/src/builder/builder.ts @@ -658,10 +658,6 @@ export class AfilmoryBuilder { addReference(ref) } - if (this.getUserSettings().repo?.enable && !hasPluginWithName('afilmory:github-repo-sync')) { - addReference(() => import('@afilmory/builder/plugins/github-repo-sync.js')) - } - return references } diff --git a/packages/builder/src/cli.ts b/packages/builder/src/cli.ts index c8057813..b4382b32 100644 --- a/packages/builder/src/cli.ts +++ b/packages/builder/src/cli.ts @@ -59,10 +59,6 @@ async function main() { 在 builder.config.ts 中设置 performance.worker.useClusterMode = true 可启用多进程集群模式,发挥多核心优势。 -远程仓库: - 如果启用了远程仓库 (repo.enable = true),构建完成后会自动推送更新。 - 需要配置 repo.token 或设置 GIT_TOKEN 环境变量以提供推送权限。 - 如果没有提供 token,将跳过推送步骤。 `) return } @@ -105,16 +101,9 @@ async function main() { logger.main.info(` 集群模式:${config.system.observability.performance.worker.useClusterMode ? '启用' : '禁用'}`) logger.main.info('') if (!userConfig) { - logger.main.warn('未配置用户级设置(repo/storage)') + logger.main.warn('未配置用户级存储设置') return } - - logger.main.info('📦 远程仓库配置:') - logger.main.info(` 启用状态:${userConfig.repo.enable ? '启用' : '禁用'}`) - if (userConfig.repo.enable) { - logger.main.info(` 仓库地址:${userConfig.repo.url || '未设置'}`) - logger.main.info(` 推送权限:${userConfig.repo.token ? '已配置' : '未配置'}`) - } return } diff --git a/packages/builder/src/config/index.ts b/packages/builder/src/config/index.ts index c1faa1b6..9bf05f87 100644 --- a/packages/builder/src/config/index.ts +++ b/packages/builder/src/config/index.ts @@ -56,11 +56,6 @@ function applySystemOverrides(target: BuilderConfig['system'], overrides?: Build function ensureUserSettings(target: BuilderConfig): UserBuilderSettings { if (!target.user) { target.user = { - repo: { - enable: false, - url: '', - token: '', - }, storage: null, } } @@ -71,12 +66,6 @@ function applyUserOverrides(target: BuilderConfig, overrides?: BuilderConfigInpu if (!overrides) return const user = ensureUserSettings(target) - if (overrides.repo) { - user.repo = { - ...user.repo, - ...overrides.repo, - } - } if (overrides.storage !== undefined) { user.storage = overrides.storage as StorageConfig | null } @@ -88,14 +77,6 @@ function normalizeBuilderConfig(defaults: BuilderConfig, input: BuilderConfigInp applySystemOverrides(next.system, input.system) applyUserOverrides(next, input.user) - if (input.repo) { - const user = ensureUserSettings(next) - user.repo = { - ...user.repo, - ...input.repo, - } - } - if (input.storage !== undefined) { ensureUserSettings(next).storage = input.storage ?? null } diff --git a/packages/builder/src/plugins/github-repo-sync.ts b/packages/builder/src/plugins/github-repo-sync.ts index 6c0bc41d..6fa79793 100644 --- a/packages/builder/src/plugins/github-repo-sync.ts +++ b/packages/builder/src/plugins/github-repo-sync.ts @@ -10,30 +10,35 @@ import type { BuilderPlugin } from './types.js' const RUN_SHARED_ASSETS_DIR = 'assetsGitDir' export interface GitHubRepoSyncPluginOptions { + repo: { + enable: boolean + url: string + token?: string + branch?: string + } autoPush?: boolean } -export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOptions = {}): BuilderPlugin { +export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOptions): BuilderPlugin { const autoPush = options.autoPush ?? true + const repoConfig = options.repo + + if (!repoConfig) { + throw new Error('githubRepoSyncPlugin 需要 repo 配置') + } + + const branchName = repoConfig.branch?.trim() || 'main' return { name: 'afilmory:github-repo-sync', hooks: { beforeBuild: async (context) => { - const userConfig = context.config.user - if (!userConfig) { - context.logger.main.warn('⚠️ 未配置用户级设置,跳过远程仓库同步') - return - } - - if (!userConfig.repo.enable) { + if (!repoConfig.enable) { return } const { logger } = context - const { repo } = userConfig - - if (!repo.url) { + if (!repoConfig.url) { logger.main.warn('⚠️ 未配置远程仓库地址,跳过同步') return } @@ -43,7 +48,7 @@ export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOption logger.main.info('🔄 同步远程仓库...') - const repoUrl = buildAuthenticatedRepoUrl(repo.url, repo.token) + const repoUrl = buildAuthenticatedRepoUrl(repoConfig.url, repoConfig.token) if (!existsSync(assetsGitDir)) { logger.main.info('📥 克隆远程仓库...') @@ -67,17 +72,12 @@ export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOption } } + await ensureRepositoryBranch({ assetsGitDir, branchName, logger }) await prepareRepositoryLayout({ assetsGitDir, logger }) logger.main.success('✅ 远程仓库同步完成') }, afterBuild: async (context) => { - const userConfig = context.config.user - if (!userConfig) { - context.logger.main.warn('⚠️ 未配置用户级设置,跳过推送') - return - } - - if (!autoPush || !userConfig.repo.enable) { + if (!autoPush || !repoConfig.enable) { return } @@ -97,7 +97,8 @@ export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOption await pushUpdatesToRemoteRepo({ assetsGitDir, logger: context.logger, - repoConfig: userConfig.repo, + repoConfig, + branchName, }) }, }, @@ -152,9 +153,15 @@ interface PushRemoteOptions { url: string token?: string } + branchName: string } -async function pushUpdatesToRemoteRepo({ assetsGitDir, logger, repoConfig }: PushRemoteOptions): Promise { +async function pushUpdatesToRemoteRepo({ + assetsGitDir, + logger, + repoConfig, + branchName, +}: PushRemoteOptions): Promise { if (!repoConfig.url) { return } @@ -194,7 +201,7 @@ async function pushUpdatesToRemoteRepo({ assetsGitDir, logger, repoConfig }: Pus cwd: assetsGitDir, stdio: 'inherit', })`git commit -m ${commitMessage}` - await $({ cwd: assetsGitDir, stdio: 'inherit' })`git push origin HEAD` + await $({ cwd: assetsGitDir, stdio: 'inherit' })`git push -u origin HEAD:${branchName}` logger.main.success('✅ 成功推送更新到远程仓库') } @@ -214,6 +221,71 @@ async function ensureGitUserConfigured(assetsGitDir: string): Promise { } } +interface EnsureRepositoryBranchOptions { + assetsGitDir: string + branchName: string + logger: typeof import('../logger/index.js').logger +} + +async function ensureRepositoryBranch({ + assetsGitDir, + branchName, + logger, +}: EnsureRepositoryBranchOptions): Promise { + const currentBranch = await getCurrentBranch(assetsGitDir) + + if (currentBranch === branchName) { + return + } + + const hasLocalBranch = await branchExistsLocally(assetsGitDir, branchName) + if (hasLocalBranch) { + logger.main.info(`🔀 切换到分支 ${branchName}`) + await $({ cwd: assetsGitDir, stdio: 'inherit' })`git checkout ${branchName}` + return + } + + if (await remoteBranchExists(assetsGitDir, branchName)) { + logger.main.info(`🔄 检出远程分支 ${branchName}`) + await $({ cwd: assetsGitDir, stdio: 'inherit' })`git checkout -b ${branchName} origin/${branchName}` + return + } + + logger.main.info(`🌱 创建新分支 ${branchName}`) + await $({ cwd: assetsGitDir, stdio: 'inherit' })`git checkout -b ${branchName}` +} + +async function getCurrentBranch(assetsGitDir: string): Promise { + try { + const { stdout } = await $({ cwd: assetsGitDir, stdio: 'pipe' })`git rev-parse --abbrev-ref HEAD` + const branch = stdout.trim() + if (!branch || branch === 'HEAD') { + return null + } + return branch + } catch { + return null + } +} + +async function branchExistsLocally(assetsGitDir: string, branchName: string): Promise { + try { + await $({ cwd: assetsGitDir, stdio: 'pipe' })`git show-ref --verify --quiet refs/heads/${branchName}` + return true + } catch { + return false + } +} + +async function remoteBranchExists(assetsGitDir: string, branchName: string): Promise { + try { + await $({ cwd: assetsGitDir, stdio: 'pipe' })`git rev-parse --verify origin/${branchName}` + return true + } catch { + return false + } +} + function buildAuthenticatedRepoUrl(url: string, token?: string): string { if (!token) return url @@ -226,6 +298,6 @@ function buildAuthenticatedRepoUrl(url: string, token?: string): string { } export const plugin = githubRepoSyncPlugin -export function createGitHubRepoSyncPlugin(options?: GitHubRepoSyncPluginOptions): BuilderPlugin { +export function createGitHubRepoSyncPlugin(options: GitHubRepoSyncPluginOptions): BuilderPlugin { return githubRepoSyncPlugin(options) } diff --git a/packages/builder/src/types/config.ts b/packages/builder/src/types/config.ts index 5a81d790..477648dd 100644 --- a/packages/builder/src/types/config.ts +++ b/packages/builder/src/types/config.ts @@ -1,12 +1,6 @@ import type { BuilderPluginConfigEntry } from '../plugins/types.js' import type { StorageConfig } from '../storage/interfaces.js' -export interface BuilderRepoSettings { - enable: boolean - url: string - token?: string -} - export interface LoggingConfig { verbose: boolean level: 'info' | 'warn' | 'error' | 'debug' @@ -43,7 +37,6 @@ export interface SystemBuilderSettings { } export interface UserBuilderSettings { - repo: BuilderRepoSettings storage: StorageConfig | null } @@ -59,7 +52,7 @@ type DeepPartial = T extends object } : T -export type BuilderConfigInput = DeepPartial> & { +export type BuilderConfigInput = { storage?: StorageConfig | null user?: DeepPartial system?: DeepPartial diff --git a/packages/docs/contents/deployment/docker.mdx b/packages/docs/contents/deployment/docker.mdx index bd2d9bed..2e038b90 100644 --- a/packages/docs/contents/deployment/docker.mdx +++ b/packages/docs/contents/deployment/docker.mdx @@ -2,7 +2,7 @@ title: Docker description: Guide to deploying Afilmory via Docker. createdAt: 2025-07-20T22:35:03+08:00 -lastModified: 2025-10-28T19:48:05+08:00 +lastModified: 2025-11-14T00:23:27+08:00 --- # Docker Deployment @@ -53,13 +53,9 @@ Before building your Docker image, you'll need to configure the following files: **`builder.config.ts`** ```ts -import { defineBuilderConfig } from '@afilmory/builder' +import { defineBuilderConfig, githubRepoSyncPlugin } from '@afilmory/builder' export default defineBuilderConfig(() => ({ - repo: { - enable: false, - url: 'https://github.com/username/gallery-public', - }, storage: { provider: 's3', bucket: 'your-photos-bucket', @@ -67,6 +63,16 @@ export default defineBuilderConfig(() => ({ prefix: 'photos/', customDomain: 'cdn.yourdomain.com', }, + plugins: [ + githubRepoSyncPlugin({ + repo: { + enable: true, + url: 'https://github.com/username/gallery-public', + token: process.env.GIT_TOKEN, + branch: process.env.BUILDER_REPO_BRANCH ?? 'main', + }, + }), + ], performance: { worker: { enabled: true, @@ -76,6 +82,8 @@ export default defineBuilderConfig(() => ({ })) ``` +If you keep assets on a non-`main` branch, set `branch` accordingly; the plugin will also auto-create the specified branch when it doesn’t exist yet so first pushes succeed even for empty repositories. + **`.env`** ```bash