Add structured rebuild entrypoint for AstrBot
This commit is contained in:
parent
85e510fbc0
commit
89bc23e7e7
67
TODO.md
67
TODO.md
|
|
@ -78,59 +78,66 @@
|
||||||
- [x] 生成 `itemCount`(activity)
|
- [x] 生成 `itemCount`(activity)
|
||||||
- [x] 前端更明确地显示“数据更新时间”
|
- [x] 前端更明确地显示“数据更新时间”
|
||||||
|
|
||||||
### 3. 增加变更检测
|
### 3. 增加日志与通知
|
||||||
|
|
||||||
- [ ] 数据无变化时跳过部署
|
- [ ] AstrBot 或 cron 每日 00:00 触发同步与重建
|
||||||
- [ ] 可通过 hash / diff / 序列化比较实现
|
- [x] 封装统一重建指令供 AstrBot 调用(如 `npm run rebuild` 或独立 shell 入口)
|
||||||
|
- [x] 约定结构化执行结果与错误码,便于 AstrBot 判断成功/失败阶段
|
||||||
### 4. 增加日志与通知
|
|
||||||
|
|
||||||
- [ ] AstrBot 每日 00:00 触发同步与重建
|
|
||||||
- [ ] 成功时发送通知
|
- [ ] 成功时发送通知
|
||||||
- [ ] 失败时发送告警
|
- [ ] 失败时发送告警
|
||||||
- [ ] 支持手动补跑一次
|
- [x] 支持手动补跑一次
|
||||||
|
|
||||||
|
### 4. 补部署文件与部署说明
|
||||||
|
|
||||||
|
- [ ] 增加 `Dockerfile`
|
||||||
|
- [ ] 增加 `docker-compose.yml`
|
||||||
|
- [ ] 明确静态产物部署方式
|
||||||
|
- [ ] 写清部署步骤与重建入口
|
||||||
|
|
||||||
|
### 5. 补运行文档
|
||||||
|
|
||||||
|
- [ ] 在 README 或单独文档里写清:
|
||||||
|
- [x] AstrBot / cron 如何触发
|
||||||
|
- [x] AstrBot 调用的统一构建指令与返回约定
|
||||||
|
- [x] 错误码含义(sync/build/deploy/unknown)
|
||||||
|
- [ ] 环境变量如何配置
|
||||||
|
- [ ] Gitea / Seafile 映射如何填写
|
||||||
|
- [ ] 同步脚本职责
|
||||||
|
- [ ] 构建失败如何排查
|
||||||
|
|
||||||
## P2:后续完善项
|
## P2:后续完善项
|
||||||
|
|
||||||
### 5. 项目与资源映射收口
|
### 6. 项目与资源映射收口
|
||||||
|
|
||||||
- [ ] 明确“哪些仓库进入 projects”
|
- [ ] 明确“哪些仓库进入 projects”
|
||||||
- [ ] 明确“哪些仓库只用于 activity 不进入 projects”
|
- [ ] 明确“哪些仓库只用于 activity 不进入 projects”
|
||||||
- [ ] 决定项目封面图是继续本地维护,还是允许远程字段补充
|
- [ ] 决定项目封面图是继续本地维护,还是允许远程字段补充
|
||||||
- [ ] 决定 `download_link` 是否最终完全被 `downloads[]` 取代
|
- [ ] 决定 `download_link` 是否最终完全被 `downloads[]` 取代
|
||||||
|
|
||||||
### 6. Seafile 下载策略收口
|
### 7. Seafile 下载策略收口
|
||||||
|
|
||||||
- [x] 首版支持只展示元数据 + 下载链接
|
- [x] 首版支持只展示元数据 + 下载链接
|
||||||
- [ ] 评估是否需要构建时同步特定打包文件到 `public/downloads/`
|
- [ ] 评估是否需要构建时同步特定打包文件到 `public/downloads/`
|
||||||
- [ ] 如同步文件本体,补充大小限制、覆盖策略、清理策略
|
- [ ] 如同步文件本体,补充大小限制、覆盖策略、清理策略
|
||||||
- [ ] 评估是否需要“自动扫描目录”而不是完全依赖映射文件
|
- [ ] 评估是否需要“自动扫描目录”而不是完全依赖映射文件
|
||||||
|
|
||||||
### 7. 首页与页面体验完善
|
### 8. 首页与页面体验完善
|
||||||
|
|
||||||
- [ ] 首页增加 shares preview / recent resources 模块
|
- [ ] 首页增加 shares preview / recent resources 模块
|
||||||
- [ ] `/shares` 页增加更明确的类型 / 所属项目 / 链接状态展示优化
|
- [ ] `/shares` 页增加更明确的类型 / 所属项目 / 链接状态展示优化
|
||||||
- [ ] 项目卡片下载区样式优化
|
- [ ] 项目卡片下载区样式优化
|
||||||
- [ ] 前端更明确显示同步时间与数据来源
|
- [ ] 前端更明确显示同步时间与数据来源
|
||||||
|
|
||||||
### 8. 补部署文件
|
### 9. 变更检测(当前不是必须项)
|
||||||
|
|
||||||
- [ ] 增加 `Dockerfile`
|
- [ ] 数据无变化时跳过部署
|
||||||
- [ ] 增加 `docker-compose.yml`
|
- [ ] 可通过 hash / diff / 序列化比较实现
|
||||||
- [ ] 明确静态产物部署方式
|
- [ ] 当前数据量较小,可在部署资源或通知噪音变高时再做
|
||||||
|
|
||||||
### 9. 补文档
|
|
||||||
|
|
||||||
- [ ] 在 README 或单独文档里写清:
|
|
||||||
- AstrBot / cron 如何触发
|
|
||||||
- 环境变量如何配置
|
|
||||||
- Gitea / Seafile 映射如何填写
|
|
||||||
- 同步脚本职责
|
|
||||||
- 构建失败如何排查
|
|
||||||
|
|
||||||
## 推荐最小闭环
|
## 推荐最小闭环
|
||||||
|
|
||||||
- [ ] AstrBot 每天 00:00 触发
|
- [ ] AstrBot 或 cron 每天 00:00 触发
|
||||||
|
- [ ] 调用统一重建指令并读取错误码 / 执行结果
|
||||||
- [x] 执行 `npm run content:sync`
|
- [x] 执行 `npm run content:sync`
|
||||||
- [x] 执行 `npm run build`
|
- [x] 执行 `npm run build`
|
||||||
- [ ] 部署静态产物
|
- [ ] 部署静态产物
|
||||||
|
|
@ -138,8 +145,8 @@
|
||||||
|
|
||||||
## 实施顺序建议
|
## 实施顺序建议
|
||||||
|
|
||||||
1. 先补 projects / shares / activity schema 校验
|
1. 先补 AstrBot / cron 触发与通知
|
||||||
2. 再整理并提交 `src/config.ts` / `src/content/projects/index.json`
|
2. 再补 Docker / docker-compose / 静态产物部署说明
|
||||||
3. 再补首页 shares preview
|
3. 再补 README / 运维文档,形成可维护闭环
|
||||||
4. 再补 AstrBot / cron 触发与通知
|
4. 然后回头做首页 shares preview 和列表体验优化
|
||||||
5. 最后补 Docker / README / 部署说明
|
5. 变更检测放到最后,需要时再做
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
# 重建触发与错误码约定
|
||||||
|
|
||||||
|
本文档定义仓库对外提供的统一重建入口,供 AstrBot、cron 或手工执行复用。
|
||||||
|
|
||||||
|
## 统一入口
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
该命令会顺序执行:
|
||||||
|
|
||||||
|
1. `npm run content:sync`
|
||||||
|
2. `npm run build`
|
||||||
|
|
||||||
|
当前还不包含部署步骤;后续如接入部署,可继续扩展到同一入口里。
|
||||||
|
|
||||||
|
## 适合 AstrBot 的调用方式
|
||||||
|
|
||||||
|
AstrBot 只需要做三件事:
|
||||||
|
|
||||||
|
1. 定时触发 `npm run rebuild`
|
||||||
|
2. 读取进程退出码
|
||||||
|
3. 从输出中提取 `REBUILD_RESULT ...` 这一行,作为结构化结果
|
||||||
|
|
||||||
|
推荐通知内容:
|
||||||
|
|
||||||
|
- 成功:开始时间、结束时间、总耗时
|
||||||
|
- 失败:失败阶段、错误码、错误符号、最后几行日志
|
||||||
|
|
||||||
|
## 结构化输出
|
||||||
|
|
||||||
|
命令结束前会输出一行 JSON:
|
||||||
|
|
||||||
|
```text
|
||||||
|
REBUILD_RESULT {"ok":true,"stage":"build","code":0,"symbol":"SUCCESS",...}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
- `ok`: 是否成功
|
||||||
|
- `stage`: 失败或结束所在阶段,当前可能为 `sync` / `build` / `bootstrap`
|
||||||
|
- `code`: 退出码
|
||||||
|
- `symbol`: 退出码对应的文本符号
|
||||||
|
- `startedAt`: 整体开始时间
|
||||||
|
- `finishedAt`: 整体结束时间
|
||||||
|
- `durationMs`: 整体耗时
|
||||||
|
- `failedStepDurationMs`: 当前失败步骤耗时;成功时为最后一步耗时
|
||||||
|
- `logTail`: 最后若干行日志,便于 AstrBot 通知时直接带上排障信息
|
||||||
|
|
||||||
|
## 错误码
|
||||||
|
|
||||||
|
### 成功
|
||||||
|
|
||||||
|
| Code | Symbol | 含义 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 0 | `SUCCESS` | 全部成功 |
|
||||||
|
|
||||||
|
### 同步阶段
|
||||||
|
|
||||||
|
| Code | Symbol | 含义 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 10 | `SYNC_FAILED` | 通用同步失败 |
|
||||||
|
| 11 | `GITEA_SYNC_FAILED` | Gitea 同步失败 |
|
||||||
|
| 12 | `SEAFILE_SYNC_FAILED` | Seafile 同步失败 |
|
||||||
|
| 13 | `SCHEMA_VALIDATION_FAILED` | JSON/Schema 校验失败 |
|
||||||
|
|
||||||
|
### 构建阶段
|
||||||
|
|
||||||
|
| Code | Symbol | 含义 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 20 | `BUILD_FAILED` | Astro 构建失败 |
|
||||||
|
|
||||||
|
### 部署阶段(预留)
|
||||||
|
|
||||||
|
| Code | Symbol | 含义 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 30 | `DEPLOY_FAILED` | 静态产物部署失败 |
|
||||||
|
|
||||||
|
### 运行阶段
|
||||||
|
|
||||||
|
| Code | Symbol | 含义 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 40 | `CONFIG_INVALID` | 运行环境缺少必要命令或配置异常 |
|
||||||
|
| 50 | `UNKNOWN_ERROR` | 未归类异常 |
|
||||||
|
|
||||||
|
## 手动补跑
|
||||||
|
|
||||||
|
本地或服务器上都可以直接执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
如果只是想单独排查同步阶段,也可以先运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run content:sync
|
||||||
|
```
|
||||||
|
|
||||||
|
## 建议的 AstrBot 判定逻辑
|
||||||
|
|
||||||
|
- 退出码 `0`:发送成功通知
|
||||||
|
- 非 `0`:发送失败告警
|
||||||
|
- 优先展示:
|
||||||
|
- `symbol`
|
||||||
|
- `stage`
|
||||||
|
- `code`
|
||||||
|
- `logTail`
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"content:sync": "node --env-file-if-exists=.env --experimental-strip-types ./scripts/sync-content.ts",
|
"content:sync": "node --env-file-if-exists=.env --experimental-strip-types ./scripts/sync-content.ts",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"rebuild": "npm run content:sync && npm run build",
|
"rebuild": "node --env-file-if-exists=.env --experimental-strip-types ./scripts/rebuild.ts",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
export const REBUILD_EXIT_CODES = {
|
||||||
|
SUCCESS: 0,
|
||||||
|
SYNC_FAILED: 10,
|
||||||
|
GITEA_SYNC_FAILED: 11,
|
||||||
|
SEAFILE_SYNC_FAILED: 12,
|
||||||
|
SCHEMA_VALIDATION_FAILED: 13,
|
||||||
|
BUILD_FAILED: 20,
|
||||||
|
DEPLOY_FAILED: 30,
|
||||||
|
CONFIG_INVALID: 40,
|
||||||
|
UNKNOWN_ERROR: 50,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type RebuildExitCode =
|
||||||
|
(typeof REBUILD_EXIT_CODES)[keyof typeof REBUILD_EXIT_CODES];
|
||||||
|
|
||||||
|
export function getRebuildExitSymbol(code: number) {
|
||||||
|
const entry = Object.entries(REBUILD_EXIT_CODES).find(([, value]) => value === code);
|
||||||
|
return entry?.[0] ?? 'UNRECOGNIZED_EXIT_CODE';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { REBUILD_EXIT_CODES, getRebuildExitSymbol } from './rebuild-codes.ts';
|
||||||
|
|
||||||
|
type RebuildStage = 'sync' | 'build' | 'deploy' | 'bootstrap';
|
||||||
|
|
||||||
|
type StepResult = {
|
||||||
|
ok: boolean;
|
||||||
|
exitCode: number;
|
||||||
|
stage: RebuildStage;
|
||||||
|
durationMs: number;
|
||||||
|
tail: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const startedAt = new Date();
|
||||||
|
console.log(`[rebuild] start ${startedAt.toISOString()}`);
|
||||||
|
|
||||||
|
const syncResult = await runNpmStep({
|
||||||
|
stage: 'sync',
|
||||||
|
script: 'content:sync',
|
||||||
|
failureCode: REBUILD_EXIT_CODES.SYNC_FAILED,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!syncResult.ok) {
|
||||||
|
return finish(syncResult, startedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildResult = await runNpmStep({
|
||||||
|
stage: 'build',
|
||||||
|
script: 'build',
|
||||||
|
failureCode: REBUILD_EXIT_CODES.BUILD_FAILED,
|
||||||
|
});
|
||||||
|
|
||||||
|
return finish(buildResult, startedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runNpmStep(input: {
|
||||||
|
stage: RebuildStage;
|
||||||
|
script: string;
|
||||||
|
failureCode: number;
|
||||||
|
}): Promise<StepResult> {
|
||||||
|
console.log(`[rebuild] stage=${input.stage} command="npm run ${input.script}"`);
|
||||||
|
|
||||||
|
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const tailBuffer = createTailBuffer(40);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const child = spawn(command, ['run', input.script], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: process.env,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.on('data', (chunk) => {
|
||||||
|
const text = chunk.toString();
|
||||||
|
process.stdout.write(text);
|
||||||
|
tailBuffer.push(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (chunk) => {
|
||||||
|
const text = chunk.toString();
|
||||||
|
process.stderr.write(text);
|
||||||
|
tailBuffer.push(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (error) => {
|
||||||
|
const durationMs = Date.now() - startedAt;
|
||||||
|
const exitCode = classifySpawnError(error);
|
||||||
|
tailBuffer.push(`[rebuild] failed to spawn command: ${error.message}\n`);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
ok: false,
|
||||||
|
exitCode,
|
||||||
|
stage: input.stage,
|
||||||
|
durationMs,
|
||||||
|
tail: tailBuffer.read(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', (code) => {
|
||||||
|
const durationMs = Date.now() - startedAt;
|
||||||
|
const normalizedExitCode =
|
||||||
|
code === 0 ? REBUILD_EXIT_CODES.SUCCESS : normalizeStageExitCode(input.failureCode, code);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
ok: normalizedExitCode === REBUILD_EXIT_CODES.SUCCESS,
|
||||||
|
exitCode: normalizedExitCode,
|
||||||
|
stage: input.stage,
|
||||||
|
durationMs,
|
||||||
|
tail: tailBuffer.read(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish(result: StepResult, startedAt: Date) {
|
||||||
|
const finishedAt = new Date();
|
||||||
|
const payload = {
|
||||||
|
ok: result.ok,
|
||||||
|
stage: result.stage,
|
||||||
|
code: result.exitCode,
|
||||||
|
symbol: getRebuildExitSymbol(result.exitCode),
|
||||||
|
startedAt: startedAt.toISOString(),
|
||||||
|
finishedAt: finishedAt.toISOString(),
|
||||||
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
||||||
|
failedStepDurationMs: result.durationMs,
|
||||||
|
logTail: result.tail,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`REBUILD_RESULT ${JSON.stringify(payload)}`);
|
||||||
|
|
||||||
|
if (!result.ok) {
|
||||||
|
console.error(
|
||||||
|
`[rebuild] failed stage=${result.stage} code=${result.exitCode} symbol=${payload.symbol}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(`[rebuild] success code=0 symbol=${payload.symbol}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exitCode = result.exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeStageExitCode(fallbackCode: number, childCode: number | null) {
|
||||||
|
if (childCode == null) {
|
||||||
|
return fallbackCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.values(REBUILD_EXIT_CODES).includes(childCode as never)) {
|
||||||
|
return childCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifySpawnError(error: NodeJS.ErrnoException) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
return REBUILD_EXIT_CODES.CONFIG_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REBUILD_EXIT_CODES.UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTailBuffer(maxLines: number) {
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
push(chunk: string) {
|
||||||
|
const normalized = chunk.replace(/\r\n/g, '\n');
|
||||||
|
for (const line of normalized.split('\n')) {
|
||||||
|
if (!line.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (lines.length > maxLines) {
|
||||||
|
lines.shift();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
read() {
|
||||||
|
return [...lines];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error('[rebuild] unhandled failure');
|
||||||
|
console.error(message);
|
||||||
|
console.log(
|
||||||
|
`REBUILD_RESULT ${JSON.stringify({
|
||||||
|
ok: false,
|
||||||
|
stage: 'bootstrap',
|
||||||
|
code: REBUILD_EXIT_CODES.UNKNOWN_ERROR,
|
||||||
|
symbol: getRebuildExitSymbol(REBUILD_EXIT_CODES.UNKNOWN_ERROR),
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
finishedAt: new Date().toISOString(),
|
||||||
|
durationMs: 0,
|
||||||
|
failedStepDurationMs: 0,
|
||||||
|
logTail: [message],
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
process.exitCode = REBUILD_EXIT_CODES.UNKNOWN_ERROR;
|
||||||
|
});
|
||||||
|
|
@ -3,6 +3,7 @@ import path from 'node:path';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { fetchGiteaSnapshot, type GeneratedGiteaActivity } from './fetch-gitea.ts';
|
import { fetchGiteaSnapshot, type GeneratedGiteaActivity } from './fetch-gitea.ts';
|
||||||
import { fetchSeafileSnapshot } from './fetch-seafile.ts';
|
import { fetchSeafileSnapshot } from './fetch-seafile.ts';
|
||||||
|
import { REBUILD_EXIT_CODES, getRebuildExitSymbol } from './rebuild-codes.ts';
|
||||||
import {
|
import {
|
||||||
generatedProjectListSchema,
|
generatedProjectListSchema,
|
||||||
generatedShareListSchema,
|
generatedShareListSchema,
|
||||||
|
|
@ -203,7 +204,29 @@ async function writeJson(filePath: string, data: unknown) {
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
main().catch((error) => {
|
||||||
|
const exitCode = classifySyncExitCode(error);
|
||||||
console.error('[sync-content] failed');
|
console.error('[sync-content] failed');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exitCode = 1;
|
console.error(
|
||||||
|
`[sync-content] exit code ${exitCode} (${getRebuildExitSymbol(exitCode)})`,
|
||||||
|
);
|
||||||
|
process.exitCode = exitCode;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function classifySyncExitCode(error: unknown) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
if (error instanceof SyntaxError || message.includes('[data-schema]')) {
|
||||||
|
return REBUILD_EXIT_CODES.SCHEMA_VALIDATION_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.includes('[fetch-gitea]')) {
|
||||||
|
return REBUILD_EXIT_CODES.GITEA_SYNC_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.includes('[fetch-seafile]')) {
|
||||||
|
return REBUILD_EXIT_CODES.SEAFILE_SYNC_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return REBUILD_EXIT_CODES.SYNC_FAILED;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue