import path from 'node:path'; import fs from 'node:fs'; import { Config, SyncList, SyncConfigType, SyncConfig } from './type.ts'; import { fileIsExist } from '@/uitls/file.ts'; import { getHash } from '@/uitls/hash.ts'; import glob from 'fast-glob'; import { logger } from '@/module/logger.ts'; export type SyncOptions = { dir?: string; configFilename?: string; baseURL?: string; }; export class SyncBase { config: Config; #filename: string; #dir: string; baseURL: string; constructor(opts?: SyncOptions) { const filename = opts?.configFilename || 'kevisual.json'; const dir = opts?.dir || process.cwd(); this.#filename = filename; this.#dir = path.resolve(dir); this.baseURL = opts?.baseURL ?? ''; this.init(); } async init() { try { const dir = this.#dir; const filename = this.#filename; const filepath = path.join(dir, filename); if (!fileIsExist(filepath)) throw new Error('config file not found'); const config = JSON.parse(fs.readFileSync(filepath, 'utf-8')); const sync = config.sync || {}; const keys = Object.keys(sync); const newConfigSync: any = {}; for (let key of keys) { const keyPath = path.join(dir, key); const newKey = path.relative(dir, keyPath); newConfigSync[newKey] = sync[key]; } config.sync = newConfigSync; this.config = config; return config; } catch (err) { this.config = {} as Config; return {} as Config; } } async canDone(syncType: SyncConfigType, type?: SyncConfigType) { if (syncType === 'sync') return true; return syncType === type; } /** * * @param opts * @param opts.getFile 是否检测文件是否存在 * @returns */ async getSyncList(opts?: { getFile?: boolean }): Promise { const config = this.config!; let sync = config?.sync || {}; const syncDirectory = await this.getSyncDirectoryList(); sync = this.getMergeSync(sync, syncDirectory.sync); const syncKeys = Object.keys(sync); const baseURL = this.baseURL; const syncList = syncKeys.map((key) => { const value = sync[key]; const filepath = path.join(this.#dir, key); // 文件的路径 const checkAuth = (value: string = '', baseURL: string = '') => { if (value.startsWith(baseURL)) { return true; } return false; }; if (typeof value === 'string') { const auth = checkAuth(value, baseURL); const type = auth ? 'sync' : 'none'; return { key, type: type as any, filepath, url: value, auth, }; } const auth = checkAuth(value.url, baseURL); const type = auth ? 'sync' : 'none'; return { key, filepath, ...value, type: value?.type ?? type, auth: checkAuth(value.url, baseURL), }; }); if (opts?.getFile) { return this.getSyncListFile(syncList); } return syncList; } getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) { const syncFileSyncKeys = Object.keys(fileSync); const syncKeys = Object.keys(sync); const keys = [...syncKeys, ...syncFileSyncKeys]; const obj: Config['sync'] = {}; for (let key of keys) { const value = sync[key] ?? fileSync[key]; obj[key] = value; } return obj; } async getSyncDirectoryList() { const config = this.config; const syncDirectory = config?.syncDirectory || []; let obj: Record = {}; const keys: string[] = []; for (let item of syncDirectory) { const { registry, ignore = [], files = [], replace = {} } = item; const cwd = this.#dir; const glob_files = await glob(files, { ignore, onlyFiles: true, cwd, dot: true, absolute: true, }); const registyURL = registry || config.registry; if (!registyURL) { logger.error('请配置 registry', item); continue; } for (let file of glob_files) { const key = path.relative(cwd, file); const _registryURL = new URL(registyURL); const replaceKeys = Object.keys(replace); let newKey = key; for (let replaceKey of replaceKeys) { if (newKey.startsWith(replaceKey)) { newKey = key.replace(replaceKey, replace[replaceKey]); } } const pathname = path.join(_registryURL.pathname, newKey); _registryURL.pathname = pathname; keys.push(key); obj[key] = _registryURL.toString(); } } return { sync: obj, keys }; } /** * 获取文件列表,检测文件是否存在 * @param syncList * @returns */ async getSyncListFile(syncList: SyncList[]) { let syncListFile: SyncList[] = []; for (let item of syncList) { const { filepath, auth } = item; if (filepath && fileIsExist(filepath) && auth) { syncListFile.push({ ...item, exist: true, hash: getHash(filepath), }); } else { syncListFile.push({ ...item, exist: false }); } } return syncListFile; } getHash = getHash; async getDir(filepath: string, check = false) { const dir = path.dirname(filepath); if (check) { if (!fileIsExist(dir)) { fs.mkdirSync(dir, { recursive: true }); } } return dir; } async download() { // const syncList = await this.getSyncList(); // for (const item of syncList) { // } } async upload() { // need check permission } }