diff --git a/.specstory/history/2025-06-26_17-14-更新流程和-git-上传配置.md b/.specstory/history/2025-06-26_17-14-更新流程和-git-上传配置.md new file mode 100644 index 00000000..12af889e --- /dev/null +++ b/.specstory/history/2025-06-26_17-14-更新流程和-git-上传配置.md @@ -0,0 +1,742 @@ + + +# 更新流程和 git 上传配置 (2025-06-27 01:14:44) + +_**User**_ + +这里需要 pull或者 clone 之后,再跑一下增量更新的流程,如果有更新,还需要吧更新之后生成的 manifest 的上传到 git 仓库。 +上传到 git 仓库需要提供 api token,如果没有就跳过。 +记得增加 logger 输出 + +--- + +_**Assistant**_ + +我需要查看一下相关的配置文件和构建器代码,了解当前的结构,然后修改这段代码来实现你要求的功能。 + +--- + +
+ Searched codebase "builderConfig repo" • **24** results + +| File | Lines | +|------|-------| +| `builder.config.example.json` | L1-6 | +| `builder.config.ts` | L71-113 | +| `packages/builder/src/storage/interfaces.ts` | L59-69 | +| `packages/builder/src/builder/builder.ts` | L42-64 | +| `builder.config.ts` | L128-130 | +| `builder.config.ts` | L1-71 | +| `builder.config.ts` | L113-128 | +| `scripts/preinstall.sh` | L1-3 | +| `packages/builder/src/storage/interfaces.ts` | L69-70 | +| `builder.config.ts` | L130-136 | +| `packages/builder/src/storage/interfaces.ts` | L46-59 | +| `README.md` | L219-311 | +| `packages/builder/src/builder/builder.ts` | L362-369 | +| `packages/builder/tsconfig.json` | L1-43 | +| `packages/builder/src/builder/builder.ts` | L27-42 | +| `apps/web/tsconfig.json` | L1-48 | +| `packages/builder/src/index.ts` | L1-2 | +| `packages/builder/src/index.ts` | L2-3 | +| `packages/builder/src/builder/builder.ts` | L1-27 | +| `packages/builder/src/storage/factory.ts` | L1-21 | +| `packages/builder/src/builder/index.ts` | L1-2 | +| `README.md` | L123-219 | +| `packages/builder/src/builder/index.ts` | L1-1 | +| `packages/builder/README.md` | L1-58 | + +
+ +--- + +
+ Searched codebase "defaultBuilder buildManifest" • **25** results + +| File | Lines | +|------|-------| +| `packages/data/package.json` | L1-13 | +| `packages/builder/src/builder/builder.ts` | L281-292 | +| `packages/builder/src/builder/builder.ts` | L69-78 | +| `packages/builder/src/index.ts` | L34-42 | +| `README.md` | L219-311 | +| `packages/builder/src/builder/builder.ts` | L1-27 | +| `packages/builder/src/builder/builder.ts` | L228-281 | +| `packages/builder/src/builder/builder.ts` | L78-137 | +| `README.md` | L123-219 | +| `packages/builder/src/cli.ts` | L145-179 | +| `packages/builder/src/builder/builder.ts` | L329-355 | +| `scripts/photo-loader.ts` | L1-35 | +| `packages/builder/src/manifest/manager.ts` | L1-30 | +| `packages/builder/src/manifest/manager.ts` | L44-67 | +| `packages/builder/src/types/manifest.ts` | L1-6 | +| `packages/builder/src/builder/builder.ts` | L137-167 | +| `apps/web/src/global.d.ts` | L1-42 | +| `packages/builder/src/manifest/manager.ts` | L30-44 | +| `README.zh.md` | L219-323 | +| `builder.config.ts` | L113-128 | +| `README.zh.md` | L125-219 | +| `packages/builder/src/cli.ts` | L63-104 | +| `packages/builder/README.md` | L1-58 | +| `builder.config.ts` | L71-113 | +| `apps/web/plugins/vite/feed-sitemap.ts` | L1-55 | + +
+ +--- + +
+ Searched codebase "builder.config" • **2** results + +| File | +|------| +| `undefined` | +| `undefined` | + +
+ +--- + +现在让我查看一下构建器的更多细节,特别是如何检测更新和当前的 git 操作支持: + +--- + +Read file: builder.config.ts + +--- + +
+ Searched codebase "git push commit upload manifest" • **23** results + +| File | Lines | +|------|-------| +| `scripts/build-update-remote-repo.sh` | L1-14 | +| `packages/builder/src/manifest/manager.ts` | L44-67 | +| `packages/builder/src/index.ts` | L34-42 | +| `packages/builder/src/manifest/manager.ts` | L30-44 | +| `packages/builder/src/manifest/manager.ts` | L1-30 | +| `packages/builder/src/builder/builder.ts` | L228-281 | +| `apps/web/scripts/pull-remote.ts` | L1-49 | +| `.git/refs/remotes/origin/main` | L1-2 | +| `README.md` | L219-311 | +| `apps/web/plugins/vite/manifest-inject.ts` | L1-50 | +| `package.json` | L1-61 | +| `README.md` | L123-219 | +| `packages/builder/src/runAsWorker.ts` | L20-26 | +| `packages/builder/src/builder/builder.ts` | L69-78 | +| `packages/builder/src/builder/builder.ts` | L78-137 | +| `packages/builder/src/builder/builder.ts` | L281-292 | +| `packages/builder/src/runAsWorker.ts` | L174-237 | +| `packages/data/package.json` | L1-13 | +| `apps/web/plugins/vite/__internal__/constants.ts` | L1-7 | +| `apps/web/vite.config.ts` | L36-103 | +| `.git/refs/heads/main` | L1-2 | +| `packages/builder/src/types/manifest.ts` | L1-6 | +| `apps/web/src/pages/(data)/manifest.tsx` | L187-226 | + +
+ +--- + +现在我需要查看 env.ts 文件以了解可能的环境变量配置: + +--- + +Read file: env.ts + +--- + +现在我基本了解了当前的结构。我需要修改以下几个文件: + +1. 修改 `builder.config.ts` 中的 `BuilderConfig` 接口,添加 git token 配置 +2. 修改 `env.ts` 添加 git token 环境变量 +3. 修改 `packages/builder/src/cli.ts` 中的逻辑,在 pull/clone 后运行增量更新,并在有更新时上传到 git 仓库 +4. 创建一个 git 操作的工具函数 + +让我开始实现: + +--- + +```diff + + PG_CONNECTION_STRING: z.string().min(1).optional(), ++ ++ // Git token for uploading updated manifest to remote repository ++ GIT_TOKEN: z.string().optional(), + }, + runtimeEnv: process.env, +``` + +--- + +```diff + enable: boolean + url: string ++ // Git token for pushing updates back to the repository ++ token?: string + } + // 存储配置 +``` + +--- + +```diff + enable: false, + url: '', ++ token: env.GIT_TOKEN, + }, + +``` + +--- + +现在我需要创建一个 git 操作的工具函数: + +--- + +```diff + import { workdir } from './path.js' + import { runAsWorker } from './runAsWorker.js' ++ ++ /** ++ * 推送更新后的 manifest 到远程仓库 ++ */ ++ async function pushManifestToRemoteRepo(): Promise { ++ if (!builderConfig.repo.enable || !builderConfig.repo.token) { ++ if (!builderConfig.repo.enable) { ++ logger.main.info('🔧 远程仓库未启用,跳过推送') ++ } else { ++ logger.main.warn('⚠️ 未提供 Git Token,跳过推送到远程仓库') ++ } ++ return false ++ } ++ ++ try { ++ const assetsGitDir = path.resolve(workdir, 'assets-git') ++ ++ if (!existsSync(assetsGitDir)) { ++ logger.main.error('❌ assets-git 目录不存在,无法推送') ++ return false ++ } ++ ++ logger.main.info('📤 开始推送更新到远程仓库...') ++ ++ // 检查是否有变更 ++ const status = await $({ ++ cwd: assetsGitDir, ++ stdio: 'pipe', ++ })`git status --porcelain` ++ ++ if (!status.stdout.trim()) { ++ logger.main.info('💡 没有变更需要推送') ++ return false ++ } ++ ++ logger.main.info('📋 检测到以下变更:') ++ logger.main.info(status.stdout) ++ ++ // 配置 git 凭据 ++ const repoUrl = builderConfig.repo.url ++ const token = builderConfig.repo.token ++ ++ // 解析仓库 URL,添加 token ++ let authenticatedUrl = repoUrl ++ if (repoUrl.startsWith('https://github.com/')) { ++ const urlWithoutProtocol = repoUrl.replace('https://', '') ++ authenticatedUrl = `https://${token}@${urlWithoutProtocol}` ++ } ++ ++ // 设置远程仓库 URL(包含 token) ++ await $({ ++ cwd: assetsGitDir, ++ stdio: 'pipe', ++ })`git remote set-url origin ${authenticatedUrl}` ++ ++ // 添加所有变更 ++ await $({ ++ cwd: assetsGitDir, ++ stdio: 'inherit', ++ })`git add .` ++ ++ // 提交变更 ++ const commitMessage = `chore: update photos-manifest.json and thumbnails - ${new Date().toISOString()}` ++ await $({ ++ cwd: assetsGitDir, ++ stdio: 'inherit', ++ })`git commit -m ${commitMessage}` ++ ++ // 推送到远程仓库 ++ await $({ ++ cwd: assetsGitDir, ++ stdio: 'inherit', ++ })`git push origin HEAD` ++ ++ logger.main.success('✅ 成功推送更新到远程仓库') ++ return true ++ ++ } catch (error) { ++ logger.main.error('❌ 推送到远程仓库失败:', error) ++ return false ++ } ++ } + + async function main() { +``` + +--- + +现在我需要修改 main 函数的逻辑,在 pull/clone 远程仓库后运行增量更新,然后推送更新: + +--- + + + +--- + +现在我需要在构建完成后调用推送函数: + +--- + + + +--- + +现在我需要修改 builder 构建器,让它返回是否有更新的信息。首先查看 builder 的返回类型: + +--- + +Read file: packages/builder/src/builder/builder.ts + +--- + +我需要修改 `BuilderOptions` 和 `buildManifest` 方法,使其返回构建结果信息: + +--- + + + +--- + + + +--- + +我需要修改 `#buildManifest` 方法的返回类型和返回值: + +--- + + + +--- + +我需要修改方法末尾,使其返回 BuilderResult,以及处理早期返回的情况: + +--- + + + +--- + +现在我需要在方法的最后添加返回语句: + +--- + + + +--- + +现在我需要更新 `builder/index.ts` 导出新的类型: + +--- + +```diff +- export type { BuilderOptions } from './builder.js' ++ export type { BuilderOptions, BuilderResult } from './builder.js' + export { defaultBuilder } from './builder.js' + +``` + +--- + +现在我需要修改 CLI 代码,使用返回的构建结果来决定是否推送: + +--- + + + +--- + +现在我需要更新 `builder.config.example.json` 文件,添加 token 字段的示例: + +--- + +Read file: builder.config.example.json + +--- + +```diff + "repo": { + "enable": true, +- "url": "https://github.com/Innei/gallery-public" ++ "url": "https://github.com/Innei/gallery-public", ++ "token": "your_github_token_here" + } + } +``` + +--- + +最后,我需要更新帮助信息,说明新的 token 配置: + +--- + + + +--- + +现在我需要在 `--config` 选项中显示 token 的状态信息: + +--- + + + +--- + +现在我测试一下代码是否正常工作。让我运行构建命令查看配置: + +--- + +```bash +cd packages/builder && pnpm run cli -- --config +``` + +``` + +> @afilmory/builder@0.0.1 cli /Users/innei/git/vibe-coding/photo-gallery-site/pa +ckages/builder +> tsx src/cli.ts -- --config + +ℹ 🔄 同步远程仓库... + MAIN 5:05:04 PM +ℹ 🔄 拉取远程仓库更新... + MAIN 5:05:04 PM +Already up to date. +✔ ✅ 远程仓库同步完成 + MAIN 5:05:05 PM +ℹ 🔧 当前配置: + MAIN 5:05:05 PM +ℹ 存储提供商:s3 + MAIN 5:05:05 PM +ℹ 存储桶:images + MAIN 5:05:05 PM +ℹ 区域:us-east-1 + MAIN 5:05:05 PM +ℹ 端点:http://10.0.0.33:18888 + MAIN 5:05:05 PM +ℹ 自定义域名:https://s3-private.innei.in/images + MAIN 5:05:05 PM +ℹ 前缀:无 + MAIN 5:05:05 PM +ℹ 默认并发数:10 + MAIN 5:05:05 PM +ℹ Live Photo 检测:启用 + MAIN 5:05:05 PM +ℹ Worker 数:24 + MAIN 5:05:05 PM +ℹ Worker 超时:30000ms + MAIN 5:05:05 PM +ℹ 集群模式:启用 + MAIN 5:05:05 PM +ℹ + MAIN 5:05:05 PM +ℹ 📦 远程仓库配置: + MAIN 5:05:05 PM +ℹ 启用状态:启用 + MAIN 5:05:05 PM +ℹ 仓库地址:https://github.com/Innei/gallery-public + MAIN 5:05:05 PM +ℹ 推送权限:未配置 + MAIN 5:05:05 PM + +``` + +--- + +太好了!配置显示功能正常工作。现在让我测试帮助信息: + +--- + +```bash +pnpm run cli -- --help +``` + +``` +at 17:05:05 ❯ pnpm run cli -- --help + +> @afilmory/builder@0.0.1 cli /Users/innei/git/vibe-coding/photo-gallery-site/packages/builder +> tsx src/cli.ts -- --help + +ℹ 🔄 同步远程仓库... MAIN 5:05:11 PM +ℹ 🔄 拉取远程仓库更新... MAIN 5:05:11 PM +Already up to date. +✔ ✅ 远程仓库同步完成 MAIN 5:05:12 PM +ℹ MAIN 5:05:12 PM +照片库构建工具 (新版本 - 使用适配器模式) + +用法:tsx src/core/cli.ts [选项] + +选项: + --force 强制重新处理所有照片 + --force-manifest 强制重新生成 manifest + --force-thumbnails 强制重新生成缩略图 + --config 显示当前配置信息 + --help, -h 显示帮助信息 + +示例: + tsx src/core/cli.ts # 增量更新 + tsx src/core/cli.ts --force # 全量更新 + tsx src/core/cli.ts --force-thumbnails # 强制重新生成缩略图 + tsx src/core/cli.ts --config # 显示配置信息 + +配置: + 在 builder.config.ts 中设置 performance.worker.useClusterMode = true + 可启用多进程集群模式,发挥多核心优势。 + +远程仓库: + 如果启用了远程仓库 (repo.enable = true),构建完成后会自动推送更新。 + 需要配置 repo.token 或设置 GIT_TOKEN 环境变量以提供推送权限。 + 如果没有提供 token,将跳过推送步骤。 + + +innei in photo-gallery-site/packages/builder on  main [$!?] via  v22.15.0 on ☁️ innei@rss3.io at    6668425 "f +eat: add Vite plugins for enhanced functionality and localization support" by tukon479@gmail.com +at 17:05:12 ❯ +``` + +--- + + + +很好!帮助信息显示正常。现在让我回到根目录测试一下实际的构建流程: + +--- + +```bash +cd ../.. && pnpm run build:manifest +``` + +``` +at 17:05:12 ❯ cd ../.. && pnpm run build:manifest + +> @afilmory/monorepo@0.1.0 build:manifest /Users/innei/git/vibe-coding/photo-gallery-site +> pnpm --filter @afilmory/builder cli + + +> @afilmory/builder@0.0.1 cli /Users/innei/git/vibe-coding/photo-gallery-site/packages/builder +> tsx src/cli.ts + +ℹ 🔄 同步远程仓库... MAIN 5:05:21 PM +ℹ 🔄 拉取远程仓库更新... MAIN 5:05:21 PM +Already up to date. +✔ ✅ 远程仓库同步完成 MAIN 5:05:22 PM +ℹ 🚀 运行模式:增量更新 MAIN 5:05:22 PM +ℹ ⚡ 最大并发数:24 MAIN 5:05:22 PM +ℹ 🔧 处理模式:多进程集群 MAIN 5:05:22 PM +ℹ 🏗️ 使用构建器:PhotoGalleryBuilder (适配器模式) MAIN 5:05:22 PM +ℹ Perl 已安装 MAIN 5:05:22 PM +ℹ 🚀 开始从存储获取照片列表... MAIN 5:05:22 PM +ℹ 🔗 使用端点:http://10.0.0.33:18888 MAIN 5:05:22 PM +ℹ 🌐 自定义域名:https://s3-private.innei.in/images MAIN 5:05:22 PM +ℹ 🪣 存储桶:images MAIN 5:05:22 PM +ℹ 📂 前缀:无前缀 MAIN 5:05:22 PM +ℹ 现有 manifest 包含 79 张照片 MAIN 5:05:22 PM +ℹ 存储中找到 67 个文件 MAIN 5:05:22 PM +ℹ 检测到 3 个 Live Photo MAIN 5:05:22 PM +ℹ 存储中找到 55 张照片 MAIN 5:05:22 PM +ℹ 存储中找到 55 张照片,实际需要处理 1 张 MAIN 5:05:22 PM +ℹ 开始并发处理任务,Worker数:24 MAIN 5:05:22 PM +ℹ 开始并发处理任务,工作池模式,并发数:24 MAIN 5:05:22 PM +◐ Worker 1 启动 WORKER-1 5:05:22 PM +ℹ 开始处理任务 1/1 WORKER-1 5:05:22 PM +ℹ 📸 [1/1] 风光/DSCF3007.jpg WORKER-1:IMAGE 5:05:22 PM +ℹ 🆕 新照片:风光/DSCF3007.jpg WORKER-1:IMAGE 5:05:22 PM +ℹ 下载文件:风光/DSCF3007.jpg S3 5:05:22 PM +✔ 下载完成:风光/DSCF3007.jpg (26335KB, 426ms) S3 5:05:22 PM +ℹ 开始提取 EXIF 数据 WORKER-1:EXIF 5:05:22 PM +✔ EXIF 数据提取完成 WORKER-1:EXIF 5:05:23 PM +ℹ 提取照片信息:风光/DSCF3007.jpg WORKER-1:IMAGE 5:05:23 PM +ℹ 从路径提取标签:[风光] WORKER-1:IMAGE 5:05:23 PM +ℹ 使用 EXIF Date 对象作为拍摄时间 WORKER-1:IMAGE 5:05:23 PM +ℹ 照片信息提取完成:"DSCF3007" WORKER-1:IMAGE 5:05:23 PM +✔ ✅ 处理完成:风光/DSCF3007.jpg WORKER-1:IMAGE 5:05:23 PM +ℹ 完成任务 1/1 - 1167ms WORKER-1 5:05:23 PM +✔ Worker 1 完成,处理了 1 个任务 WORKER-1 5:05:23 PM +ℹ 🔍 检查已删除的图片... MAIN 5:05:23 PM +[FS 5:05:23 PM] ℹ 📁 Manifest 保存至: /Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/data/photos-mani +fest.json +✔ 🎉 Manifest 构建完成! MAIN 5:05:23 PM +ℹ 📊 处理统计: MAIN 5:05:23 PM +ℹ 📸 总照片数:55 MAIN 5:05:23 PM +ℹ 🆕 新增照片:1 MAIN 5:05:23 PM +ℹ 🔄 处理照片:1 MAIN 5:05:23 PM +ℹ ⏭️ 跳过照片:54 MAIN 5:05:23 PM +ℹ 🗑️ 删除照片:25 MAIN 5:05:23 PM +ℹ ⏱️ 总耗时:1秒 MAIN 5:05:23 PM +ℹ 🔄 检测到更新,推送到远程仓库... MAIN 5:05:23 PM + + WARN ⚠️ 未提供 Git Token,跳过推送到远程仓库 MAIN 5:05:23 PM + + +innei in photo-gallery-site on  main [$!?] via 🐳 orbstack via  v22.15.0 on ☁️ innei@rss3.io at    6668425 "fe +at: add Vite plugins for enhanced functionality and localization support" by tukon479@gmail.com took 2s +at 17:05:23 ❯ +``` + +--- + +完美!功能工作正常。我可以看到: + +1. 成功检测到了更新(新增了1张照片,删除了25张照片) +2. 由于 `hasUpdates = true`,所以尝试推送到远程仓库 +3. 但由于没有配置 Git Token,所以跳过了推送,并正确显示了警告信息 + +现在让我检查一下构建后软链接是否正确建立: + +--- + +```bash +ls -la apps/web/src/data/ +``` + +``` +at 17:05:23 ❯ ls -la apps/web/src/data/ +total 0 +drwxr-xr-x 4 innei staff 128 Jun 27 17:05 . +drwxrwxr-x 25 innei staff 800 Jun 27 01:52 .. +-rw-r--r-- 1 innei staff 0 Jun 8 22:04 .gitkeep +lrwxr-xr-x 1 innei staff 88 Jun 27 17:05 photos-manifest.json -> /Users/innei/git/vibe-coding/photo-gallery-sit +e/apps/web/assets-git/photos-manifest.json + +innei in photo-gallery-site on  main [$!?] via 🐳 orbstack via  v22.15.0 on ☁️ innei@rss3.io at    6668425 "fe +at: add Vite plugins for enhanced functionality and localization support" by tukon479@gmail.com +at 17:05:30 ❯ +``` + +--- + +```bash +ls -la apps/web/public/ +``` + +``` +at 17:05:30 ❯ ls -la apps/web/public/ +total 134528 +-rw-r--r-- 1 innei staff 267 Jun 5 13:51 _routes.json +drwxr-xr-x 19 innei staff 608 Jun 27 17:05 . +drwxr-xr-x 18 innei staff 576 Jun 27 01:52 .. +-rw-r--r-- 1 innei staff 14643 Jun 27 16:57 android-chrome-192x192.png +-rw-r--r-- 1 innei staff 60803 Jun 27 16:57 android-chrome-512x512.png +-rw-r--r-- 1 innei staff 14228 Jun 27 16:57 apple-touch-icon.png +-rw-r--r-- 1 innei staff 778 Jun 27 16:57 favicon-16x16.png +-rw-r--r-- 1 innei staff 1736 Jun 27 16:57 favicon-32x32.png +-rw-r--r-- 1 innei staff 2803 Jun 27 16:57 favicon-48x48.png +-rw-r--r-- 1 innei staff 1736 Jun 27 16:57 favicon.ico +-rw-r--r-- 1 innei staff 308114 Jun 27 01:52 og-image-1750960326224.png +-rw-r--r-- 1 innei staff 308114 Jun 27 01:54 og-image-1750960455628.png +-rw-r--r-- 1 innei staff 308114 Jun 27 16:57 og-image-1751014676160.png +-rw-r--r-- 1 innei staff 375 Jun 27 16:57 site.webmanifest +-rw-r--r-- 1 innei staff 98940 May 25 23:59 test-svg-font-rendering.png +-rw-r--r-- 1 innei staff 97309 May 25 23:59 test-traditional-font-rendering.png +-rw-r--r--@ 1 innei staff 67527869 May 27 13:13 thomas-x2d-xcd-25v-1.jpg +-rw-r--r-- 1 innei staff 92258 May 27 13:24 thomas-x2d-xcd-25v-1.webp +lrwxr-xr-x 1 innei staff 78 Jun 27 17:05 thumbnails -> /Users/innei/git/vibe-coding/photo-gallery-site/app +s/web/assets-git/thumbnails + +innei in photo-gallery-site on  main [$!?] via 🐳 orbstack via  v22.15.0 on ☁️ innei@rss3.io at    6668425 "fe +at: add Vite plugins for enhanced functionality and localization support" by tukon479@gmail.com +at 17:05:34 ❯ +``` + +--- + +很好!软链接都正确建立了。现在让我创建一个简单的测试来验证推送功能: + +--- + +```bash +cd apps/web/assets-git && git status +``` + +``` +at 17:05:34 ❯ cd apps/web/assets-git && git status +On branch main +Your branch is up to date with 'origin/main'. + +Changes not staged for commit: + (use "git add/rm ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: photos-manifest.json + deleted: thumbnails/DSCF3755.webp + deleted: thumbnails/DSCF3756.webp + deleted: thumbnails/DSCF3768.webp + deleted: thumbnails/DSCF3819.webp + deleted: thumbnails/DSCF3837.webp + deleted: thumbnails/DSCF3838.webp + deleted: thumbnails/DSCF3859.webp + deleted: thumbnails/DSCF3868.webp + deleted: thumbnails/DSCF3884.webp + deleted: thumbnails/DSCF3946.webp + deleted: thumbnails/DSCF4016.webp + deleted: thumbnails/DSCF4020.webp + deleted: thumbnails/DSCF4032.webp + deleted: thumbnails/DSCF4069.webp + deleted: thumbnails/DSCF4072.webp + deleted: thumbnails/DSCF4075.webp + deleted: thumbnails/DSCF4086.webp + deleted: thumbnails/DSCF4113.webp + deleted: thumbnails/DSCF4115.webp + deleted: thumbnails/DSCF4116.webp + deleted: thumbnails/DSCF4127.webp + deleted: thumbnails/DSCF4149.webp + deleted: thumbnails/IMG_1401.webp + deleted: thumbnails/IMG_1461.webp + deleted: thumbnails/IMG_8186.webp + +Untracked files: + (use "git add ..." to include in what will be committed) + thumbnails/DSCF3007.webp + +no changes added to commit (use "git add" and/or "git commit -a") + +innei in assets-git on  main [✘!?] on ☁️ innei@rss3.io at    c4335c2 "chore: update photos-manifest.json and th +umbnails" by tukon479@gmail.com +at 17:05:40 ❯ +``` + +--- + +完美!可以看到确实有更改需要推送: +- `photos-manifest.json` 被修改了 +- 删除了很多旧的缩略图文件 +- 添加了一个新的缩略图 `DSCF3007.webp` + +现在让我创建一个简单的说明文档,总结一下这次实现的功能: + +--- + +Cancelled + +--- + +_**User**_ + + + +--- + +_**Assistant**_ + + + +--- + diff --git a/README.md b/README.md index 4b3dac23..a7ed02b9 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,6 @@ Live Photo Galleries: - https://photography.pseudoyu.com - https://afilmory.magren.cc -[简体中文](./README.zh.md) - ## 🌟 Features ### Core Functionality @@ -97,20 +95,33 @@ cd photo-gallery-site pnpm install ``` -### 3. Environment Configuration +### 3. Configuration Create `.env` file: ```env -# S3 Storage Configuration -S3_REGION=us-east-1 +# S3 Storage Keys S3_ACCESS_KEY_ID=your_access_key_id S3_SECRET_ACCESS_KEY=your_secret_access_key -S3_ENDPOINT=https://s3.amazonaws.com -S3_BUCKET_NAME=your_bucket_name -S3_PREFIX=photos/ -S3_CUSTOM_DOMAIN=your_custom_domain.com -S3_EXCLUDE_REGEX= +``` + +Create `builder.config.json` file for storage configuration and other options: + +```json +{ + "repo": { + "enable": false, + "url": "https://github.com/username/gallery-assets" + }, + "storage": { + "provider": "s3", + "bucket": "my-photos", + "region": "us-east-1", + "prefix": "photos/", + "customDomain": "https://cdn.example.com", + "endpoint": "https://s3.amazonaws.com" + } +} ``` ### 4. Site Configuration @@ -162,47 +173,22 @@ pnpm dev ## ⚙️ Configuration Options -### Builder Configuration +#### Remote Repository Configuration (`repo`) -Create `builder.config.json` file for advanced configuration: +To achieve incremental builds in CI, it is necessary to configure a cache repository, which will pull the cache before each build and upload the build results after the build. ```json { "repo": { - "enable": false, + "enable": true, "url": "https://github.com/username/gallery-assets" - }, - "storage": { - "provider": "s3", - "bucket": "my-photos", - "region": "us-east-1", - "prefix": "photos/", - "customDomain": "https://cdn.example.com", - "endpoint": "https://s3.amazonaws.com" - }, - "options": { - "defaultConcurrency": 8, - "enableLivePhotoDetection": true, - "showProgress": true, - "showDetailedStats": true - }, - "logging": { - "verbose": true, - "level": "info", - "outputToFile": false - }, - "performance": { - "worker": { - "workerCount": 8, - "timeout": 30000, - "useClusterMode": true, - "workerConcurrency": 2 - } } } ``` -### Configuration Options Description +This will automatically pull resources from the remote repository, avoiding rebuilds each time. + +**In order to achieve uploading to the git repository, you need to provide a `GIT_TOKEN` and write it in the `.env` file.** #### Storage Configuration (`storage`) @@ -233,21 +219,6 @@ Create `builder.config.json` file for advanced configuration: - `level`: Log level (`info` | `warn` | `error` | `debug`) - `outputToFile`: Output to file -### Remote Repository Configuration - -If you have a separate asset repository for storing thumbnails and manifests: - -```json -{ - "repo": { - "enable": true, - "url": "https://github.com/username/gallery-assets" - } -} -``` - -This will automatically pull resources from the remote repository, avoiding rebuilds each time. - ## 📋 CLI Commands ### Build Commands diff --git a/README.zh.md b/README.zh.md deleted file mode 100644 index 593fcc7f..00000000 --- a/README.zh.md +++ /dev/null @@ -1,329 +0,0 @@ -

- Afilmory -

- -#

Afilmory

- -Afilmory (/əˈfɪlməri/, "uh-FIL-muh-ree") 是一个为个人摄影网站创造的术语,融合了对焦 (AF)、光圈(光线控制)、胶片(复古媒介)和记忆(捕捉的瞬间)。 - -一个现代化的照片画廊网站,采用 React + TypeScript 构建,支持从多种存储源(S3、GitHub)自动同步照片,具有高性能 WebGL 渲染、瀑布流布局、EXIF 信息展示、缩略图生成等功能。 - -线上照片墙: - -- https://gallery.innei.in -- https://gallery.mxte.cc -- https://photography.pseudoyu.com -- https://afilmory.magren.cc - -## 🌟 特性 - -### 核心功能 - -- 🖼️ **高性能 WebGL 图像渲染器** - 基于自研 WebGL 组件,支持流畅的缩放、平移操作 -- 📱 **响应式瀑布流布局** - 采用 Masonic 实现,自适应不同屏幕尺寸 -- 🎨 **现代化 UI 设计** - 基于 Tailwind CSS 和 Radix UI 组件库 -- ⚡ **增量同步** - 智能检测文件变化,仅处理新增或修改的照片 -- 🌐 **i18n** - 支持多语言 -- 🔗 **OpenGraph** - OpenGraph 元数据,用于社交媒体分享 - -### 图像处理 - -- 🔄 **HEIC/HEIF 格式支持** - 自动转换 Apple 设备的 HEIC 格式 -- 🖼️ **智能缩略图生成** - 多尺寸缩略图,优化加载性能 -- 📊 **EXIF 信息展示** - 完整的拍摄参数,包括相机型号、焦距、光圈等 -- 🌈 **Blurhash 占位符** - 优雅的图片加载体验 -- 📱 **Live Photo 支持** - 检测并展示 iPhone Live Photo - -### 高级功能 - -- 🎛️ **富士胶片模拟** - 读取和展示富士相机的胶片模拟设置 -- 🔍 **全屏查看器** - 支持手势操作的图片查看器 -- 🏷️ **文件系统标签** - 基于文件系统自动生成标签 -- ⚡ **并发处理** - 支持多进程/多线程并发处理 -- 🗂️ **多存储支持** - S3、GitHub 等多种存储后端 - -## 🏗️ 技术架构 - -### 前端技术栈 - -- **React 19** - 使用最新的 React 版本和 Compiler -- **TypeScript** - 完整的类型安全 -- **Vite** - 现代化构建工具 -- **Tailwind CSS** - 原子化 CSS 框架 -- **Radix UI** - 无障碍组件库 -- **Jotai** - 状态管理 -- **TanStack Query** - 数据获取和缓存 -- **React Router 7** - 路由管理 -- **i18next** - 国际化 - -### 构建系统 - -- **Node.js** - 服务端运行时 -- **Sharp** - 高性能图像处理 -- **AWS SDK** - S3 存储操作 -- **Worker Threads/Cluster** - 并发处理 -- **EXIF-Reader** - EXIF 数据提取 - -### 存储架构 - -采用适配器模式设计,支持多种存储后端: - -- **S3 兼容存储** - AWS S3、MinIO、阿里云 OSS 等 -- **GitHub 存储** - 使用 GitHub 仓库作为图片存储 - -## 🚀 快速开始 - -### Docker 部署 - -[Docker 部署](https://github.com/Afilmory/docker) - -### 环境要求 - -- Node.js 18+ -- 至少 4GB RAM(用于图像处理) - -### 1. 克隆项目 - -```bash -git clone https://github.com/Afilmory/Afilmory.git -cd photo-gallery-site -``` - -### 2. 安装依赖 - -```bash -pnpm install -``` - -### 3. 环境配置 - -创建 `.env` 文件: - -```env -# S3 存储配置 -S3_REGION=us-east-1 -S3_ACCESS_KEY_ID=your_access_key_id -S3_SECRET_ACCESS_KEY=your_secret_access_key -S3_ENDPOINT=https://s3.amazonaws.com -S3_BUCKET_NAME=your_bucket_name -S3_PREFIX=photos/ -S3_CUSTOM_DOMAIN=your_custom_domain.com -S3_EXCLUDE_REGEX= -``` - -### 4. 站点配置 - -复制并编辑配置文件: - -```bash -cp config.example.json config.json -``` - -编辑 `config.json`: - -```json -{ - "name": "我的照片画廊", - "title": "我的照片画廊", - "description": "记录生活中的美好瞬间", - "url": "https://gallery.example.com", - "accentColor": "#007bff", // 可选, 设置主题色 - "author": { - "name": "Your Name", // 必填, 设置作者名称 - "url": "https://example.com", // 可选, 设置作者主页 - "avatar": "https://example.com/avatar.png" // 可选, 设置作者头像 - }, - "social": { - "twitter": "@yourusername" // 可选, 设置社交账号 - } -} -``` - -### 5. 构建照片清单 - -```bash -# 首次构建 -pnpm run build:manifest - -# 增量更新 -pnpm run build:manifest - -# 强制全量更新 -pnpm run build:manifest -- --force -``` - -### 6. 启动开发服务器 - -```bash -pnpm dev -``` - -## ⚙️ 配置选项 - -### 构建器配置 - -创建 `builder.config.json` 文件进行高级配置: - -```json -{ - "repo": { - "enable": false, - "url": "https://github.com/username/gallery-assets" - }, - "storage": { - "provider": "s3", - "bucket": "my-photos", - "region": "us-east-1", - "prefix": "photos/", - "customDomain": "https://cdn.example.com" - }, - "options": { - "defaultConcurrency": 8, - "enableLivePhotoDetection": true, - "showProgress": true, - "showDetailedStats": true - }, - "logging": { - "verbose": true, - "level": "info", - "outputToFile": false - }, - "performance": { - "worker": { - "workerCount": 8, - "timeout": 30000, - "useClusterMode": true, - "workerConcurrency": 2 - } - } -} -``` - -### 配置选项说明 - -#### 存储配置 (`storage`) - -- `provider`: 存储提供商 (`s3` | `github`) -- `bucket`: S3 存储桶名称 -- `region`: S3 区域 -- `endpoint`: S3 端点(可选) -- `prefix`: 文件前缀 -- `customDomain`: 自定义域名 -- `excludeRegex`: 排除文件的正则表达式(可选) - -#### 构建选项 (`options`) - -- `defaultConcurrency`: 默认并发数 -- `enableLivePhotoDetection`: 启用 Live Photo 检测 -- `showProgress`: 显示构建进度 -- `showDetailedStats`: 显示详细统计信息 - -#### 性能配置 (`performance`) - -- `worker.workerCount`: Worker 进程数 -- `worker.timeout`: Worker 超时时间(毫秒) -- `worker.useClusterMode`: 启用集群模式 - -#### 日志配置 (`logging`) - -- `verbose`: 详细日志 -- `level`: 日志级别 (`info` | `warn` | `error` | `debug`) -- `outputToFile`: 输出到文件 - -### 远程资源库配置 - -如果你有独立的资源仓库存储缩略图和清单: - -```json -{ - "repo": { - "enable": true, - "url": "https://github.com/username/gallery-assets" - } -} -``` - -这将自动从远程仓库拉取资源,避免每次构建。 - -## 📋 CLI 命令 - -### 构建命令 - -```bash -# 查看帮助 -pnpm run build:manifest -- --help - -# 增量更新(默认) -pnpm run build:manifest - -# 强制全量更新 -pnpm run build:manifest -- --force - -# 仅重新生成缩略图 -pnpm run build:manifest -- --force-thumbnails - -# 仅重新生成清单 -pnpm run build:manifest -- --force-manifest -``` - -### 开发命令 - -```bash -# 启动开发服务器 -pnpm dev - -# 构建生产版本 -pnpm build -``` - -### 注意事项 - -- 确保你的 S3 存储桶已经包含照片文件 -- 如果使用远程资源库,需要先配置 `builder.config.json` - -## 🔧 高级用法 - -### 自定义存储提供商 - -实现 `StorageProvider` 接口以支持新的存储后端: - -```typescript -import { StorageProvider } from './src/core/storage/interfaces' - -class MyStorageProvider implements StorageProvider { - async getFile(key: string): Promise { - // 实现文件获取逻辑 - } - - async listImages(): Promise { - // 实现图片列表获取逻辑 - } - - // ... 其他方法 -} -``` - -### 自定义图像处理 - -在 `src/core/image/` 目录下添加自定义处理器: - -```typescript -export async function customImageProcessor(buffer: Buffer) { - // 自定义图像处理逻辑 - return processedBuffer -} -``` - -## 📄 许可证 - -MIT License © 2025 Innei - -## 🔗 相关链接 - -- [在线演示](https://gallery.innei.in) -- [个人网站](https://innei.in) -- [GitHub](https://github.com/innei) - ---- - -如果这个项目对你有帮助,请给个 ⭐️ Star 支持一下! diff --git a/builder.config.example.json b/builder.config.example.json index 035ab8ad..fe6a9947 100644 --- a/builder.config.example.json +++ b/builder.config.example.json @@ -1,6 +1,6 @@ { "repo": { "enable": true, - "url": "https://github.com/Innei/gallery-public" + "url": "https://github.com/cupchino/afilmory-assets" } } \ No newline at end of file diff --git a/builder.config.ts b/builder.config.ts index ce25e562..23e210a5 100644 --- a/builder.config.ts +++ b/builder.config.ts @@ -14,6 +14,8 @@ export interface BuilderConfig { repo: { enable: boolean url: string + // Git token for pushing updates back to the repository + token?: string } // 存储配置 storage: StorageConfig @@ -74,6 +76,7 @@ export const defaultBuilderConfig: BuilderConfig = { repo: { enable: false, url: '', + token: env.GIT_TOKEN, }, storage: { diff --git a/env.ts b/env.ts index a8a3a80f..e1fbff7a 100644 --- a/env.ts +++ b/env.ts @@ -18,6 +18,9 @@ export const env = createEnv({ S3_EXCLUDE_REGEX: z.string().optional(), PG_CONNECTION_STRING: z.string().min(1).optional(), + + // Git token for uploading updated manifest to remote repository + GIT_TOKEN: z.string().optional(), }, runtimeEnv: process.env, isServer: typeof window === 'undefined', diff --git a/packages/builder/src/builder/builder.ts b/packages/builder/src/builder/builder.ts index ac8b7361..bade4119 100644 --- a/packages/builder/src/builder/builder.ts +++ b/packages/builder/src/builder/builder.ts @@ -26,6 +26,15 @@ export interface BuilderOptions { concurrencyLimit?: number // 可选,如果未提供则使用配置文件中的默认值 } +export interface BuilderResult { + hasUpdates: boolean + newCount: number + processedCount: number + skippedCount: number + deletedCount: number + totalPhotos: number +} + class PhotoGalleryBuilder { private storageManager: StorageManager private config: BuilderConfig @@ -68,9 +77,9 @@ class PhotoGalleryBuilder { // 目前日志配置在 logger 模块中处理 } - async buildManifest(options: BuilderOptions): Promise { + async buildManifest(options: BuilderOptions): Promise { try { - await this.#buildManifest(options) + return await this.#buildManifest(options) } catch (error) { logger.main.error('❌ 构建 manifest 失败:', error) throw error @@ -80,7 +89,7 @@ class PhotoGalleryBuilder { * 构建照片清单 * @param options 构建选项 */ - async #buildManifest(options: BuilderOptions): Promise { + async #buildManifest(options: BuilderOptions): Promise { const startTime = Date.now() this.logBuildStart() @@ -122,7 +131,14 @@ class PhotoGalleryBuilder { if (imageObjects.length === 0) { logger.main.error('❌ 没有找到需要处理的照片') - return + return { + hasUpdates: false, + newCount: 0, + processedCount: 0, + skippedCount: 0, + deletedCount: 0, + totalPhotos: 0, + } } // 筛选出实际需要处理的图片 @@ -278,6 +294,17 @@ class PhotoGalleryBuilder { Date.now() - startTime, ) } + + // 返回构建结果 + const hasUpdates = newCount > 0 || processedCount > 0 || deletedCount > 0 + return { + hasUpdates, + newCount, + processedCount, + skippedCount, + deletedCount, + totalPhotos: manifest.length, + } } private async loadExistingManifest( diff --git a/packages/builder/src/builder/index.ts b/packages/builder/src/builder/index.ts index 012d9124..3e763d24 100644 --- a/packages/builder/src/builder/index.ts +++ b/packages/builder/src/builder/index.ts @@ -1,2 +1,2 @@ -export type { BuilderOptions } from './builder.js' +export type { BuilderOptions, BuilderResult } from './builder.js' export { defaultBuilder } from './builder.js' diff --git a/packages/builder/src/cli.ts b/packages/builder/src/cli.ts index f404833b..040c5f13 100644 --- a/packages/builder/src/cli.ts +++ b/packages/builder/src/cli.ts @@ -14,6 +14,87 @@ import { logger } from './logger/index.js' import { workdir } from './path.js' import { runAsWorker } from './runAsWorker.js' +/** + * 推送更新后的 manifest 到远程仓库 + */ +async function pushManifestToRemoteRepo(): Promise { + if (!builderConfig.repo.enable || !builderConfig.repo.token) { + if (!builderConfig.repo.enable) { + logger.main.info('🔧 远程仓库未启用,跳过推送') + } else { + logger.main.warn('⚠️ 未提供 Git Token,跳过推送到远程仓库') + } + return false + } + + try { + const assetsGitDir = path.resolve(workdir, 'assets-git') + + if (!existsSync(assetsGitDir)) { + logger.main.error('❌ assets-git 目录不存在,无法推送') + return false + } + + logger.main.info('📤 开始推送更新到远程仓库...') + + // 检查是否有变更 + const status = await $({ + cwd: assetsGitDir, + stdio: 'pipe', + })`git status --porcelain` + + if (!status.stdout.trim()) { + logger.main.info('💡 没有变更需要推送') + return false + } + + logger.main.info('📋 检测到以下变更:') + logger.main.info(status.stdout) + + // 配置 git 凭据 + const repoUrl = builderConfig.repo.url + const { token } = builderConfig.repo + + // 解析仓库 URL,添加 token + let authenticatedUrl = repoUrl + if (repoUrl.startsWith('https://github.com/')) { + const urlWithoutProtocol = repoUrl.replace('https://', '') + authenticatedUrl = `https://${token}@${urlWithoutProtocol}` + } + + // 设置远程仓库 URL(包含 token) + await $({ + cwd: assetsGitDir, + stdio: 'pipe', + })`git remote set-url origin ${authenticatedUrl}` + + // 添加所有变更 + await $({ + cwd: assetsGitDir, + stdio: 'inherit', + })`git add .` + + // 提交变更 + const commitMessage = `chore: update photos-manifest.json and thumbnails - ${new Date().toISOString()}` + await $({ + cwd: assetsGitDir, + stdio: 'inherit', + })`git commit -m ${commitMessage}` + + // 推送到远程仓库 + await $({ + cwd: assetsGitDir, + stdio: 'inherit', + })`git push origin HEAD` + + logger.main.success('✅ 成功推送更新到远程仓库') + return true + } catch (error) { + logger.main.error('❌ 推送到远程仓库失败:', error) + return false + } +} + async function main() { // 检查是否作为 cluster worker 运行 if ( @@ -28,18 +109,55 @@ async function main() { // 如果配置了远程仓库,则使用远程仓库 if (builderConfig.repo.enable) { // 拉取远程仓库 + logger.main.info('🔄 同步远程仓库...') const hasExist = existsSync(path.resolve(workdir, 'assets-git')) if (!hasExist) { + logger.main.info('📥 克隆远程仓库...') await $({ cwd: workdir, stdio: 'inherit', })`git clone ${builderConfig.repo.url} assets-git` } else { + logger.main.info('🔄 拉取远程仓库更新...') + try { + await $({ + cwd: path.resolve(workdir, 'assets-git'), + stdio: 'inherit', + })`git pull --rebase` + } catch { + logger.main.warn('⚠️ git pull 失败,尝试重置远程仓库...') + logger.main.info('🗑️ 删除现有仓库目录...') + await $({ cwd: workdir, stdio: 'inherit' })`rm -rf assets-git` + logger.main.info('📥 重新克隆远程仓库...') + await $({ + cwd: workdir, + stdio: 'inherit', + })`git clone ${builderConfig.repo.url} assets-git` + } + } + + // 确保远程仓库有必要的目录和文件 + const assetsGitDir = path.resolve(workdir, 'assets-git') + const thumbnailsSourceDir = path.resolve(assetsGitDir, 'thumbnails') + const manifestSourcePath = path.resolve( + assetsGitDir, + 'photos-manifest.json', + ) + + // 创建 thumbnails 目录(如果不存在) + if (!existsSync(thumbnailsSourceDir)) { + logger.main.info('📁 创建 thumbnails 目录...') + await $({ cwd: assetsGitDir, stdio: 'inherit' })`mkdir -p thumbnails` + } + + // 创建空的 manifest 文件(如果不存在) + if (!existsSync(manifestSourcePath)) { + logger.main.info('📄 创建初始 manifest 文件...') await $({ - cwd: path.resolve(workdir, 'assets-git'), + cwd: assetsGitDir, stdio: 'inherit', - })`git pull --rebase` + })`echo '{"version":"v2","data":[]}' > photos-manifest.json` } // 删除 public/thumbnails 目录,并建立软连接到 assets-git/thumbnails @@ -50,7 +168,8 @@ async function main() { await $({ cwd: workdir, stdio: 'inherit', - })`ln -s ${path.resolve(workdir, 'assets-git', 'thumbnails')} ${thumbnailsDir}` + })`ln -s ${thumbnailsSourceDir} ${thumbnailsDir}` + // 删除 src/data/photos-manifest.json,并建立软连接到 assets-git/photos-manifest.json const photosManifestPath = path.resolve( workdir, @@ -59,13 +178,14 @@ async function main() { 'photos-manifest.json', ) if (existsSync(photosManifestPath)) { - await $({ cwd: workdir, stdio: 'inherit' })`rm -rf ${photosManifestPath}` + await $({ cwd: workdir, stdio: 'inherit' })`rm -f ${photosManifestPath}` } - await $({ cwd: workdir, stdio: 'inherit' })`ln -s ${path.resolve( - workdir, - 'assets-git', - 'photos-manifest.json', - )} ${photosManifestPath}` + await $({ + cwd: workdir, + stdio: 'inherit', + })`ln -s ${manifestSourcePath} ${photosManifestPath}` + + logger.main.success('✅ 远程仓库同步完成') } process.title = 'photo-gallery-builder-main' @@ -99,6 +219,11 @@ async function main() { 配置: 在 builder.config.ts 中设置 performance.worker.useClusterMode = true 可启用多进程集群模式,发挥多核心优势。 + +远程仓库: + 如果启用了远程仓库 (repo.enable = true),构建完成后会自动推送更新。 + 需要配置 repo.token 或设置 GIT_TOKEN 环境变量以提供推送权限。 + 如果没有提供 token,将跳过推送步骤。 `) return } @@ -138,6 +263,15 @@ async function main() { logger.main.info( ` 集群模式:${config.performance.worker.useClusterMode ? '启用' : '禁用'}`, ) + logger.main.info('') + logger.main.info('📦 远程仓库配置:') + logger.main.info(` 启用状态:${config.repo.enable ? '启用' : '禁用'}`) + if (config.repo.enable) { + logger.main.info(` 仓库地址:${config.repo.url || '未设置'}`) + logger.main.info( + ` 推送权限:${config.repo.token ? '已配置' : '未配置'}`, + ) + } return } @@ -168,12 +302,23 @@ async function main() { environmentCheck() // 启动构建过程 - await defaultBuilder.buildManifest({ + const buildResult = await defaultBuilder.buildManifest({ isForceMode, isForceManifest, isForceThumbnails, concurrencyLimit, }) + + // 如果启用了远程仓库,在构建完成后推送更新 + if (builderConfig.repo.enable) { + if (buildResult.hasUpdates) { + logger.main.info('🔄 检测到更新,推送到远程仓库...') + await pushManifestToRemoteRepo() + } else { + logger.main.info('💡 没有更新需要推送到远程仓库') + } + } + // eslint-disable-next-line unicorn/no-process-exit process.exit(0) }