import { oss } from '@/app.ts'; import { fileIsExist } from '@kevisual/use-config'; import { spawn, spawnSync } from 'child_process'; import { getMinioList, MinioFile } from '@/routes/file/index.ts'; import fs from 'fs'; import path from 'path'; import { appsPath } from '../lib/index.ts'; import { installAppFromKey } from './manager.ts'; import { Readable } from 'stream'; import { CustomError } from '@kevisual/router'; export type InstallAppOpts = { needInstallDeps?: boolean; // minio中 appKey?: string; version?: string; uid?: string; username?: string; }; /** * 检测路径是否存在 * @param opts * @returns */ export const appPathCheck = async (opts: InstallAppOpts) => { const { appKey } = opts; const directory = path.join(appsPath, appKey); if (fileIsExist(directory)) { return true; } return false; }; export const installApp = async (opts: InstallAppOpts) => { const { needInstallDeps, appKey, version, uid, username } = opts; const userAppKey = `${username}/${appKey}`; const prefix = `data/${uid}/${appKey}/${version}`; const pkgPrefix = prefix + '/package.json'; const stat = await oss.statObject(pkgPrefix); if (!stat) { throw new CustomError({ code: 400, message: 'App not found' }); } const fileList = await getMinioList({ prefix, recursive: true, }); for (const file of fileList) { const { name } = file as MinioFile; const outputPath = path.join(appsPath, userAppKey, name.replace(prefix, '')); const dir = path.dirname(outputPath); if (!fileIsExist(dir)) { fs.mkdirSync(dir, { recursive: true }); } const fileStream = (await oss.getObject(`${name}`)).Body as Readable; const writeStream = fs.createWriteStream(outputPath); fileStream.pipe(writeStream); await new Promise((resolve, reject) => { writeStream.on('finish', () => resolve(true)); writeStream.on('error', reject); }); } const directory = path.join(appsPath, userAppKey); if (!fileIsExist(directory)) { fs.mkdirSync(directory, { recursive: true }); } if (needInstallDeps) { try { installDeps({ appPath: directory, isProduction: true, sync: true }); } catch (e) { console.log('installDeps error, [need manual install deps]', e); } } return installAppFromKey(userAppKey); }; export const checkPnpm = () => { try { spawnSync('pnpm', ['--version']); return true; } catch (e) { return false; } }; type InstallDepsOptions = { appPath: string; isProduction?: boolean; sync?: boolean; }; export const installDeps = async (opts: InstallDepsOptions) => { const { appPath } = opts; const isProduction = opts.isProduction ?? true; const isPnpm = checkPnpm(); const params = ['i']; if (isProduction && isPnpm) { params.push('--production'); } else { params.push('--omit=dev'); } console.log('installDeps', appPath, params); const syncSpawn = opts.sync ? spawnSync : spawn; if (isPnpm) { syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: process.env }); } else { syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env }); } };