init
This commit is contained in:
20
.cnb.yml
20
.cnb.yml
@@ -0,0 +1,20 @@
|
|||||||
|
.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.development
|
||||||
|
- https://cnb.cool/kevisual/env/-/blob/main/ssh.yml
|
||||||
|
- https://cnb.cool/kevisual/env/-/blob/main/ssh-config.yml
|
||||||
|
stages:
|
||||||
|
- name: 检测版本更新
|
||||||
|
script: |
|
||||||
|
bun run src/cli.ts cnb sync
|
||||||
|
timeout: 20s
|
||||||
|
|
||||||
|
main:
|
||||||
|
"crontab: 0 11,23 * * *": !reference [.crontab-job]
|
||||||
|
|
||||||
|
$:
|
||||||
|
push: !reference [.crontab-job]
|
||||||
0
compose.yml
Normal file
0
compose.yml
Normal file
@@ -1,5 +1,5 @@
|
|||||||
import { App } from "@kevisual/router";
|
import { App } from "@kevisual/router";
|
||||||
import { useContextKey } from "@kevisual/context";
|
import { useContextKey, useKey } from "@kevisual/context";
|
||||||
import { CNB } from "@kevisual/cnb";
|
import { CNB } from "@kevisual/cnb";
|
||||||
import { Gitea } from '@kevisual/gitea';
|
import { Gitea } from '@kevisual/gitea';
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
@@ -11,13 +11,13 @@ export const app = useContextKey("app", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const cnb = useContextKey("cnb", () => {
|
export const cnb = useContextKey("cnb", () => {
|
||||||
const token = useContextKey("CNB_API_KEY") || useContextKey('CNB_TOKEN')
|
const token = useKey("CNB_API_KEY") || useKey('CNB_TOKEN')
|
||||||
return new CNB({ token });
|
return new CNB({ token });
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gitea = useContextKey('gitea', () => {
|
export const gitea = useContextKey('gitea', () => {
|
||||||
const GITEA_TOKEN = useContextKey("GITEA_TOKEN")
|
const GITEA_TOKEN = useKey("GITEA_TOKEN")
|
||||||
const GITEA_URL = useContextKey("GITEA_URL")
|
const GITEA_URL = useKey("GITEA_URL")
|
||||||
return new Gitea({
|
return new Gitea({
|
||||||
token: GITEA_TOKEN,
|
token: GITEA_TOKEN,
|
||||||
baseURL: GITEA_URL,
|
baseURL: GITEA_URL,
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './app.ts';
|
export * from './app.ts';
|
||||||
import './routes/cnb.ts';
|
import './routes/cnb.ts';
|
||||||
|
import './routes/sync.ts';
|
||||||
@@ -1,18 +1,37 @@
|
|||||||
import { app, cnb } from '../app';
|
import { app, cnb } from '../app';
|
||||||
import dayjs from 'dayjs';
|
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仓库链接')
|
||||||
|
}).describe('仓库信息');
|
||||||
|
export type Repo = z.infer<typeof repoSchema>;
|
||||||
|
|
||||||
app.route({
|
app.route({
|
||||||
path: 'cnb',
|
path: 'cnb',
|
||||||
key: 'list-today',
|
key: 'list-today',
|
||||||
description: '获取今日更新的仓库列表',
|
description: '获取今日更新的仓库列表',
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const res = await cnb.repo.getRepoList({ page_size: 100 });
|
const res = await cnb.repo.getRepoList({ status: 'active', page_size: 100 });
|
||||||
const today = dayjs().format('YYYY-MM-DD');
|
|
||||||
const list = res.data || [];
|
const list = res.data || [];
|
||||||
|
const twelveHoursAgo = dayjs().subtract(12, 'hour');
|
||||||
const todayList = list.filter(item => {
|
const todayList = list.filter(item => {
|
||||||
const updatedAt = dayjs(item.updated_at).format('YYYY-MM-DD');
|
const updatedAt = dayjs(item.updated_at);
|
||||||
return updatedAt === today;
|
return updatedAt.isAfter(twelveHoursAgo);
|
||||||
});
|
});
|
||||||
|
const repositories = 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) : [],
|
||||||
|
})).filter(item => item.topics.includes('gitea'));
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
list: todayList,
|
list: repositories
|
||||||
}
|
}
|
||||||
}).addTo(app)
|
}).addTo(app)
|
||||||
68
src/routes/sync.ts
Normal file
68
src/routes/sync.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { app, gitea, rootPath } 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',
|
||||||
|
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');
|
||||||
|
item.giteaUrl = `https://${giteaUsername}:${giteaPassword}@git.xiongxiao.me/${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)) {
|
||||||
|
execSync(`git clone ${repo.web_url} ${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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user