diff --git a/package.json b/package.json index e1c4eab..bf33b2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/cli", - "version": "0.0.64", + "version": "0.0.65", "description": "envision 命令行工具", "type": "module", "basename": "/root/cli", @@ -29,6 +29,7 @@ "dev": "bun src/run.ts ", "dev:tsx": "tsx src/run.ts ", "build": "rimraf dist && bun run bun.config.mjs", + "deploy": "ev pack -u -p -m no", "pub:me": "npm publish --registry https://npm.xiongxiao.me --tag beta", "postbuild": "cd assistant && pnpm build ", "dts": "dts-bundle-generator --external-inlines=@types/jsonwebtoken src/index.ts -o dist/index.d.ts " @@ -39,8 +40,10 @@ ], "author": "abearxiong", "dependencies": { + "@kevisual/context": "^0.0.4", "micromatch": "^4.0.8", - "pm2": "^6.0.14" + "pm2": "^6.0.14", + "semver": "^7.7.3" }, "devDependencies": { "@kevisual/dts": "^0.0.3", @@ -53,6 +56,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/micromatch": "^4.0.10", "@types/node": "^24.10.1", + "@types/semver": "^7.7.1", "chalk": "^5.6.2", "commander": "^14.0.2", "crypto-js": "^4.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e84399..3b88193 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@kevisual/context': + specifier: ^0.0.4 + version: 0.0.4 micromatch: specifier: ^4.0.8 version: 4.0.8 pm2: specifier: ^6.0.14 version: 6.0.14(supports-color@10.2.2) + semver: + specifier: ^7.7.3 + version: 7.7.3 devDependencies: '@kevisual/dts': specifier: ^0.0.3 @@ -45,6 +51,9 @@ importers: '@types/node': specifier: ^24.10.1 version: 24.10.1 + '@types/semver': + specifier: ^7.7.1 + version: 7.7.1 chalk: specifier: ^5.6.2 version: 5.6.2 @@ -336,8 +345,8 @@ packages: '@kevisual/cache@0.0.3': resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==} - '@kevisual/cache@0.0.3': - resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==} + '@kevisual/context@0.0.4': + resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==} '@kevisual/dts@0.0.3': resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==} @@ -357,11 +366,6 @@ packages: peerDependencies: '@kevisual/query': ^0 - '@kevisual/query-login@0.0.7': - resolution: {integrity: sha512-oOyPIz337cdTt7WncFj7Wr7nxUHh0pBB6KSAJlas+lQiWBPwQEZhpEd7YciydCRlMc9IJMcZRV1Bw3qgy8FFqQ==} - peerDependencies: - '@kevisual/query': ^0 - '@kevisual/query@0.0.29': resolution: {integrity: sha512-rQZk0J073UuC1QGzuyq+pb4Y0hu8/Qx/xYHs9NbsmslM+RuMnd1zpXmvhXNj7Kn1MdYTH90ng2MlFLBkkQFaIg==} @@ -560,9 +564,6 @@ packages: '@types/bun@1.3.3': resolution: {integrity: sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==} - '@types/bun@1.3.3': - resolution: {integrity: sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==} - '@types/crypto-js@4.2.2': resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} @@ -593,6 +594,9 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} @@ -673,9 +677,6 @@ packages: bun-types@1.3.3: resolution: {integrity: sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==} - bun-types@1.3.3: - resolution: {integrity: sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1442,13 +1443,13 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -1846,9 +1847,7 @@ snapshots: dependencies: idb-keyval: 6.2.1 - '@kevisual/cache@0.0.3': - dependencies: - idb-keyval: 6.2.1 + '@kevisual/context@0.0.4': {} '@kevisual/dts@0.0.3(typescript@5.8.2)': dependencies: @@ -2085,10 +2084,6 @@ snapshots: '@types/braces@3.0.5': {} - '@types/bun@1.3.3': - dependencies: - bun-types: 1.3.3 - '@types/bun@1.3.3': dependencies: bun-types: 1.3.3 @@ -2125,6 +2120,8 @@ snapshots: '@types/resolve@1.20.2': {} + '@types/semver@7.7.1': {} + '@types/send@1.2.1': dependencies: '@types/node': 24.10.1 @@ -2186,10 +2183,6 @@ snapshots: buffer-from@1.1.2: {} - bun-types@1.3.3: - dependencies: - '@types/node': 24.10.1 - bun-types@1.3.3: dependencies: '@types/node': 24.10.1 @@ -2621,7 +2614,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.6.3 + semver: 7.7.3 jwa@1.4.1: dependencies: @@ -3009,10 +3002,10 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.6.3: {} - semver@7.7.2: {} + semver@7.7.3: {} + send@1.2.0(supports-color@10.2.2): dependencies: debug: 4.4.0(supports-color@10.2.2) diff --git a/src/command/deploy.ts b/src/command/deploy.ts index 1342665..620a242 100644 --- a/src/command/deploy.ts +++ b/src/command/deploy.ts @@ -4,10 +4,8 @@ import path from 'path'; import fs from 'fs'; import FormData from 'form-data'; import { getBaseURL, query, storage } from '@/module/query.ts'; -import { getConfig } from '@/module/index.ts'; import inquirer from 'inquirer'; import chalk from 'chalk'; -import { installDeps } from '@/uitls/npm.ts'; import { upload } from '@/module/download/upload.ts'; import { getHash } from '@/uitls/hash.ts'; import { queryAppVersion } from '@/query/app-manager/query-app.ts'; @@ -96,7 +94,7 @@ const command = new Command('deploy') dot, absolute: true, }); - console.log('files', files); + // console.log('files', files); // 添加一个工具函数来统一处理路径 const normalizeFilePath = (filePath: string) => { return filePath.split(path.sep).join('/'); @@ -224,7 +222,6 @@ const uploadFiles = async (files: string[], directory: string, opts: UploadFileO console.error('check failed', res); return res; } - console.log('res', res); let needUpload = false; for (const file of files) { const filePath = path.join(directory, file); diff --git a/src/command/login.ts b/src/command/login.ts index 5dd5eea..28ecf24 100644 --- a/src/command/login.ts +++ b/src/command/login.ts @@ -62,6 +62,8 @@ const loginCommand = new Command('login') const res = await queryLogin.login({ username, password, + }).catch((err) => { + return { code: 500, message: err.message || '' }; }); if (res.code === 200) { console.log('welcome', username); diff --git a/src/command/publish.ts b/src/command/publish.ts index 0ab91de..b8a8215 100644 --- a/src/command/publish.ts +++ b/src/command/publish.ts @@ -36,8 +36,9 @@ async function collectFileInfo(filePath: string, baseDir = '.'): Promise * @param files 文件列表, 或者文件夹列表 * @param cwd 当前工作目录 * @param packDist 打包目录 pack-dist + * @param mergeDist 是否合并 dist 目录到 pack-dist 中 */ -export const copyFilesToPackDist = async (files: string[], cwd: string, packDist = 'pack-dist') => { +export const copyFilesToPackDist = async (files: string[], cwd: string, packDist = 'pack-dist', mergeDist = true) => { const packDistPath = path.join(cwd, packDist); if (!fileIsExist(packDistPath)) { fs.mkdirSync(packDistPath, { recursive: true }); @@ -47,10 +48,12 @@ export const copyFilesToPackDist = async (files: string[], cwd: string, packDist files.forEach((file) => { const stat = fs.statSync(path.join(cwd, file)); let outputFile = file; - if (file.startsWith('dist/')) { - outputFile = file.replace(/^dist\//, ''); - } else if (file === 'dist') { - outputFile = ''; + if (mergeDist) { + if (file.startsWith('dist/')) { + outputFile = file.replace(/^dist\//, ''); + } else if (file === 'dist') { + outputFile = ''; + } } if (stat.isDirectory()) { fs.cpSync(path.join(cwd, file), path.join(packDistPath, outputFile), { recursive: true }); @@ -93,9 +96,11 @@ ${filesString} fs.writeFileSync(indexHtmlPath, indexHtmlContent); } }; -export const pack = async (opts: { packDist?: string }) => { + +export const pack = async (opts: { packDist?: string, mergeDist?: boolean }) => { const cwd = process.cwd(); const collection: Record = {}; + const mergeDist = opts.mergeDist !== false; const packageJsonPath = path.join(cwd, 'package.json'); if (!fileIsExist(packageJsonPath)) { console.error('package.json not found'); @@ -150,7 +155,7 @@ export const pack = async (opts: { packDist?: string }) => { console.log(`version: ${packageJson.version}`); console.log(`total files: ${allFiles.length}`); try { - copyFilesToPackDist(filesToInclude, cwd, opts.packDist); + copyFilesToPackDist(filesToInclude, cwd, opts.packDist, mergeDist); } catch (error) { console.error('Error creating tarball:', error); } @@ -173,17 +178,6 @@ export const getPackageInfo = async () => { } }; -/** - * 打包应用 - * @returns 打包结果 - */ -export const packLib = async ({ - packDist = 'pack-dist', -}: { - packDist?: string; -}): Promise<{ collection: Record; dir: string }> => { - return await pack({ packDist }); -}; const publishCommand = new Command('publish') .description('发布应用') .option('-k, --key ', '应用 key') @@ -199,21 +193,6 @@ const deployLoadFn = async (id: string, fileKey: string, force = false, install console.error(chalk.red('id is required')); return; } - // pkg: { - // name: 'mark', - // version: '0.0.2', - // description: '', - // main: 'dist/app.mjs', - // app: [Object], - // files: [Array], - // scripts: [Object], - // keywords: [Array], - // author: 'abearxiong ', - // license: 'MIT', - // type: 'module', - // devDependencies: [Object], - // dependencies: [Object] - // }, let appKey = ''; let version = ''; if (id && id.includes('/')) { @@ -253,10 +232,13 @@ const packCommand = new Command('pack') .option('-p, --publish', '打包并发布') .option('-u, --update', '发布后显示更新命令, show command for deploy to server') .option('-d, --packDist ', '打包到的目录') - .option('-y, --yes', '确定,直接打包', true) + .option('-m, --mergeDist ', '合并 dist 目录到 pack-dist 中', "true") + .option('-y, --yes ', '确定,直接打包', "true") .option('-c, --clean', '清理 package.json中的 devDependencies') .action(async (opts) => { const packDist = opts.packDist || 'pack-dist'; + const mergeDist = opts.mergeDist === "true"; + const yes = opts.yes === "true"; const packageInfo = await getPackageInfo(); if (!packageInfo) { console.error('Invalid package.json:'); @@ -297,8 +279,9 @@ const packCommand = new Command('pack') ]); appKey = answers.appKey || appKey; } - let value = await packLib({ + let value = await pack({ packDist, + mergeDist }); if (opts?.clean) { const newPackageJson = { ...packageInfo }; @@ -317,7 +300,7 @@ const packCommand = new Command('pack') if (opts.update) { deployCommand.push('-s'); } - if (opts.yes) { + if (yes) { deployCommand.push('-y', 'yes'); } console.log(chalk.blue('deploy doing: '), deployCommand.slice(2).join(' '), '\n'); diff --git a/src/command/update.ts b/src/command/update.ts index a11487a..648f3d6 100644 --- a/src/command/update.ts +++ b/src/command/update.ts @@ -1,13 +1,100 @@ import { program, Command } from '@/program.ts'; import { execSync } from 'node:child_process'; +import path from 'node:path'; +import fs from 'node:fs'; +import { getConfig } from '@/module/get-config.ts'; +import { fetchLink } from '@/module/download/install.ts'; +import { fileIsExist } from '@/uitls/file.ts'; +import { getHash, getBufferHash } from '@/uitls/hash.ts'; +import { useContextKey } from '@kevisual/context' +import semver from 'semver' +const getRunFilePath = () => { + const c = process.argv[1]; // 例子: /home/ubuntu/kevisual/cli/bin/envision.js + const runFilePath = path.resolve(c); + const isJs = runFilePath.endsWith('.js'); + let distDir = ''; + if (isJs) { + const dir = path.dirname(runFilePath); // /home/ubuntu/kevisual/cli/bin + distDir = path.relative(dir, '../dist'); // /home/ubuntu/kevisual/cli + } + distDir = path.resolve(process.cwd(), 'dist'); + return distDir; +} +const distFiles = ["assistant-server.js", "assistant.js", "envision.js"]; + +const downloadNewDistFiles = async (distDir: string) => { + const baseURL = getConfig().baseURL || 'https://kevisual.cn'; + const newData = distFiles.map(file => { + const url = `${baseURL}/root/cli/dist/${file}`; + const filePath = path.join(distDir, file); + const exist = fileIsExist(filePath); + let hash = ''; + hash = getHash(filePath); + return { url, filePath, exist, hash }; + }); + const promises = newData.map(async ({ url, filePath }) => { + return await fetchLink(url, { returnContent: true }); + }); + let isUpdate = false; + await Promise.all(promises).then(results => { + results.forEach((res, index) => { + const data = newData[index]; + const filePath = data.filePath; + const newHash = getBufferHash(res.content); + if (data.hash === newHash) { + return; + } + console.log('更新文件:', filePath); + isUpdate = true; + if (data.exist) { + fs.writeFileSync(filePath, res.content, 'utf-8'); + } else { + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(filePath, res.content, 'utf-8'); + } + }); + if (isUpdate) { + console.log('更新完成,请重新运行命令'); + } else { + console.log('检测完成'); + } + }).catch(error => { + console.error('Error downloading files:', error); + }); +} +const getVersion = async () => { + const baseURL = getConfig().baseURL || 'https://kevisual.cn'; + const file = 'package.json'; + const url = `${baseURL}/root/cli/${file}`; + const res = await fetchLink(url, { returnContent: true }); + const text = res.content.toString('utf-8'); + const json = JSON.parse(text); + const latestVersion = json.version; + const version = useContextKey('version'); + if (semver.lt(version, latestVersion)) { + console.log('当前版本:', version); + console.log('最新版本:', latestVersion); + downloadNewDistFiles(getRunFilePath()); + } else { + console.log('已经是最新版本', version); + } +} const update = new Command('update') .option('-g --global', 'update global') + .option('-n --npm', 'use npm to update', false) .description('update cli') .action((opts) => { try { - const cmd = opts.global ? 'npm install -g @kevisual/envision-cli' : 'npm install -D @kevisual/envision-cli'; - execSync(cmd, { stdio: 'inherit', encoding: 'utf-8' }); + if (opts.npm) { + const cmd = opts.global ? 'npm install -g @kevisual/envision-cli' : 'npm install -D @kevisual/envision-cli'; + execSync(cmd, { stdio: 'inherit', encoding: 'utf-8' }); + } else { + getVersion() + } } catch (error) { console.error('Error updating CLI:', error); } diff --git a/src/module/download/install.ts b/src/module/download/install.ts index a884b54..c3568b2 100644 --- a/src/module/download/install.ts +++ b/src/module/download/install.ts @@ -26,8 +26,14 @@ export type Package = { }; type Options = { check?: boolean; + /** + * 是否返回文本内容 + */ returnContent?: boolean; setToken?: boolean; + /** + * 本地文件hash + */ hash?: string; [key: string]: any; }; diff --git a/src/module/download/upload.ts b/src/module/download/upload.ts index 9761446..03fab64 100644 --- a/src/module/download/upload.ts +++ b/src/module/download/upload.ts @@ -66,7 +66,7 @@ type UploadOptions = { * @param opts.meta meta * @returns */ -export const upload = (opts: UploadOptions): Promise<{ code?: number; message?: string; [key: string]: any }> => { +export const upload = (opts: UploadOptions): Promise<{ code?: number; message?: string;[key: string]: any }> => { const form = opts?.form || new FormData(); if (!opts.form) { let hash = ''; diff --git a/src/program.ts b/src/program.ts index 44b3495..4cf2d1e 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,11 +1,16 @@ import { program, Command } from 'commander'; import fs from 'fs'; +import { useContextKey } from '@kevisual/context' // 将多个子命令加入主程序中 -let version = '0.0.1'; -try { - // @ts-ignore - if (ENVISION_VERSION) version = ENVISION_VERSION; -} catch (e) {} +const version = useContextKey('version', () => { + let version = '0.0.64'; + try { + // @ts-ignore + if (ENVISION_VERSION) version = ENVISION_VERSION; + } catch (e) { } + return version; +}) + // @ts-ignore program.name('app').description('A CLI tool with envison').version(version, '-V, --version'); diff --git a/src/uitls/hash.ts b/src/uitls/hash.ts index c435b7c..8fc3981 100644 --- a/src/uitls/hash.ts +++ b/src/uitls/hash.ts @@ -10,3 +10,7 @@ export const getHash = (file: string) => { export const getBufferHash = (buffer: Buffer) => { return MD5(buffer.toString()).toString(); }; + +export const getStringHash = (str: string) => { + return MD5(str).toString(); +}