mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 14:44:48 +00:00
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 <tukon479@gmail.com>
This commit is contained in:
@@ -1,15 +1,10 @@
|
|||||||
import os from 'node:os'
|
import os from 'node:os'
|
||||||
|
|
||||||
import { defineBuilderConfig } from '@afilmory/builder'
|
import { defineBuilderConfig, githubRepoSyncPlugin } from '@afilmory/builder'
|
||||||
|
|
||||||
import { env } from './env.js'
|
import { env } from './env.js'
|
||||||
|
|
||||||
export default defineBuilderConfig(() => ({
|
export default defineBuilderConfig(() => ({
|
||||||
repo: {
|
|
||||||
enable: false,
|
|
||||||
url: process.env.BUILDER_REPO_URL ?? '',
|
|
||||||
token: env.GIT_TOKEN,
|
|
||||||
},
|
|
||||||
storage: {
|
storage: {
|
||||||
// "provider": "local",
|
// "provider": "local",
|
||||||
// "basePath": "./apps/web/public/photos",
|
// "basePath": "./apps/web/public/photos",
|
||||||
@@ -61,5 +56,14 @@ export default defineBuilderConfig(() => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// plugins: [thumbnailStoragePlugin()],
|
// 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',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -658,10 +658,6 @@ export class AfilmoryBuilder {
|
|||||||
addReference(ref)
|
addReference(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getUserSettings().repo?.enable && !hasPluginWithName('afilmory:github-repo-sync')) {
|
|
||||||
addReference(() => import('@afilmory/builder/plugins/github-repo-sync.js'))
|
|
||||||
}
|
|
||||||
|
|
||||||
return references
|
return references
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,10 +59,6 @@ async function main() {
|
|||||||
在 builder.config.ts 中设置 performance.worker.useClusterMode = true
|
在 builder.config.ts 中设置 performance.worker.useClusterMode = true
|
||||||
可启用多进程集群模式,发挥多核心优势。
|
可启用多进程集群模式,发挥多核心优势。
|
||||||
|
|
||||||
远程仓库:
|
|
||||||
如果启用了远程仓库 (repo.enable = true),构建完成后会自动推送更新。
|
|
||||||
需要配置 repo.token 或设置 GIT_TOKEN 环境变量以提供推送权限。
|
|
||||||
如果没有提供 token,将跳过推送步骤。
|
|
||||||
`)
|
`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -105,16 +101,9 @@ async function main() {
|
|||||||
logger.main.info(` 集群模式:${config.system.observability.performance.worker.useClusterMode ? '启用' : '禁用'}`)
|
logger.main.info(` 集群模式:${config.system.observability.performance.worker.useClusterMode ? '启用' : '禁用'}`)
|
||||||
logger.main.info('')
|
logger.main.info('')
|
||||||
if (!userConfig) {
|
if (!userConfig) {
|
||||||
logger.main.warn('未配置用户级设置(repo/storage)')
|
logger.main.warn('未配置用户级存储设置')
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,11 +56,6 @@ function applySystemOverrides(target: BuilderConfig['system'], overrides?: Build
|
|||||||
function ensureUserSettings(target: BuilderConfig): UserBuilderSettings {
|
function ensureUserSettings(target: BuilderConfig): UserBuilderSettings {
|
||||||
if (!target.user) {
|
if (!target.user) {
|
||||||
target.user = {
|
target.user = {
|
||||||
repo: {
|
|
||||||
enable: false,
|
|
||||||
url: '',
|
|
||||||
token: '',
|
|
||||||
},
|
|
||||||
storage: null,
|
storage: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +66,6 @@ function applyUserOverrides(target: BuilderConfig, overrides?: BuilderConfigInpu
|
|||||||
if (!overrides) return
|
if (!overrides) return
|
||||||
const user = ensureUserSettings(target)
|
const user = ensureUserSettings(target)
|
||||||
|
|
||||||
if (overrides.repo) {
|
|
||||||
user.repo = {
|
|
||||||
...user.repo,
|
|
||||||
...overrides.repo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (overrides.storage !== undefined) {
|
if (overrides.storage !== undefined) {
|
||||||
user.storage = overrides.storage as StorageConfig | null
|
user.storage = overrides.storage as StorageConfig | null
|
||||||
}
|
}
|
||||||
@@ -88,14 +77,6 @@ function normalizeBuilderConfig(defaults: BuilderConfig, input: BuilderConfigInp
|
|||||||
applySystemOverrides(next.system, input.system)
|
applySystemOverrides(next.system, input.system)
|
||||||
applyUserOverrides(next, input.user)
|
applyUserOverrides(next, input.user)
|
||||||
|
|
||||||
if (input.repo) {
|
|
||||||
const user = ensureUserSettings(next)
|
|
||||||
user.repo = {
|
|
||||||
...user.repo,
|
|
||||||
...input.repo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.storage !== undefined) {
|
if (input.storage !== undefined) {
|
||||||
ensureUserSettings(next).storage = input.storage ?? null
|
ensureUserSettings(next).storage = input.storage ?? null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,30 +10,35 @@ import type { BuilderPlugin } from './types.js'
|
|||||||
const RUN_SHARED_ASSETS_DIR = 'assetsGitDir'
|
const RUN_SHARED_ASSETS_DIR = 'assetsGitDir'
|
||||||
|
|
||||||
export interface GitHubRepoSyncPluginOptions {
|
export interface GitHubRepoSyncPluginOptions {
|
||||||
|
repo: {
|
||||||
|
enable: boolean
|
||||||
|
url: string
|
||||||
|
token?: string
|
||||||
|
branch?: string
|
||||||
|
}
|
||||||
autoPush?: boolean
|
autoPush?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOptions = {}): BuilderPlugin {
|
export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOptions): BuilderPlugin {
|
||||||
const autoPush = options.autoPush ?? true
|
const autoPush = options.autoPush ?? true
|
||||||
|
const repoConfig = options.repo
|
||||||
|
|
||||||
|
if (!repoConfig) {
|
||||||
|
throw new Error('githubRepoSyncPlugin 需要 repo 配置')
|
||||||
|
}
|
||||||
|
|
||||||
|
const branchName = repoConfig.branch?.trim() || 'main'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'afilmory:github-repo-sync',
|
name: 'afilmory:github-repo-sync',
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeBuild: async (context) => {
|
beforeBuild: async (context) => {
|
||||||
const userConfig = context.config.user
|
if (!repoConfig.enable) {
|
||||||
if (!userConfig) {
|
|
||||||
context.logger.main.warn('⚠️ 未配置用户级设置,跳过远程仓库同步')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userConfig.repo.enable) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { logger } = context
|
const { logger } = context
|
||||||
const { repo } = userConfig
|
if (!repoConfig.url) {
|
||||||
|
|
||||||
if (!repo.url) {
|
|
||||||
logger.main.warn('⚠️ 未配置远程仓库地址,跳过同步')
|
logger.main.warn('⚠️ 未配置远程仓库地址,跳过同步')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -43,7 +48,7 @@ export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOption
|
|||||||
|
|
||||||
logger.main.info('🔄 同步远程仓库...')
|
logger.main.info('🔄 同步远程仓库...')
|
||||||
|
|
||||||
const repoUrl = buildAuthenticatedRepoUrl(repo.url, repo.token)
|
const repoUrl = buildAuthenticatedRepoUrl(repoConfig.url, repoConfig.token)
|
||||||
|
|
||||||
if (!existsSync(assetsGitDir)) {
|
if (!existsSync(assetsGitDir)) {
|
||||||
logger.main.info('📥 克隆远程仓库...')
|
logger.main.info('📥 克隆远程仓库...')
|
||||||
@@ -67,17 +72,12 @@ export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOption
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ensureRepositoryBranch({ assetsGitDir, branchName, logger })
|
||||||
await prepareRepositoryLayout({ assetsGitDir, logger })
|
await prepareRepositoryLayout({ assetsGitDir, logger })
|
||||||
logger.main.success('✅ 远程仓库同步完成')
|
logger.main.success('✅ 远程仓库同步完成')
|
||||||
},
|
},
|
||||||
afterBuild: async (context) => {
|
afterBuild: async (context) => {
|
||||||
const userConfig = context.config.user
|
if (!autoPush || !repoConfig.enable) {
|
||||||
if (!userConfig) {
|
|
||||||
context.logger.main.warn('⚠️ 未配置用户级设置,跳过推送')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!autoPush || !userConfig.repo.enable) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,8 @@ export default function githubRepoSyncPlugin(options: GitHubRepoSyncPluginOption
|
|||||||
await pushUpdatesToRemoteRepo({
|
await pushUpdatesToRemoteRepo({
|
||||||
assetsGitDir,
|
assetsGitDir,
|
||||||
logger: context.logger,
|
logger: context.logger,
|
||||||
repoConfig: userConfig.repo,
|
repoConfig,
|
||||||
|
branchName,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -152,9 +153,15 @@ interface PushRemoteOptions {
|
|||||||
url: string
|
url: string
|
||||||
token?: string
|
token?: string
|
||||||
}
|
}
|
||||||
|
branchName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pushUpdatesToRemoteRepo({ assetsGitDir, logger, repoConfig }: PushRemoteOptions): Promise<void> {
|
async function pushUpdatesToRemoteRepo({
|
||||||
|
assetsGitDir,
|
||||||
|
logger,
|
||||||
|
repoConfig,
|
||||||
|
branchName,
|
||||||
|
}: PushRemoteOptions): Promise<void> {
|
||||||
if (!repoConfig.url) {
|
if (!repoConfig.url) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -194,7 +201,7 @@ async function pushUpdatesToRemoteRepo({ assetsGitDir, logger, repoConfig }: Pus
|
|||||||
cwd: assetsGitDir,
|
cwd: assetsGitDir,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
})`git commit -m ${commitMessage}`
|
})`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('✅ 成功推送更新到远程仓库')
|
logger.main.success('✅ 成功推送更新到远程仓库')
|
||||||
}
|
}
|
||||||
@@ -214,6 +221,71 @@ async function ensureGitUserConfigured(assetsGitDir: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EnsureRepositoryBranchOptions {
|
||||||
|
assetsGitDir: string
|
||||||
|
branchName: string
|
||||||
|
logger: typeof import('../logger/index.js').logger
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureRepositoryBranch({
|
||||||
|
assetsGitDir,
|
||||||
|
branchName,
|
||||||
|
logger,
|
||||||
|
}: EnsureRepositoryBranchOptions): Promise<void> {
|
||||||
|
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<string | null> {
|
||||||
|
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<boolean> {
|
||||||
|
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<boolean> {
|
||||||
|
try {
|
||||||
|
await $({ cwd: assetsGitDir, stdio: 'pipe' })`git rev-parse --verify origin/${branchName}`
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildAuthenticatedRepoUrl(url: string, token?: string): string {
|
function buildAuthenticatedRepoUrl(url: string, token?: string): string {
|
||||||
if (!token) return url
|
if (!token) return url
|
||||||
|
|
||||||
@@ -226,6 +298,6 @@ function buildAuthenticatedRepoUrl(url: string, token?: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const plugin = githubRepoSyncPlugin
|
export const plugin = githubRepoSyncPlugin
|
||||||
export function createGitHubRepoSyncPlugin(options?: GitHubRepoSyncPluginOptions): BuilderPlugin {
|
export function createGitHubRepoSyncPlugin(options: GitHubRepoSyncPluginOptions): BuilderPlugin {
|
||||||
return githubRepoSyncPlugin(options)
|
return githubRepoSyncPlugin(options)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import type { BuilderPluginConfigEntry } from '../plugins/types.js'
|
import type { BuilderPluginConfigEntry } from '../plugins/types.js'
|
||||||
import type { StorageConfig } from '../storage/interfaces.js'
|
import type { StorageConfig } from '../storage/interfaces.js'
|
||||||
|
|
||||||
export interface BuilderRepoSettings {
|
|
||||||
enable: boolean
|
|
||||||
url: string
|
|
||||||
token?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoggingConfig {
|
export interface LoggingConfig {
|
||||||
verbose: boolean
|
verbose: boolean
|
||||||
level: 'info' | 'warn' | 'error' | 'debug'
|
level: 'info' | 'warn' | 'error' | 'debug'
|
||||||
@@ -43,7 +37,6 @@ export interface SystemBuilderSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UserBuilderSettings {
|
export interface UserBuilderSettings {
|
||||||
repo: BuilderRepoSettings
|
|
||||||
storage: StorageConfig | null
|
storage: StorageConfig | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +52,7 @@ type DeepPartial<T> = T extends object
|
|||||||
}
|
}
|
||||||
: T
|
: T
|
||||||
|
|
||||||
export type BuilderConfigInput = DeepPartial<Omit<UserBuilderSettings, 'storage'>> & {
|
export type BuilderConfigInput = {
|
||||||
storage?: StorageConfig | null
|
storage?: StorageConfig | null
|
||||||
user?: DeepPartial<UserBuilderSettings>
|
user?: DeepPartial<UserBuilderSettings>
|
||||||
system?: DeepPartial<SystemBuilderSettings>
|
system?: DeepPartial<SystemBuilderSettings>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
title: Docker
|
title: Docker
|
||||||
description: Guide to deploying Afilmory via Docker.
|
description: Guide to deploying Afilmory via Docker.
|
||||||
createdAt: 2025-07-20T22:35:03+08:00
|
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
|
# Docker Deployment
|
||||||
@@ -53,13 +53,9 @@ Before building your Docker image, you'll need to configure the following files:
|
|||||||
**`builder.config.ts`**
|
**`builder.config.ts`**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { defineBuilderConfig } from '@afilmory/builder'
|
import { defineBuilderConfig, githubRepoSyncPlugin } from '@afilmory/builder'
|
||||||
|
|
||||||
export default defineBuilderConfig(() => ({
|
export default defineBuilderConfig(() => ({
|
||||||
repo: {
|
|
||||||
enable: false,
|
|
||||||
url: 'https://github.com/username/gallery-public',
|
|
||||||
},
|
|
||||||
storage: {
|
storage: {
|
||||||
provider: 's3',
|
provider: 's3',
|
||||||
bucket: 'your-photos-bucket',
|
bucket: 'your-photos-bucket',
|
||||||
@@ -67,6 +63,16 @@ export default defineBuilderConfig(() => ({
|
|||||||
prefix: 'photos/',
|
prefix: 'photos/',
|
||||||
customDomain: 'cdn.yourdomain.com',
|
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: {
|
performance: {
|
||||||
worker: {
|
worker: {
|
||||||
enabled: true,
|
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`**
|
**`.env`**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Reference in New Issue
Block a user