Commit 331123b5 authored by xuchentao's avatar xuchentao

refactor: move previews outside runtime directory

parent bd42cab9
...@@ -5,6 +5,8 @@ FRONTEND_ORIGIN=http://localhost ...@@ -5,6 +5,8 @@ FRONTEND_ORIGIN=http://localhost
WEBAGENT_USER_PASSWORD=user WEBAGENT_USER_PASSWORD=user
# 相对路径以 WebAgent 项目根目录为基准,也支持 /data/WebAgent-sites 等绝对路径。 # 相对路径以 WebAgent 项目根目录为基准,也支持 /data/WebAgent-sites 等绝对路径。
WEBAGENT_SITES_DIR=../WebAgent-sites WEBAGENT_SITES_DIR=../WebAgent-sites
# 测试预览静态产物独立保存,便于直接查看,也不会随运行缓存清理。
WEBAGENT_PREVIEWS_DIR=../WebAgent-previews
# 已上线的静态产物独立保存,不会被测试环境构建覆盖。 # 已上线的静态产物独立保存,不会被测试环境构建覆盖。
WEBAGENT_PRODUCTION_DIR=../WebAgent-production WEBAGENT_PRODUCTION_DIR=../WebAgent-production
# 自定义域名使用单独的根路径构建产物,不会覆盖上面的生产目录。 # 自定义域名使用单独的根路径构建产物,不会覆盖上面的生产目录。
......
...@@ -21,6 +21,7 @@ pnpm dev ...@@ -21,6 +21,7 @@ pnpm dev
```bash ```bash
WEBAGENT_SITES_DIR=../WebAgent-sites WEBAGENT_SITES_DIR=../WebAgent-sites
WEBAGENT_PREVIEWS_DIR=../WebAgent-previews
WEBAGENT_PRODUCTION_DIR=../WebAgent-production WEBAGENT_PRODUCTION_DIR=../WebAgent-production
# 或 WEBAGENT_SITES_DIR=/data/WebAgent-sites # 或 WEBAGENT_SITES_DIR=/data/WebAgent-sites
``` ```
...@@ -53,6 +54,6 @@ WEBAGENT_PRODUCTION_DIR=../WebAgent-production ...@@ -53,6 +54,6 @@ WEBAGENT_PRODUCTION_DIR=../WebAgent-production
- 可接入 Cloudflare for SaaS 自动创建 Custom Hostname、展示证书验证记录并跟踪 HTTPS 状态 - 可接入 Cloudflare for SaaS 自动创建 Custom Hostname、展示证书验证记录并跟踪 HTTPS 状态
- 自定义域名使用独立根路径产物,域名故障不影响系统默认生产链接 - 自定义域名使用独立根路径产物,域名故障不影响系统默认生产链接
- 浏览 Git 历史并安全回滚到任意版本 - 浏览 Git 历史并安全回滚到任意版本
- 生成站点保存在独立的可配置目录,构建缓存等临时数据隔离在 `.runtime/` - 生成站点、测试预览和生产产物保存在独立的可配置目录,构建缓存等临时数据隔离在 `.runtime/`
详细目录约束见 [docs/storage-convention.md](docs/storage-convention.md),Agent 写入规则见 [docs/site-patch-schema.md](docs/site-patch-schema.md) 详细目录约束见 [docs/storage-convention.md](docs/storage-convention.md),Agent 写入规则见 [docs/site-patch-schema.md](docs/site-patch-schema.md)
...@@ -35,7 +35,7 @@ export class BuildManager { ...@@ -35,7 +35,7 @@ export class BuildManager {
} }
async publishPreview(siteId: string, distPath: string): Promise<string> { async publishPreview(siteId: string, distPath: string): Promise<string> {
return this.publishAtomically(path.join(runtimePaths.previews, siteId), distPath); return this.publishAtomically(path.join(config.previewsDir, siteId), distPath);
} }
async publishProduction(siteId: string, distPath: string): Promise<string> { async publishProduction(siteId: string, distPath: string): Promise<string> {
......
...@@ -8,6 +8,10 @@ const configuredSitesDir = process.env.WEBAGENT_SITES_DIR?.trim() || "../WebAgen ...@@ -8,6 +8,10 @@ const configuredSitesDir = process.env.WEBAGENT_SITES_DIR?.trim() || "../WebAgen
const sitesDir = path.isAbsolute(configuredSitesDir) const sitesDir = path.isAbsolute(configuredSitesDir)
? configuredSitesDir ? configuredSitesDir
: path.resolve(rootDir, configuredSitesDir); : path.resolve(rootDir, configuredSitesDir);
const configuredPreviewsDir = process.env.WEBAGENT_PREVIEWS_DIR?.trim() || "../WebAgent-previews";
const previewsDir = path.isAbsolute(configuredPreviewsDir)
? configuredPreviewsDir
: path.resolve(rootDir, configuredPreviewsDir);
const configuredProductionDir = process.env.WEBAGENT_PRODUCTION_DIR?.trim() || "../WebAgent-production"; const configuredProductionDir = process.env.WEBAGENT_PRODUCTION_DIR?.trim() || "../WebAgent-production";
const productionDir = path.isAbsolute(configuredProductionDir) const productionDir = path.isAbsolute(configuredProductionDir)
? configuredProductionDir ? configuredProductionDir
...@@ -26,6 +30,7 @@ export const config = { ...@@ -26,6 +30,7 @@ export const config = {
runtimeDir: path.resolve(currentDir, "../../.runtime"), runtimeDir: path.resolve(currentDir, "../../.runtime"),
templateDir: path.resolve(currentDir, "../../astro-template"), templateDir: path.resolve(currentDir, "../../astro-template"),
sitesDir, sitesDir,
previewsDir,
productionDir, productionDir,
customDomainsDir, customDomainsDir,
host: process.env.HOST || "127.0.0.1", host: process.env.HOST || "127.0.0.1",
...@@ -59,7 +64,6 @@ export const config = { ...@@ -59,7 +64,6 @@ export const config = {
export const runtimePaths = { export const runtimePaths = {
sites: config.sitesDir, sites: config.sitesDir,
builds: path.join(config.runtimeDir, "builds"), builds: path.join(config.runtimeDir, "builds"),
previews: path.join(config.runtimeDir, "previews"),
uploads: path.join(config.runtimeDir, "uploads"), uploads: path.join(config.runtimeDir, "uploads"),
pnpmStore: path.join(config.runtimeDir, "pnpm-store"), pnpmStore: path.join(config.runtimeDir, "pnpm-store"),
logs: path.join(config.runtimeDir, "logs"), logs: path.join(config.runtimeDir, "logs"),
......
...@@ -128,7 +128,7 @@ for (let site of await sites.list()) { ...@@ -128,7 +128,7 @@ for (let site of await sites.list()) {
await versions.rebuild(site.siteId).catch((error) => app.log.warn(error)); await versions.rebuild(site.siteId).catch((error) => app.log.warn(error));
site = await sites.get(site.siteId); site = await sites.get(site.siteId);
} }
const previewPath = path.join(runtimePaths.previews, site.siteId); const previewPath = path.join(config.previewsDir, site.siteId);
if (await access(previewPath).then(() => true).catch(() => false)) { if (await access(previewPath).then(() => true).catch(() => false)) {
await previews.start(site.siteId, previewPath, site.previewPort) await previews.start(site.siteId, previewPath, site.previewPort)
.then(async () => sites.update(site.siteId, { .then(async () => sites.update(site.siteId, {
......
...@@ -77,10 +77,12 @@ export class SiteRepository { ...@@ -77,10 +77,12 @@ export class SiteRepository {
private async initializeRuntime(): Promise<void> { private async initializeRuntime(): Promise<void> {
await Promise.all([ await Promise.all([
...Object.values(runtimePaths).map((directory) => mkdir(directory, { recursive: true })), ...Object.values(runtimePaths).map((directory) => mkdir(directory, { recursive: true })),
mkdir(config.previewsDir, { recursive: true }),
mkdir(config.productionDir, { recursive: true }), mkdir(config.productionDir, { recursive: true }),
mkdir(config.customDomainsDir, { recursive: true }), mkdir(config.customDomainsDir, { recursive: true }),
]); ]);
await this.migrateLegacySites(); await this.migrateLegacySites();
await this.migrateLegacyPreviews();
} }
private async migrateLegacySites(): Promise<void> { private async migrateLegacySites(): Promise<void> {
...@@ -105,4 +107,27 @@ export class SiteRepository { ...@@ -105,4 +107,27 @@ export class SiteRepository {
} }
} }
} }
private async migrateLegacyPreviews(): Promise<void> {
const legacyPreviewsDir = path.join(config.runtimeDir, "previews");
if (path.resolve(legacyPreviewsDir) === path.resolve(config.previewsDir)) return;
const legacyExists = await stat(legacyPreviewsDir).then((value) => value.isDirectory()).catch(() => false);
if (!legacyExists) return;
const entries = await readdir(legacyPreviewsDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || !/^site_[a-z0-9]+$/.test(entry.name)) continue;
const source = path.join(legacyPreviewsDir, entry.name);
const destination = path.join(config.previewsDir, entry.name);
const destinationExists = await stat(destination).then(() => true).catch(() => false);
if (destinationExists) continue;
try {
await rename(source, destination);
} catch (error) {
if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") throw error;
await cp(source, destination, { recursive: true, errorOnExist: true });
await rm(source, { recursive: true, force: true });
}
}
}
} }
...@@ -48,7 +48,7 @@ server { ...@@ -48,7 +48,7 @@ server {
} }
location /previews/ { location /previews/ {
alias /Users/mac/Desktop/code/WebAgent/.runtime/previews/; alias /Users/mac/Desktop/code/WebAgent-previews/;
index index.html; index index.html;
add_header Cache-Control "no-store" always; add_header Cache-Control "no-store" always;
} }
......
# 本地存储规范 # 本地存储规范
生成站点源码、测试构建和生产产物分开保存。`.env` 中默认配置为 `WEBAGENT_SITES_DIR=../WebAgent-sites``WEBAGENT_PRODUCTION_DIR=../WebAgent-production`,相对路径以 WebAgent 项目根目录为基准,也可以指定绝对路径。 生成站点源码、测试构建和生产产物分开保存。`.env` 中默认配置为 `WEBAGENT_SITES_DIR=../WebAgent-sites``WEBAGENT_PREVIEWS_DIR=../WebAgent-previews``WEBAGENT_PRODUCTION_DIR=../WebAgent-production`,相对路径以 WebAgent 项目根目录为基准,也可以指定绝对路径。
```text ```text
WebAgent-sites/ WebAgent-sites/
...@@ -10,6 +10,9 @@ WebAgent-sites/ ...@@ -10,6 +10,9 @@ WebAgent-sites/
└── metadata/ └── metadata/
└── site.json # WebAgent 管理信息,不交给 Agent 修改 └── site.json # WebAgent 管理信息,不交给 Agent 修改
WebAgent-previews/
└── site_xxxxxx/ # 当前代码最近一次构建成功的测试产物
WebAgent-production/ WebAgent-production/
└── site_xxxxxx/ # 用户确认发布的生产静态产物 └── site_xxxxxx/ # 用户确认发布的生产静态产物
...@@ -20,15 +23,13 @@ WebAgent/.runtime/ ...@@ -20,15 +23,13 @@ WebAgent/.runtime/
├── builds/ ├── builds/
│ └── task_xxxxxx/ │ └── task_xxxxxx/
│ └── workspace/ # 临时 Git Worktree,任务结束必须删除 │ └── workspace/ # 临时 Git Worktree,任务结束必须删除
├── previews/
│ └── site_xxxxxx/ # 当前代码最近一次构建成功的测试产物
├── uploads/ # 原始上传文件的临时落点 ├── uploads/ # 原始上传文件的临时落点
├── pnpm-store/ # 所有生成站点共享的 pnpm store ├── pnpm-store/ # 所有生成站点共享的 pnpm store
├── logs/ # Agent 与构建日志 ├── logs/ # Agent 与构建日志
└── nginx/ # 根据已验证域名生成的 Host 路由映射 └── nginx/ # 根据已验证域名生成的 Host 路由映射
``` ```
`WebAgent-sites/``WebAgent-production/``WebAgent-custom-domains/` 是持久数据,应纳入服务器备份;`.runtime/` 是可重建的测试与运行缓存,不提交 Git。修改路径配置后,系统不会自动迁移外部目录之间的数据,需要由管理员手动移动。旧版本位于 `.runtime/sites/` 的站点会在首次启动时自动迁移到当前配置目录。 `WebAgent-sites/``WebAgent-previews/``WebAgent-production/``WebAgent-custom-domains/` 位于项目外部,便于直接查看和按需备份;`.runtime/` 是可重建的运行缓存,不提交 Git。修改路径配置后,系统不会自动迁移外部目录之间的数据,需要由管理员手动移动。旧版本位于 `.runtime/sites/` 的站点和 `.runtime/previews/` 的测试产物会在首次启动时自动迁移到当前配置目录。
## 不变量 ## 不变量
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment