287 lines
8.6 KiB
TypeScript
287 lines
8.6 KiB
TypeScript
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 { isMatch } from 'micromatch';
|
|
import { logger } from '@/module/logger.ts';
|
|
import { normalizeScriptPath } from "@/uitls/file.ts";
|
|
export type SyncOptions = {
|
|
dir?: string;
|
|
configFilename?: string;
|
|
baseURL?: string;
|
|
};
|
|
const checkAuth = (value: string = '', baseURL: string = '') => {
|
|
if (value.startsWith(baseURL)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const DEFAULT_IGNORE = ['node_modules/**', '.git/**', '.next/**', '.astro/**', '.pack-dist/**'];
|
|
export class SyncBase {
|
|
config: Config;
|
|
#filename: string;
|
|
#dir: string;
|
|
baseURL: string;
|
|
defaultIgnore: string[] = DEFAULT_IGNORE;
|
|
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();
|
|
}
|
|
get dir() {
|
|
return this.#dir;
|
|
}
|
|
get configFilename() {
|
|
return this.#filename;
|
|
}
|
|
get configPath() {
|
|
return path.join(this.#dir, this.#filename);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
getRelativePath(filename?: string) {
|
|
if (!filename) return false;
|
|
const dir = this.#dir;
|
|
const file = path.join(dir, filename);
|
|
const realFilename = path.basename(filename);
|
|
return { relative: path.relative(dir, file), absolute: file, filename: realFilename };
|
|
}
|
|
/**
|
|
*
|
|
* @param syncType
|
|
* @param type
|
|
* @returns
|
|
*/
|
|
async canDone(syncType: SyncConfigType, type?: SyncConfigType) {
|
|
if (syncType === 'sync') return true;
|
|
return syncType === type;
|
|
}
|
|
getIngore(ignore: string[] = []) {
|
|
const defaultIgnore = [...this.defaultIgnore, ...ignore];
|
|
const set = new Set(defaultIgnore);
|
|
return new Array(...set);
|
|
}
|
|
getMatchList(opts?: { matchList?: string[]; ignore: string[]; matchObjectList?: { path: string;[key: string]: any }[] }) {
|
|
const { matchList = [], ignore = [], matchObjectList = [] } = opts || {};
|
|
const _ignore = this.getIngore(ignore);
|
|
const _matchList = matchList.filter((file) => !isMatch(file, _ignore));
|
|
const _matchObjectList = matchObjectList.filter((item) => !isMatch(item.path, _ignore));
|
|
return { matchList: _matchList, matchObjectList: _matchObjectList };
|
|
}
|
|
/**
|
|
*
|
|
* @param opts
|
|
* @param opts.getFile 是否检测文件是否存在
|
|
* @returns
|
|
*/
|
|
async getSyncList(opts?: { getFile?: boolean }): Promise<SyncList[]> {
|
|
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); // 文件的路径
|
|
if (filepath.includes('node_modules') || filepath.includes('.git')) {
|
|
return null;
|
|
}
|
|
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),
|
|
};
|
|
}).filter((item) => item);
|
|
let resultSyncList: SyncList[] = []
|
|
if (opts?.getFile) {
|
|
resultSyncList = await this.getSyncListFile(syncList);
|
|
} else {
|
|
resultSyncList = syncList;
|
|
}
|
|
return resultSyncList;
|
|
}
|
|
async getCheckList() {
|
|
const checkDir = this.config?.clone || {};
|
|
const dirKeys = Object.keys(checkDir);
|
|
const registry = this.config?.registry || '';
|
|
const files = dirKeys.map((key) => {
|
|
return { key, ...this.getRelativePath(key) };
|
|
});
|
|
return files
|
|
.map((item) => {
|
|
if (!item) return;
|
|
let url = checkDir[item.key]?.url || registry;
|
|
let auth = checkAuth(url, this.baseURL);
|
|
return {
|
|
key: item.key,
|
|
...checkDir[item.key],
|
|
url: url,
|
|
filepath: item?.absolute,
|
|
auth,
|
|
};
|
|
})
|
|
.filter((item) => item);
|
|
}
|
|
/**
|
|
* sync 是已有的,优先级高于 fileSync
|
|
*
|
|
* @param sync
|
|
* @param fileSync
|
|
* @returns
|
|
*/
|
|
getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) {
|
|
const syncFileSyncKeys = Object.keys(fileSync);
|
|
const syncKeys = Object.keys(sync);
|
|
const config = this.config!;
|
|
const registry = config?.registry;
|
|
const keys = [...syncKeys, ...syncFileSyncKeys];
|
|
const obj: Config['sync'] = {};
|
|
const wrapperRegistry = (value: SyncConfig | string) => {
|
|
if (typeof value === 'object') {
|
|
const url = value.url;
|
|
if (registry && !url.startsWith('http')) {
|
|
return {
|
|
...value,
|
|
url: registry.replace(/\/+$/g, '') + '/' + url.replace(/^\/+/g, ''),
|
|
};
|
|
}
|
|
return value;
|
|
}
|
|
const url = value;
|
|
if (registry && !url.startsWith('http')) {
|
|
return registry.replace(/\/+$/g, '') + '/' + url.replace(/^\/+/g, '');
|
|
}
|
|
return url;
|
|
}
|
|
for (let key of keys) {
|
|
const value = sync[key] ?? fileSync[key];
|
|
obj[key] = wrapperRegistry(value);
|
|
}
|
|
return obj;
|
|
}
|
|
async getSyncDirectoryList() {
|
|
const config = this.config;
|
|
const syncDirectory = config?.syncd || [];
|
|
let obj: Record<string, any> = {};
|
|
const keys: string[] = [];
|
|
for (let item of syncDirectory) {
|
|
const { registry, ignore = [], files = [], replace = {}, metadata } = item;
|
|
const cwd = this.#dir;
|
|
const glob_files = await glob(files, {
|
|
ignore: this.getIngore(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) {
|
|
const _replaceKey = normalizeScriptPath(replaceKey);
|
|
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] = { url: _registryURL.toString() };
|
|
if (metadata) {
|
|
obj[key] = { ...obj[key], metadata };
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|