291 lines
8.0 KiB
TypeScript
291 lines
8.0 KiB
TypeScript
import path from 'path';
|
|
import fs from 'fs';
|
|
import { storage, baseURL } from '../query.ts';
|
|
import { chalk } from '../chalk.ts';
|
|
import { Result } from '@kevisual/query';
|
|
import { fileIsExist } from '@/uitls/file.ts';
|
|
import { glob } from 'fast-glob';
|
|
import inquirer from 'inquirer';
|
|
|
|
type DownloadTask = {
|
|
downloadPath: string;
|
|
downloadUrl: string;
|
|
user: string;
|
|
key: string;
|
|
version: string;
|
|
};
|
|
export type Package = {
|
|
id: string;
|
|
name?: string;
|
|
version?: string;
|
|
description?: string;
|
|
title?: string;
|
|
user?: string;
|
|
key?: string;
|
|
[key: string]: any;
|
|
};
|
|
type Options = {
|
|
check?: boolean;
|
|
returnContent?: boolean;
|
|
setToken?: boolean;
|
|
hash?: string;
|
|
[key: string]: any;
|
|
};
|
|
export const fetchLink = async (url: string = '', opts?: Options) => {
|
|
const token = process.env.KEVISUAL_TOKEN || storage.getItem('token');
|
|
const fetchURL = new URL(url);
|
|
const check = opts?.check ?? false;
|
|
const isKevisual = !!url.includes('kevisual');
|
|
const setToken = opts?.setToken ?? isKevisual;
|
|
if (check) {
|
|
if (!url.startsWith(baseURL)) {
|
|
throw new Error('url must start with ' + baseURL);
|
|
}
|
|
}
|
|
if (token && setToken) {
|
|
fetchURL.searchParams.set('token', token);
|
|
}
|
|
if (opts?.hash) {
|
|
fetchURL.searchParams.set('hash', opts.hash);
|
|
}
|
|
fetchURL.searchParams.set('download', 'true');
|
|
|
|
const res = await fetch(fetchURL.toString());
|
|
const blob = await res.blob();
|
|
const type = blob.type;
|
|
let content: Buffer | undefined;
|
|
if (opts?.returnContent) {
|
|
content = Buffer.from(await blob.arrayBuffer());
|
|
}
|
|
const pathname = fetchURL.pathname;
|
|
const filename = pathname.split('/').pop();
|
|
return {
|
|
status: res.status,
|
|
filename,
|
|
blob,
|
|
type,
|
|
content,
|
|
};
|
|
};
|
|
const checkDelete = async (opts?: { force?: boolean; dir?: string; yes?: boolean }) => {
|
|
const { force = false, dir = '', yes = false } = opts || {};
|
|
if (force) {
|
|
try {
|
|
if (fileIsExist(dir)) {
|
|
const files = await glob(`${dir}/**/*`, { onlyFiles: true });
|
|
const answers = await inquirer.prompt([
|
|
{
|
|
type: 'confirm',
|
|
name: 'confirm',
|
|
message: `是否你需要删除 【${opts?.dir}】 目录下的文件. [${files.length}] 个?`,
|
|
when: () => files.length > 0 && !yes, // 当 username 为空时,提示用户输入
|
|
},
|
|
]);
|
|
if (answers?.confirm || yes) {
|
|
fs.rmSync(dir, { recursive: true });
|
|
console.log(chalk.green('删除成功', dir));
|
|
} else {
|
|
console.log(chalk.red('取消删除', dir));
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
} finally {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
};
|
|
export const rewritePkg = (packagePath: string, pkg: Package) => {
|
|
const readJsonFile = (filePath: string) => {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
} catch (error) {
|
|
return {};
|
|
}
|
|
};
|
|
try {
|
|
const dirname = path.dirname(packagePath);
|
|
if (!fs.existsSync(dirname)) {
|
|
fs.mkdirSync(dirname, { recursive: true });
|
|
}
|
|
const json = readJsonFile(packagePath);
|
|
json.id = pkg?.id;
|
|
json.appInfo = pkg;
|
|
|
|
fs.writeFileSync(packagePath, JSON.stringify(json, null, 2));
|
|
} catch (error) {
|
|
fs.writeFileSync(packagePath, JSON.stringify({ appInfo: pkg, id: pkg?.id }, null, 2));
|
|
}
|
|
return pkg;
|
|
};
|
|
type InstallAppOpts = {
|
|
appDir?: string;
|
|
kevisualUrl?: string;
|
|
/**
|
|
* 是否强制覆盖, 下载前删除已有的
|
|
*/
|
|
force?: boolean;
|
|
yes?: boolean;
|
|
};
|
|
export const installApp = async (app: Package, opts: InstallAppOpts = {}) => {
|
|
// const _app = demoData;
|
|
const { appDir = '', kevisualUrl = 'https://kevisual.cn', } = opts;
|
|
const _app = app;
|
|
try {
|
|
let files = _app.data.files || [];
|
|
const version = _app.version;
|
|
const user = _app.user;
|
|
const key = _app.key;
|
|
const downloadDirPath = path.join(appDir, user, key);
|
|
await checkDelete({ force: opts?.force, yes: opts?.yes, dir: downloadDirPath });
|
|
const packagePath = path.join(appDir, `${user}/${key}/package.json`);
|
|
const downFiles = files
|
|
.filter((file: any) => file?.path)
|
|
.map((file: any) => {
|
|
const name = file?.name || '';
|
|
const noVersionPath = file.path.replace(`/${version}`, '');
|
|
let downloadPath = noVersionPath;
|
|
let downloadUrl = '';
|
|
if (file.path.startsWith('http')) {
|
|
downloadUrl = file.path;
|
|
} else {
|
|
downloadUrl = `${kevisualUrl}/${noVersionPath}`;
|
|
}
|
|
return {
|
|
...file,
|
|
downloadPath: path.join(appDir, downloadPath),
|
|
downloadUrl: downloadUrl,
|
|
};
|
|
});
|
|
const downloadTasks: DownloadTask[] = downFiles as any;
|
|
console.log('downloadTasks', downloadTasks);
|
|
|
|
for (const file of downloadTasks) {
|
|
const downloadPath = file.downloadPath;
|
|
const downloadUrl = file.downloadUrl;
|
|
const dir = path.dirname(downloadPath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
console.log('downloadUrl', downloadUrl);
|
|
const { blob, type } = await fetchLink(downloadUrl);
|
|
if (type.includes('text/html')) {
|
|
const html = await blob.text();
|
|
if (html === 'fetchRes is error') {
|
|
console.log(chalk.red('fetchRes is error'), '下载失败', downloadUrl);
|
|
throw new Error('fetchRes is error');
|
|
}
|
|
}
|
|
fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer()));
|
|
}
|
|
let indexHtml = files.find((file: any) => file.name === 'index.html');
|
|
// if (!indexHtml) {
|
|
// files.push({
|
|
// name: 'index.html',
|
|
// path: `${user}/${key}/index.html`,
|
|
// });
|
|
// fs.writeFileSync(path.join(appDir, `${user}/${key}/index.html`), JSON.stringify(app, null, 2));
|
|
// }
|
|
_app.data.files = files;
|
|
rewritePkg(packagePath, _app);
|
|
return {
|
|
code: 200,
|
|
data: _app,
|
|
message: 'Install app success',
|
|
};
|
|
} catch (error) {
|
|
console.error(error);
|
|
return {
|
|
code: 500,
|
|
message: 'Install app failed',
|
|
};
|
|
}
|
|
};
|
|
/**
|
|
* 检查是否为空,如果为空则删除
|
|
* @param appDir
|
|
*/
|
|
export const checkAppDir = (appDir: string) => {
|
|
try {
|
|
const files = fs.readdirSync(appDir);
|
|
if (files.length === 0) {
|
|
fs.rmSync(appDir, { recursive: true });
|
|
}
|
|
} catch (error) { }
|
|
};
|
|
export const checkFileExists = (path: string) => {
|
|
try {
|
|
fs.accessSync(path);
|
|
return true;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
};
|
|
type UninstallAppOpts = {
|
|
appDir?: string;
|
|
type?: 'app' | 'web';
|
|
};
|
|
export const uninstallApp = async (app: Partial<Package>, opts: UninstallAppOpts = {}) => {
|
|
const { appDir = '' } = opts;
|
|
try {
|
|
const { user, key } = app;
|
|
const keyDir = path.join(appDir, user, key);
|
|
const parentDir = path.join(appDir, user);
|
|
if (!checkFileExists(appDir) || !checkFileExists(keyDir)) {
|
|
return {
|
|
code: 200,
|
|
message: 'uninstall app success',
|
|
};
|
|
}
|
|
try {
|
|
// 删除appDir和文件
|
|
fs.rmSync(keyDir, { recursive: true });
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
checkAppDir(parentDir);
|
|
return {
|
|
code: 200,
|
|
message: 'Uninstall app success',
|
|
};
|
|
} catch (error) {
|
|
console.error(error);
|
|
return {
|
|
code: 500,
|
|
message: 'Uninstall app failed',
|
|
};
|
|
}
|
|
};
|
|
|
|
export type AiList = {
|
|
name?: string;
|
|
lastModified?: string;
|
|
etag?: string;
|
|
size?: number;
|
|
path: string;
|
|
pathname?: string;
|
|
url?: string;
|
|
};
|
|
export const fetchAiList = async (url: string, opts?: { recursive: boolean }): Promise<Result<AiList[]>> => {
|
|
const token = process.env.KEVISUAL_TOKEN || storage.getItem('token');
|
|
const _url = new URL(url);
|
|
const dir = _url.searchParams.get('dir');
|
|
if (!dir) {
|
|
_url.searchParams.set('dir', 'true');
|
|
}
|
|
if (opts?.recursive) {
|
|
_url.searchParams.set('recursive', 'true');
|
|
}
|
|
if (!_url.pathname.endsWith('/')) {
|
|
_url.pathname += '/';
|
|
}
|
|
const res = await fetch(_url.toString(), {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: 'Bearer ' + token,
|
|
},
|
|
});
|
|
const data = await res.json();
|
|
return data;
|
|
};
|