256 lines
9.9 KiB
TypeScript
256 lines
9.9 KiB
TypeScript
import { program as app, Command } from '@/program.ts';
|
|
import { SyncBase } from './modules/base.ts';
|
|
import { baseURL, query, storage } from '@/module/query.ts';
|
|
import { fetchLink, fetchAiList } from '@/module/download/install.ts';
|
|
import fs from 'node:fs';
|
|
import { upload } from '@/module/download/upload.ts';
|
|
import { logger, printClickableLink } from '@/module/logger.ts';
|
|
import { chalk } from '@/module/chalk.ts';
|
|
import path from 'node:path';
|
|
import { fileIsExist } from '@/uitls/file.ts';
|
|
|
|
const command = new Command('sync')
|
|
.option('-d --dir <dir>')
|
|
.description('同步项目')
|
|
.action(() => {
|
|
console.log('同步项目');
|
|
});
|
|
|
|
const syncUpload = new Command('upload')
|
|
.option('-d --dir <dir>', '配置目录')
|
|
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
|
.option('-f --file <file>', '操作的对应的文件名')
|
|
.description('上传项目, 上传需要和registry的地址同步。')
|
|
.action(async (opts) => {
|
|
console.log('上传项目');
|
|
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
|
const syncList = await sync.getSyncList({ getFile: true });
|
|
logger.debug(syncList);
|
|
const nodonwArr: (typeof syncList)[number][] = [];
|
|
const token = storage.getItem('token');
|
|
const meta: Record<string, string> = {
|
|
...sync.config.metadata,
|
|
};
|
|
const filepath = sync.getRelativePath(opts.file);
|
|
const newInfos = [];
|
|
|
|
for (const item of syncList) {
|
|
if (!item.auth || !item.exist) {
|
|
nodonwArr.push(item);
|
|
continue;
|
|
}
|
|
if (!sync.canDone(item.type, 'upload')) {
|
|
nodonwArr.push(item);
|
|
continue;
|
|
}
|
|
if (filepath && item.filepath !== filepath.absolute) {
|
|
continue;
|
|
}
|
|
const res = await upload({
|
|
token,
|
|
file: fs.readFileSync(item.filepath),
|
|
url: item.url,
|
|
needHash: true,
|
|
hash: item.hash,
|
|
meta: item.metadata ?? meta,
|
|
});
|
|
if (res.code === 200) {
|
|
if (res.data?.isNew) {
|
|
newInfos.push(['上传成功', item.key, chalk.green(item.url), chalk.green('文件上传')]);
|
|
} else if (res.data?.isNewMeta) {
|
|
newInfos.push(['上传成功', item.key, chalk.green(item.url), chalk.green('元数据更新')]);
|
|
} else {
|
|
// 文件未更新
|
|
logger.debug('上传成功', item.key, chalk.green(item.url), chalk.blue('文件未更新'));
|
|
}
|
|
}
|
|
logger.debug(res);
|
|
}
|
|
if (newInfos.length) {
|
|
logger.info('上传成功的文件\n');
|
|
newInfos.forEach((item) => {
|
|
logger.info(...item);
|
|
});
|
|
}
|
|
if (nodonwArr.length && !filepath) {
|
|
logger.warn('以下文件未上传\n', nodonwArr.map((item) => item.key).join(','));
|
|
}
|
|
});
|
|
const syncDownload = new Command('download')
|
|
.option('-d --dir <dir>', '配置目录')
|
|
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
|
.option('-f --file <file>', '操作的对应的文件名')
|
|
.description('下载项目')
|
|
.action(async (opts) => {
|
|
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
|
const syncList = await sync.getSyncList();
|
|
logger.debug(syncList);
|
|
const nodonwArr: (typeof syncList)[number][] = [];
|
|
const filepath = sync.getRelativePath(opts.file);
|
|
for (const item of syncList) {
|
|
if (!sync.canDone(item.type, 'download')) {
|
|
nodonwArr.push(item);
|
|
continue;
|
|
}
|
|
if (filepath && item.filepath !== filepath.absolute) {
|
|
continue;
|
|
}
|
|
const hash = sync.getHash(item.filepath);
|
|
const { content, status } = await fetchLink(item.url, { setToken: item.auth, returnContent: true, hash });
|
|
if (status === 200) {
|
|
await sync.getDir(item.filepath, true);
|
|
fs.writeFileSync(item.filepath, content);
|
|
logger.info('下载成功', item.key, chalk.green(item.url));
|
|
} else if (status === 304) {
|
|
logger.info('文件未修改', item.key, chalk.green(item.url));
|
|
} else {
|
|
logger.error('下载失败', item.key, chalk.red(item.url));
|
|
}
|
|
}
|
|
if (nodonwArr.length && !filepath) {
|
|
logger.warn('以下文件未下载', nodonwArr.map((item) => item.key).join(','));
|
|
}
|
|
});
|
|
const syncList = new Command('list')
|
|
.option('-d --dir <dir>', '配置目录')
|
|
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
|
.option('-a --all', '显示所有的文件')
|
|
.description('列出同步列表')
|
|
.action(async (opts) => {
|
|
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
|
const syncList = await sync.getSyncList();
|
|
logger.debug(syncList);
|
|
logger.info('同步列表\n');
|
|
syncList.forEach((item) => {
|
|
if (opts.all) {
|
|
logger.info(item);
|
|
} else {
|
|
logger.info(chalk.green(printClickableLink({ url: item.url, text: item.key, print: false })), chalk.gray(item.type));
|
|
}
|
|
});
|
|
});
|
|
const syncCreateList = new Command('create')
|
|
.option('-d --dir <dir>', '配置目录')
|
|
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
|
.option('-o --output <output>', '输出文件')
|
|
.description('创建文件, 获取远程的目录列表,然后创建新的配置文件')
|
|
.action(async (opts) => {
|
|
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
|
const syncList = await sync.getSyncList();
|
|
logger.debug(syncList);
|
|
logger.info('同步列表\n');
|
|
let newSync = {};
|
|
syncList.forEach((item) => {
|
|
logger.info(chalk.blue(item.key), chalk.gray(item.type), chalk.green(item.url));
|
|
newSync[item.key] = item.url;
|
|
});
|
|
const newJson = { ...sync.config };
|
|
newJson.sync = newSync;
|
|
const filepath = sync.getRelativePath(opts.output);
|
|
if (filepath) {
|
|
logger.debug('输出文件', filepath);
|
|
fs.writeFileSync(filepath.absolute, JSON.stringify(newJson, null, 2));
|
|
} else {
|
|
logger.info('输出内容\n');
|
|
logger.info(newJson);
|
|
}
|
|
});
|
|
|
|
const clone = new Command('clone')
|
|
.option('-d --dir <dir>', '配置目录')
|
|
.option('-c --config <config>', '配置文件的名字', 'kevisual.json')
|
|
.option('-i --link <link>', '克隆链接, 比 kevisual.json 优先级更高')
|
|
.description('检查目录')
|
|
.action(async (opts) => {
|
|
const link = opts.link || '';
|
|
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
|
|
if (link) {
|
|
const res = await query.fetchText(link);
|
|
if (res.code === 200) {
|
|
fs.writeFileSync(sync.configPath, JSON.stringify(res.data, null, 2));
|
|
}
|
|
sync.init()
|
|
}
|
|
const syncList = await sync.getSyncList();
|
|
logger.debug(syncList);
|
|
logger.info('检查目录\n');
|
|
const checkList = await sync.getCheckList();
|
|
logger.info('检查列表', checkList);
|
|
for (const item of checkList) {
|
|
if (!item.auth) {
|
|
continue;
|
|
}
|
|
if (!item.enabled) {
|
|
logger.info('提示:', item.key, chalk.yellow('未启用'));
|
|
continue;
|
|
}
|
|
const res = await fetchAiList(item.url, { recursive: true });
|
|
if (res.code === 200) {
|
|
const data = res?.data || [];
|
|
let matchObjectList = data.filter((dataItem) => {
|
|
// 把 pathname 和 path 合并成一个路径
|
|
dataItem.pathname = path.join(item.key || '', dataItem.path);
|
|
return dataItem;
|
|
});
|
|
matchObjectList = sync.getMatchList({ ignore: item.ignore, matchObjectList }).matchObjectList;
|
|
const matchList = matchObjectList
|
|
.map((item2) => {
|
|
const rp = sync.getRelativePath(item2.pathname);
|
|
|
|
if (!rp) return false;
|
|
if (rp.absolute.endsWith('gitignore.txt')) {
|
|
// 修改为 .gitignore
|
|
const newPath = rp.absolute.replace('gitignore.txt', '.gitignore');
|
|
rp.absolute = newPath;
|
|
rp.relative = path.relative(sync.dir, newPath);
|
|
} else if (rp.absolute.endsWith('.dot')) {
|
|
const filename = path.basename(rp.absolute, '.dot');
|
|
const newPath = path.join(path.dirname(rp.absolute), `.${filename}`);
|
|
rp.absolute = newPath;
|
|
rp.relative = path.relative(sync.dir, newPath);
|
|
}
|
|
return { ...item2, relative: rp.relative, absolute: rp.absolute };
|
|
})
|
|
.filter((i) => i);
|
|
for (const matchItem of matchList) {
|
|
if (!matchItem) continue;
|
|
let needDownload = true;
|
|
let hash = '';
|
|
await sync.getDir(matchItem.absolute, true);
|
|
logger.debug('文件路径', matchItem.absolute);
|
|
if (fileIsExist(matchItem.absolute)) {
|
|
hash = sync.getHash(matchItem.absolute);
|
|
if (hash !== matchItem.etag) {
|
|
logger.error('文件不一致', matchItem.pathname, chalk.red(matchItem.url), chalk.red('文件不一致'));
|
|
} else {
|
|
needDownload = false;
|
|
logger.info('文件一致', matchItem.pathname, chalk.green(matchItem.url), chalk.green('文件一致'));
|
|
}
|
|
}
|
|
if (needDownload) {
|
|
const { content, status } = await fetchLink(matchItem.url, { setToken: item.auth, returnContent: true, hash });
|
|
if (status === 200) {
|
|
fs.writeFileSync(matchItem.absolute, content);
|
|
logger.info('下载成功', matchItem.pathname, chalk.green(matchItem.url));
|
|
} else if (status === 304) {
|
|
logger.info('文件未修改', matchItem.pathname, chalk.green(matchItem.url));
|
|
} else {
|
|
logger.error('下载失败', matchItem.pathname, chalk.red(matchItem.url));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
logger.error('检查失败', item.url, res.code);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
command.addCommand(syncUpload);
|
|
command.addCommand(syncDownload);
|
|
command.addCommand(syncList);
|
|
command.addCommand(syncCreateList);
|
|
command.addCommand(clone);
|
|
|
|
app.addCommand(command);
|