Compare commits

...

11 Commits

Author SHA1 Message Date
xiongxiao
c222a007c1 更新依赖版本;重构代码以使用环境变量中的令牌;添加身份验证中间件以增强安全性 2026-03-22 02:03:52 +08:00
xiongxiao
c5a8d22249 编辑文件 .cnb.yml 2026-03-20 00:17:40 +08:00
xiongxiao
88a688d5e3 更新 .cnb.yml 中的脚本格式,优化可读性;更新 package.json 中的依赖版本;新增 bun.lock 文件以管理依赖锁定 2026-03-18 13:38:41 +08:00
xiongxiao
2b7b541cb6 编辑文件 .cnb.yml 2026-03-18 13:32:06 +08:00
xiongxiao
998783ea4e 更新上报仓库信息表格,添加更新时间列 2026-03-18 01:43:34 +08:00
xiongxiao
62b70712aa 修复上报仓库信息表格,移除仓库名称列 2026-03-18 01:39:59 +08:00
xiongxiao
aed05790ea fix 2026-03-18 01:37:30 +08:00
xiongxiao
5bf3a7a063 update 2026-03-18 01:34:14 +08:00
xiongxiao
28a5f9a7f1 fix 2026-03-18 01:29:09 +08:00
xiongxiao
cbdaeffd16 udpate 2026-03-18 01:26:40 +08:00
xiongxiao
83ac00588e init 2026-03-18 01:25:49 +08:00
8 changed files with 300 additions and 15 deletions

View File

@@ -0,0 +1,21 @@
.crontab-job: &crontab-job
- docker:
image: docker.cnb.cool/kevisual/dev-env/ubuntu-bun:latest
runner:
cpus: 2
imports:
- https://cnb.cool/kevisual/env/-/blob/main/.env
- https://cnb.cool/kevisual/env/-/blob/main/ssh.yml
- https://cnb.cool/kevisual/env/-/blob/main/ssh-config.yml
stages:
- name: 检测版本更新
script: |
bun i
bun run src/cli.ts repo sync
timeout: 20s
main:
"crontab: 0 11,23 * * *": !reference [.crontab-job]
$:
push: !reference [.crontab-job]

87
bun.lock Normal file
View File

@@ -0,0 +1,87 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "to-my-gitea",
"dependencies": {
"@kevisual/cnb": "^0.0.56",
"@kevisual/context": "^0.0.8",
"@kevisual/gitea": "^0.0.6",
"@kevisual/router": "^0.1.6",
"dayjs": "^1.11.20",
},
},
},
"packages": {
"@kevisual/cnb": ["@kevisual/cnb@0.0.56", "https://registry.npmmirror.com/@kevisual/cnb/-/cnb-0.0.56.tgz", { "dependencies": { "@kevisual/query": "^0.0.53", "@kevisual/router": "^0.1.3", "@kevisual/use-config": "^1.0.30", "@opencode-ai/sdk": "^1.2.27", "es-toolkit": "^1.45.1", "nanoid": "^5.1.7", "unstorage": "^1.17.4", "ws": "npm:@kevisual/ws" }, "bin": { "cnb": "bin/index.js", "cloud": "bin/index.js", "cloud-npc": "bin/npc.js" } }, "sha512-Pa70TI/zVC7DOSecrRztB8QrRlf+iuFCGDH/O3YEQw/eMoyDdBtIxR3Ewnsu+n+BN0od6jcsSOhDW6kzqeX8cA=="],
"@kevisual/context": ["@kevisual/context@0.0.8", "https://registry.npmmirror.com/@kevisual/context/-/context-0.0.8.tgz", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="],
"@kevisual/gitea": ["@kevisual/gitea@0.0.6", "https://registry.npmmirror.com/@kevisual/gitea/-/gitea-0.0.6.tgz", {}, "sha512-/qJha6IQ5VO+8WOGkLIMROmP0CvAaID0rPPyd5gtzl6yATqQLJS13Fm6Lfp5U5ImjTNmsq08khZqrj93Mz60cw=="],
"@kevisual/load": ["@kevisual/load@0.0.6", "https://registry.npmmirror.com/@kevisual/load/-/load-0.0.6.tgz", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="],
"@kevisual/query": ["@kevisual/query@0.0.53", "https://registry.npmmirror.com/@kevisual/query/-/query-0.0.53.tgz", {}, "sha512-PAhpCLBr0emz0lGNlTVHMbJiC5wrtGLbInPddRzgKE35fiyNt+SWSsUWABiD0DeNrLN/OxWyAFobt880Z/e5MQ=="],
"@kevisual/router": ["@kevisual/router@0.1.6", "https://registry.npmmirror.com/@kevisual/router/-/router-0.1.6.tgz", { "dependencies": { "crypto-js": "^4.2.0", "es-toolkit": "^1.45.1", "zod": "^4.3.6" } }, "sha512-uQYxDd4j0ZKuuPXduSMSvckjEKi99hVRp7vz5AUFVDVbEBmNQBgDGbwmz9+X/DR/Gjx++x3m8XvYcAwuEzPOKw=="],
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "https://registry.npmmirror.com/@kevisual/use-config/-/use-config-1.0.30.tgz", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.27", "https://registry.npmmirror.com/@opencode-ai/sdk/-/sdk-1.2.27.tgz", {}, "sha512-Wk0o/I+Fo+wE3zgvlJDs8Fb67KlKqX0PrV8dK5adSDkANq6r4Z25zXJg2iOir+a8ntg3rAcpel1OY4FV/TwRUA=="],
"anymatch": ["anymatch@3.1.3", "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"chokidar": ["chokidar@5.0.0", "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"cookie-es": ["cookie-es@1.2.2", "https://registry.npmmirror.com/cookie-es/-/cookie-es-1.2.2.tgz", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
"crossws": ["crossws@0.3.5", "https://registry.npmmirror.com/crossws/-/crossws-0.3.5.tgz", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
"crypto-js": ["crypto-js@4.2.0", "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
"dayjs": ["dayjs@1.11.20", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
"defu": ["defu@6.1.4", "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"destr": ["destr@2.0.5", "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"dotenv": ["dotenv@17.3.1", "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"es-toolkit": ["es-toolkit@1.45.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
"eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"h3": ["h3@1.15.8", "https://registry.npmmirror.com/h3/-/h3-1.15.8.tgz", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-iOH6Vl8mGd9nNfu9C0IZ+GuOAfJHcyf3VriQxWaSWIB76Fg4BnFuk4cxBxjmQSSxJS664+pgjP6e7VBnUzFfcg=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "https://registry.npmmirror.com/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
"lru-cache": ["lru-cache@11.2.7", "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.7.tgz", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
"nanoid": ["nanoid@5.1.7", "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.7.tgz", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-mock-http": ["node-mock-http@1.0.4", "https://registry.npmmirror.com/node-mock-http/-/node-mock-http-1.0.4.tgz", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
"normalize-path": ["normalize-path@3.0.0", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"ofetch": ["ofetch@1.5.1", "https://registry.npmmirror.com/ofetch/-/ofetch-1.5.1.tgz", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
"picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"radix3": ["radix3@1.1.2", "https://registry.npmmirror.com/radix3/-/radix3-1.1.2.tgz", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"readdirp": ["readdirp@5.0.0", "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"ufo": ["ufo@1.6.3", "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
"uncrypto": ["uncrypto@0.1.3", "https://registry.npmmirror.com/uncrypto/-/uncrypto-0.1.3.tgz", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"unstorage": ["unstorage@1.17.4", "https://registry.npmmirror.com/unstorage/-/unstorage-1.17.4.tgz", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
"ws": ["@kevisual/ws@8.19.0", "https://registry.npmmirror.com/@kevisual/ws/-/ws-8.19.0.tgz", {}, "sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw=="],
"zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
}
}

0
compose.yml Normal file
View File

View File

@@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"cli": "bun run src/cli.ts "
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
@@ -12,10 +12,10 @@
"packageManager": "pnpm@10.32.1",
"type": "module",
"dependencies": {
"@kevisual/cnb": "^0.0.51",
"@kevisual/cnb": "^0.0.56",
"@kevisual/context": "^0.0.8",
"@kevisual/gitea": "^0.0.6",
"@kevisual/router": "^0.1.3",
"@kevisual/router": "^0.1.6",
"dayjs": "^1.11.20"
}
}

View File

@@ -1,5 +1,5 @@
import { App } from "@kevisual/router";
import { useContextKey } from "@kevisual/context";
import { useContextKey, useKey } from "@kevisual/context";
import { CNB } from "@kevisual/cnb";
import { Gitea } from '@kevisual/gitea';
import path from "node:path";
@@ -9,15 +9,15 @@ export const app = useContextKey("app", () => {
});
return app;
});
export const token = useKey("CNB_API_KEY") || useKey('CNB_TOKEN')
export const cnb = useContextKey("cnb", () => {
const token = useContextKey("CNB_API_KEY") || useContextKey('CNB_TOKEN')
return new CNB({ token });
});
export const gitea = useContextKey('gitea', () => {
const GITEA_TOKEN = useContextKey("GITEA_TOKEN")
const GITEA_URL = useContextKey("GITEA_URL")
const GITEA_TOKEN = useKey("GITEA_TOKEN")
const GITEA_URL = useKey("GITEA_URL")
return new Gitea({
token: GITEA_TOKEN,
baseURL: GITEA_URL,

View File

@@ -1,2 +1,7 @@
export * from './app.ts';
import './routes/cnb.ts';
import { app } from './app.ts';
import './routes/cnb.ts';
import './routes/sync.ts';
app.createAuth((ctx) => { });
app.createRouteList()

View File

@@ -1,18 +1,111 @@
import { useRepoInfoEnv } from '@kevisual/cnb';
import { app, cnb } from '../app';
import dayjs from 'dayjs';
import { z } from 'zod';
const repoSchema = z.object({
path: z.string().describe('仓库路径'),
name: z.string().describe('仓库名称'),
web_url: z.string().describe('仓库链接'),
description: z.string().describe('仓库描述'),
topics: z.array(z.string()).describe('仓库标签'),
giteaUrl: z.string().optional().describe('gitea仓库链接'),
updatedAt: z.string().describe('仓库更新时间'),
}).describe('仓库信息');
export type Repo = z.infer<typeof repoSchema>;
app.route({
path: 'cnb',
key: 'list-today',
description: '获取今日更新的仓库列表',
middleware: ['auth']
}).define(async (ctx) => {
const res = await cnb.repo.getRepoList({ page_size: 100 });
const today = dayjs().format('YYYY-MM-DD');
const res = await cnb.repo.getRepoList({ status: 'active', page_size: 100 });
const list = res.data || [];
const twelveHoursAgo = dayjs().subtract(12, 'hour');
const todayList = list.filter(item => {
const updatedAt = dayjs(item.updated_at).format('YYYY-MM-DD');
return updatedAt === today;
const updatedAt = dayjs(item.updated_at);
return updatedAt.isAfter(twelveHoursAgo);
});
ctx.body = {
list: todayList,
const _todayList = todayList.map(item => ({
path: item.path,
name: item.name,
web_url: item.web_url,
description: item.description,
topics: item.topics ? item.topics.split(',').filter(Boolean) : [],
updatedAt: dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss'),
}))
const repositories = _todayList.filter(item => item.topics.includes('gitea'));
if (_todayList.length > 5) {
app.run({ path: 'cnb', key: 'report', payload: { repoList: _todayList } });
}
}).addTo(app)
ctx.body = {
list: repositories
}
}).addTo(app)
app.route({
path: 'cnb',
key: 'report',
description: '上报仓库信息',
middleware: ['auth'],
metadata: {
args: {
repoList: z.array(repoSchema).describe('仓库列表')
}
}
}).define(async (ctx) => {
const { repoList } = ctx.args;
// 处理上报的仓库信息
await closeOldReports();
if (Array.isArray(repoList)) {
let infoTable = '| 仓库路径 | 仓库链接 | 仓库描述 | 仓库标签 | 更新时间 |\n';
infoTable += '| --- | --- | --- | --- | --- |\n';
repoList.forEach(repo => {
const topics = repo.topics.join(', ');
infoTable += `| ${repo.path} | [链接](${repo.web_url}) | ${repo.description} | ${topics} | ${repo.updatedAt} |\n`;
});
console.log('上报的仓库信息表格:\n', infoTable);
const repoInfo = useRepoInfoEnv();
let afterInfo = '来源CNB,';
if (repoInfo) {
afterInfo += `构建仓库:[${repoInfo.repoSlug}](${repoInfo.repoUrlHttps})`;
}
cnb.issue.createIssue('kevisual/kevisual', {
title: `仓库信息上报 - ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`,
body: `以下是上报的仓库信息:\n\n${infoTable}\n\n${afterInfo}`,
// @ts-ignore
labels: ['report']
})
}
}).addTo(app)
app.route({
path: 'cnb',
key: 'close-old-reports',
middleware: ['auth'],
description: '关闭过期的报告',
}).define(async (ctx) => {
await closeOldReports();
}).addTo(app)
export const closeOldReports = async () => {
const res = await cnb.issue.getList('kevisual/kevisual', {
state: 'open',
// @ts-ignore
labels: ['report']
});
const issues = res.data || [];
const oneDayAgo = dayjs().subtract(1, 'day');
let closedCount = 0;
for (const issue of issues) {
const updatedAt = dayjs(issue.updated_at);
if (updatedAt.isBefore(oneDayAgo)) {
const res = await cnb.issue.updateIssue('kevisual/kevisual', issue.number, { state: 'closed', state_reason: 'completed' });
if (res.code !== 200) {
console.error(`关闭 issue ${issue.number} 失败`, res);
} else {
closedCount++;
}
}
}
console.log(`已处理 ${issues.length} 个报告,关闭了 ${closedCount} 个过期报告`);
}

79
src/routes/sync.ts Normal file
View File

@@ -0,0 +1,79 @@
import { app, gitea, rootPath, token } from '../app'
import { Repo } from './cnb';
import { execSync } from 'node:child_process';
import path from 'node:path';
import fs from 'fs';
import { useKey } from '@kevisual/context';
app.route({
path: 'repo',
key: 'sync',
middleware: ['auth'],
description: '同步仓库数据',
}).define(async (ctx) => {
const res = await app.run({ path: 'cnb', key: 'list-today' });
if (res.code === 200) {
const list: Repo[] = res.data.list || [];
let syncList: Repo[] = [];
for (const item of list) {
try {
const [owner, repo] = item.path.split('/');
const giteaRepo = await gitea.repo.getRepo(owner, repo);
const giteaUsername = 'oauth2';
const giteaPassword = useKey('GITEA_TOKEN');
const giteaHost = useKey('GITEA_HOST') || 'git.xiongxiao.me';
item.giteaUrl = `https://${giteaUsername}:${giteaPassword}@${giteaHost}/${owner}/${repo}.git`;
if (giteaRepo.code === 200) {
// 已经存在了
} else {
await gitea.repo.createRepo({
name: owner + '/' + repo,
description: item.description,
});
}
syncList.push(item);
} catch (err) {
console.error(`处理 ${item.path} 失败`, item);
}
}
// 开始同步
for (const repo of syncList) {
try {
await sync(repo);
} catch (err) {
console.error(`同步 ${repo.path} 失败`, repo);
}
}
}
}).addTo(app)
const sync = async (repo: Repo) => {
const cwd = path.join(rootPath, repo.name);
if (!fs.existsSync(cwd)) {
const webUrl = repo.web_url;
let cloneUrl: string;
if (webUrl.startsWith('http://')) {
cloneUrl = webUrl.replace('http://', `http://cnb:${token}@`);
} else if (webUrl.startsWith('https://')) {
cloneUrl = webUrl.replace('https://', `https://cnb:${token}@`);
} else {
cloneUrl = webUrl;
}
execSync(`git clone ${cloneUrl} ${cwd}`);
}
// 添加一个remote 叫做gitea的远程仓库指向gitea的地址
const remoteUrl = repo.giteaUrl!;
try {
execSync(`git remote add gitea ${remoteUrl}`, { cwd });
} catch (err) {
// 已经添加过了
}
try {
// 拉取最新的代码
execSync(`git pull origin main`, { cwd });
// 推送到gitea
execSync(`git push gitea main`, { cwd });
} catch (err) {
console.error(`同步推送 ${repo.path} 失败`, repo);
}
}