diff --git a/.eslintrc.js b/.eslintrc.js index 5a4d8bb..dc957b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,7 @@ module.exports = getESLintConfig('react-ts', { '@typescript-eslint/explicit-function-return-type': 0, 'react-hooks/exhaustive-deps': 0, '@iceworks/best-practices/recommend-polyfill': 0, - 'import/order': 1, + 'import/order': 0, 'no-param-reassign': 0, '@typescript-eslint/no-require-imports': 0, 'no-await-in-loop': 0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..585aff7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# CHANGELOG + +## 0.2.1 + +- Fix: fail to get internal npm package info + +## 0.2.0 + +- Feat: git manager, including global config, user config and SSH key +- Feat: npm register manager, fast switch between different registries, e.g: npm, yarn, taobao +- Feat: npm package manager, fast install, upgrade, reinstall and uninstall global dependencies + +## 0.1.1 + +- Feat: record DAU +- Feat: download package automatically +- Fix: nvm install fail when bash profile files exist +- Fix: icon size +- Fix: code command was not found + +## 0.1.0 + +- Feat: fast install packages, including VS Code/Git/Node.js/AppWorks/Chrome +- Feat: node version manager \ No newline at end of file diff --git a/main/constants.ts b/main/constants.ts index 3e42eb5..f1d47ae 100644 --- a/main/constants.ts +++ b/main/constants.ts @@ -1,8 +1,9 @@ import * as path from 'path'; +import * as os from 'os'; import { ILocalPackageInfo } from './types'; export const APPLICATIONS_DIR_PATH = '/Applications'; - +export const HOME_DIR = os.homedir(); export const PACKAGE_JSON_FILE_NAME = 'package.json'; export const TOOLKIT_DIR = path.join(process.env.HOME, '.toolkit'); @@ -26,15 +27,14 @@ export const INSTALL_COMMAND_PACKAGES = [ commandRelativePath: './Contents/Resources/app/bin/code', }, ]; -export const NOT_REINSTALL_DEPENDENCIES = ['npm']; // bash profile -export const PROFILE_FILES = ['.bash_profile', '.bashrc', '.zshrc']; -export const DEFAULT_PROFILE_FILE = '.bash_profile'; +export const ZSHRC_FILE_NAME = '.zshrc'; +export const BASH_PROFILE_FILE_NAME = '.bash_profile'; +export const PROFILE_FILES = [BASH_PROFILE_FILE_NAME, '.bashrc', ZSHRC_FILE_NAME]; // npm -export const NPMRC_PATH = path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.npmrc'); +export const NPMRC_PATH = path.join(HOME_DIR, '.npmrc'); export const NPM_REGISTRY = 'https://registry.npmjs.org/'; export const TAOBAO_NPM_REGISTRY = 'https://registry.npm.taobao.org'; -export const ALI_NPM_REGISTRY = 'https://registry.npm.alibaba-inc.com/'; export const TAOBAO_NODE_MIRROR = 'https://npm.taobao.org/mirrors/node'; // git -export const GLOBAL_GITCONFIG_PATH = path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.gitconfig'); +export const GLOBAL_GITCONFIG_PATH = path.join(HOME_DIR, '.gitconfig'); diff --git a/main/data/data.json b/main/data/data.json index c22e3bc..8f6b250 100644 --- a/main/data/data.json +++ b/main/data/data.json @@ -122,12 +122,8 @@ { "name": "taobao", "registry": "https://registry.npm.taobao.org/", - "isInternal": false - }, - { - "name": "tnpm", - "registry": " https://registry.npm.alibaba-inc.com/", - "isInternal": true + "isInternal": false, + "recommended": true } ], "browserExtensions": [ diff --git a/main/data/shells/install-nvm.sh b/main/data/shells/install-nvm.sh index 051ed60..53d9394 100644 --- a/main/data/shells/install-nvm.sh +++ b/main/data/shells/install-nvm.sh @@ -68,7 +68,7 @@ nvm_source() { elif [ "_$NVM_METHOD" = "_git" ] || [ -z "$NVM_METHOD" ]; then # NVM_SOURCE_URL="https://github.com/${NVM_GITHUB_REPO}.git" # use China mirror - NVM_SOURCE_URL="https://gitee.com/mirrors/nvm.git" + NVM_SOURCE_URL="https://gitee.com/luhengchang/nvm.git" else nvm_echo >&2 "Unexpected value \"$NVM_METHOD\" for \$NVM_METHOD" return 1 diff --git a/main/git/config.ts b/main/git/config.ts index e8eee2e..6d53a4c 100644 --- a/main/git/config.ts +++ b/main/git/config.ts @@ -11,6 +11,7 @@ import { removeSSHConfig, addSSHConfig, } from './ssh'; +import { record } from '../recorder'; const USER_GIT_CONFIG_FILENAME_PREFIX = '.gitconfig-'; const IGNORE_CONFIG_KEYS = ['gitDir']; @@ -24,6 +25,10 @@ export async function getGlobalGitConfig() { export async function updateGlobalGitConfig(gitConfig: object) { log.info('update-global-git-config', gitConfig); await writeGitConfig(GLOBAL_GITCONFIG_PATH, gitConfig); + record({ + module: 'git', + action: 'updateGlobalGitConfig', + }); } export async function getExistedUserGitConfigNames() { @@ -70,6 +75,10 @@ export async function addUserGitConfig(userGitConfig: IAddUserConfig) { delete userGitConfig.configName; await writeGitConfig(gitConfigPath, userGitConfig); await addSSHConfig({ hostName, configName, userName }); + record({ + module: 'git', + action: 'addUserGitConfig', + }); } export async function updateUserGitConfig(gitConfig: any, configName: string) { @@ -85,6 +94,10 @@ export async function updateUserGitConfig(gitConfig: any, configName: string) { await writeGitConfig(gitConfigPath, gitConfig); log.info('update-user-git-config', configName, gitConfig); + record({ + module: 'git', + action: 'updateUserGitConfig', + }); } async function getUserGitDirs() { @@ -125,6 +138,10 @@ export async function updateUserGitDir( await writeGitConfig(GLOBAL_GITCONFIG_PATH, globalGitConfig); log.info('update-user-git-dir: ', currentIncludeIfKey, globalGitConfig[currentIncludeIfKey]); + record({ + module: 'git', + action: 'updateUserGitDir', + }); } export async function removeUserGitDir(gitDir: string, configName: string) { @@ -137,6 +154,10 @@ export async function removeUserGitDir(gitDir: string, configName: string) { delete globalGitConfig[includeIfKey]; await writeGitConfig(GLOBAL_GITCONFIG_PATH, globalGitConfig); log.info('remove-user-git-dir: ', includeIfKey, gitConfigPath); + record({ + module: 'git', + action: 'removeUserGitDir', + }); } else { const error = new Error(`Can not remove ${gitDir}. The ${includeIfValue} is not found.`); log.error(error); @@ -154,6 +175,11 @@ export async function removeUserGitConfig(configName: string, gitDirs = []) { // remove the gitconfig file const gitConfigPath = getGitConfigPath(configName); await fse.remove(gitConfigPath); + + record({ + module: 'git', + action: 'removeUserGitConfig', + }); } async function parseGitConfig(gitConfigPath: string) { diff --git a/main/git/ssh.ts b/main/git/ssh.ts index 81f4f7c..aa44238 100644 --- a/main/git/ssh.ts +++ b/main/git/ssh.ts @@ -4,10 +4,10 @@ import * as fse from 'fs-extra'; import SSHKeyGen = require('ssh-keygen'); import SSHConfig = require('ssh-config'); import log from '../utils/log'; +import { HOME_DIR } from '../constants'; const SSHKeyGenAsync = util.promisify(SSHKeyGen); -const HOME_DIR = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; export const SSHDir = path.join(HOME_DIR, '.ssh'); const SSHConfigPath = path.join(SSHDir, 'config'); diff --git a/main/ipc/checkIsAliInternal.ts b/main/ipc/checkIsAliInternal.ts new file mode 100644 index 0000000..4cc244e --- /dev/null +++ b/main/ipc/checkIsAliInternal.ts @@ -0,0 +1,8 @@ +import { ipcMain } from 'electron'; +import checkIsAliInternal from '../utils/checkIsAliInternal'; + +export default () => { + ipcMain.handle('check-is-ali-internal', async () => { + return await checkIsAliInternal(); + }); +}; diff --git a/main/ipc/handleNpmDependency.ts b/main/ipc/handleNpmDependency.ts index fcd237b..4bb4817 100644 --- a/main/ipc/handleNpmDependency.ts +++ b/main/ipc/handleNpmDependency.ts @@ -1,4 +1,7 @@ +import * as child_process from 'child_process'; +import * as path from 'path'; import { ipcMain, IpcMainInvokeEvent } from 'electron'; +import { send as sendMainWindow } from '../window'; import { getGlobalDependencies, uninstallGlobalDependency, @@ -6,7 +9,12 @@ import { reinstallGlobalDependency, searchNpmDependencies, installGlobalDependency, + getGlobalDependenciesInfo, } from '../npm/dependency'; +import killChannelChildProcess from '../utils/killChannelChildProcess'; +import { record } from '../recorder'; + +const childProcessMap = new Map(); export default () => { ipcMain.handle('get-global-npm-dependencies', async (e: IpcMainInvokeEvent, force) => { @@ -33,4 +41,31 @@ export default () => { ipcMain.handle('search-npm-dependencies', async (e: IpcMainInvokeEvent, query: string) => { return await searchNpmDependencies(query); }); + + ipcMain.handle('get-global-dependencies-info', async () => { + return await getGlobalDependenciesInfo(); + }); + + ipcMain.handle('create-custom-global-dependencies-dir', async (e: IpcMainInvokeEvent, channel: string, currentGlobalDepsPath: string) => { + const childProcess = child_process.fork(path.join(__dirname, '..', 'npm/dependency/createCustomGlobalDepsDir')); + childProcessMap.set(channel, childProcess); + + childProcess.send({ currentGlobalDepsPath, channel }); + + childProcessMap.set(channel, childProcess); + + childProcess.on('message', ({ data }: any) => { + if (data.status === 'done') { + record({ + module: 'node', + action: 'createCustomGlobalDependenciesDir', + }); + } + sendMainWindow(channel, data); + }); + }); + + ipcMain.handle('cancel-create-custom-global-dependencies-dir', async (e: IpcMainInvokeEvent, channel: string) => { + killChannelChildProcess(childProcessMap, channel); + }); }; diff --git a/main/ipc/index.ts b/main/ipc/index.ts index 15f7632..d1ac685 100644 --- a/main/ipc/index.ts +++ b/main/ipc/index.ts @@ -6,6 +6,7 @@ import handleGitConfig from './handleGitConfig'; import getFolderPath from './getFolderPath'; import handleNpmRegistry from './handleNpmRegistry'; import handleNpmDependency from './handleNpmDependency'; +import checkIsAliInternal from './checkIsAliInternal'; export default () => { getBasePackagesInfo(); @@ -23,4 +24,6 @@ export default () => { handleNpmRegistry(); handleNpmDependency(); + + checkIsAliInternal(); }; diff --git a/main/ipc/installBasePackages.ts b/main/ipc/installBasePackages.ts index 60b180e..a569d26 100644 --- a/main/ipc/installBasePackages.ts +++ b/main/ipc/installBasePackages.ts @@ -8,6 +8,7 @@ import killChannelChildProcess from '../utils/killChannelChildProcess'; import log from '../utils/log'; import nodeCache from '../utils/nodeCache'; import store, { packagesDataKey } from '../store'; +import { record } from '../recorder'; const childProcessMap = new Map(); @@ -33,6 +34,10 @@ export default () => { if (channel === processChannel) { if (data.status === 'done') { killChannelChildProcess(childProcessMap, installChannel); + record({ + module: 'base', + action: 'installPackages', + }); } // save process data to cache const processCaches = nodeCache.get(channel) || []; diff --git a/main/ipc/installNode.ts b/main/ipc/installNode.ts index cf76556..7ac61c9 100644 --- a/main/ipc/installNode.ts +++ b/main/ipc/installNode.ts @@ -6,6 +6,7 @@ import { send as sendMainWindow } from '../window'; import killChannelChildProcess from '../utils/killChannelChildProcess'; import nodeCache from '../utils/nodeCache'; import log from '../utils/log'; +import { record } from '../recorder'; const childProcessMap = new Map(); @@ -15,13 +16,11 @@ export default () => { { managerName, nodeVersion, - reinstallGlobalDeps, installChannel, processChannel, }: { managerName: string; nodeVersion: string; - reinstallGlobalDeps: boolean; installChannel: string; processChannel: string; }, @@ -38,7 +37,6 @@ export default () => { childProcess.send({ managerName, nodeVersion, - reinstallGlobalDeps, installChannel, processChannel, }); @@ -48,8 +46,16 @@ export default () => { const { status, result } = data; if (status === 'done') { killChannelChildProcess(childProcessMap, installChannel); + record({ + module: 'node', + action: 'installNode', + data: { + version: nodeVersion, + nodeManager: managerName, + }, + }); } else if (status === 'success' && result && result.nodePath) { - // nodeEnvPath e.g: /Users/xxx/.nvm/versions/node/v14.15.0/bin/path -> Users/xxx/.nvm/versions/node/v14.15.0/bin + // nodeEnvPath e.g: /Users/xxx/.nvm/versions/node/v14.15.0/bin/node -> Users/xxx/.nvm/versions/node/v14.15.0/bin const nodeEnvPath = result.nodePath.replace('/bin/node', '/bin'); // process.env.PATH: /usr/local/bin -> /Users/xxx/.nvm/versions/node/v14.15.0/bin:/usr/local/bin process.env.PATH = `${nodeEnvPath}${path.delimiter}${process.env.PATH}`; @@ -66,6 +72,7 @@ export default () => { } nodeCache.set(channel, processCaches); } + sendMainWindow(channel, data); }); }); diff --git a/main/node/NvmManager.ts b/main/node/NvmManager.ts index 0e0d87c..96d62c8 100644 --- a/main/node/NvmManager.ts +++ b/main/node/NvmManager.ts @@ -1,43 +1,30 @@ import * as path from 'path'; import * as execa from 'execa'; -import * as shell from 'shelljs'; import { INodeManager } from '../types'; import log from '../utils/log'; import formatNodeVersion from '../utils/formatNodeVersion'; -import { NOT_REINSTALL_DEPENDENCIES } from '../constants'; -import getNpmRegistry from '../utils/getNpmRegistry'; +import getShellName from '../utils/getShellName'; class NvmManager implements INodeManager { channel: string; std: string; - previousNpmPath: string; - - globalNpmDependencies: string[]; - nodePath: string; constructor(channel = '') { this.channel = channel; this.std = ''; - this.previousNpmPath = ''; - this.globalNpmDependencies = []; this.nodePath = ''; } - installNode = (version: string, reinstallGlobalDeps = true) => { - if (reinstallGlobalDeps) { - // get the previous npm path - const { stdout } = shell.which('npm'); - this.previousNpmPath = stdout; - } + installNode = (version: string) => { const formattedVersion = formatNodeVersion(version); const shFilePath = path.resolve(__dirname, '../data/shells', 'nvm-install-node.sh'); return new Promise((resolve, reject) => { const args: string[] = [shFilePath, formattedVersion]; - const cp = execa('sh', args); + const cp = execa(getShellName(), args); cp.stdout.on('data', this.listenFunc); @@ -45,7 +32,7 @@ class NvmManager implements INodeManager { cp.on('error', (buffer: Buffer) => { this.listenFunc(buffer); - log.error(buffer.toString()); + log.error(new Error(buffer.toString())); reject(buffer.toString()); }); @@ -58,37 +45,6 @@ class NvmManager implements INodeManager { }); }; - reinstallDependencies = () => { - return this.getGlobalNpmDependencies() - .then(() => getNpmRegistry()) - .then((npmRegistry) => { - return new Promise((resolve, reject) => { - const args = ['i', '-g', ...this.globalNpmDependencies, '--registry', npmRegistry]; - const cp = execa('npm', args, { - // specify execPath to the node path which installed just now - execPath: this.nodePath, - preferLocal: true, - // Don't extend env because it will not use the current(new) npm to install dependencies - extendEnv: false, - }); - - cp.stdout.on('data', this.listenFunc); - - cp.stderr.on('data', this.listenFunc); - - cp.on('error', (buffer: Buffer) => { - this.listenFunc(buffer); - log.error(buffer.toString()); - reject(buffer.toString()); - }); - - cp.on('exit', () => { - resolve(null); - }); - }); - }); - }; - private listenFunc = (buffer: Buffer) => { const chunk = buffer.toString(); this.std += chunk; @@ -110,22 +66,6 @@ class NvmManager implements INodeManager { } return undefined; } - - private async getGlobalNpmDependencies() { - if (!this.previousNpmPath) { - throw new Error('Npm command was not Found.'); - } - const { stdout } = await execa(this.previousNpmPath, ['list', '-g', '--depth=0', '--json']); - if (stdout) { - const { dependencies = {} } = JSON.parse(stdout); - const depNames = Object.keys(dependencies).filter((dep: string) => !NOT_REINSTALL_DEPENDENCIES.includes(dep)) || []; - - depNames.forEach((dep: string) => { - const { version: depVersion } = dependencies[dep]; - this.globalNpmDependencies.push(`${dep}@${depVersion}`); - }); - } - } } export default NvmManager; diff --git a/main/node/index.ts b/main/node/index.ts index e865a4c..ff2eae5 100644 --- a/main/node/index.ts +++ b/main/node/index.ts @@ -14,31 +14,26 @@ export default function getNodeManager(managerName: string, channel?: string) { return nodeManager; } -function processListener({ managerName, nodeVersion, reinstallGlobalDeps, installChannel, processChannel }) { +function processListener({ managerName, nodeVersion, installChannel, processChannel }) { const nodeManager = getNodeManager(managerName, installChannel); - const tasks = ['installNode', 'reinstallDependencies']; + const task = 'installNode'; install(); async function install() { - for (let i = 0; i < tasks.length; i++) { - const task = tasks[i]; - try { - let result; - - if (task === tasks[0] || (task === tasks[1] && reinstallGlobalDeps)) { - process.send({ channel: processChannel, data: { task, status: 'process' } }); - result = await nodeManager[task](nodeVersion, reinstallGlobalDeps); - } - process.send({ channel: processChannel, data: { task, status: 'success', result } }); - } catch (error) { - const errMsg = error instanceof Error ? error.message : error; - log.error(errMsg); - process.send({ channel: processChannel, data: { task, status: 'error', errMsg } }); - } + try { + process.send({ channel: processChannel, data: { task, status: 'process' } }); + + const result = await nodeManager.installNode(nodeVersion); + process.send({ channel: processChannel, data: { task, status: 'success', result } }); + + process.send({ channel: processChannel, data: { status: 'done' } }); + } catch (error) { + const errMsg = error instanceof Error ? error.message : error; + log.error(errMsg); + process.send({ channel: processChannel, data: { task, status: 'error', errMsg } }); } - process.send({ channel: processChannel, data: { status: 'done' } }); } } diff --git a/main/npm/dependency/createCustomGlobalDepsDir.ts b/main/npm/dependency/createCustomGlobalDepsDir.ts new file mode 100644 index 0000000..ebb7b08 --- /dev/null +++ b/main/npm/dependency/createCustomGlobalDepsDir.ts @@ -0,0 +1,88 @@ +import * as path from 'path'; +import * as fse from 'fs-extra'; +import gulp = require('gulp'); +import gulpZip = require('gulp-zip'); +import shellProfile = require('shell-profile'); +import * as AdmZip from 'adm-zip'; +import { getNpmInfo, setNpmInfo } from '../npmInfo'; +import log from '../../utils/log'; +import { GLOBAL_DEPENDENCIES_PATH } from './globalDependenciesPath'; + +async function createCustomGlobalDependenciesDir(channel: string, currentGlobalDepsPath: string) { + if (!path.relative(GLOBAL_DEPENDENCIES_PATH, currentGlobalDepsPath)) { + process.send({ channel, data: { status: 'error', message: `${currentGlobalDepsPath} 已经是推荐的全局依赖路径` } }); + } + const profilePath = shellProfile(); + try { + // 0. copy prefix global dependencies to target + await copyGlobalDependencies(currentGlobalDepsPath, channel); + // 1. write prefix to npm config + await writePrefixToNpmrc(channel); + // 2. write global dependencies path to profile + await writePathToProfile(profilePath, channel); + + process.send({ channel, data: { percent: 100, status: 'done' } }); + } catch (error) { + log.error(error); + process.send({ channel, data: { status: 'error', message: error.message } }); + } +} + +async function copyGlobalDependencies(currentGlobalDepsPath: string, channel: string) { + const binDestPath = path.join(GLOBAL_DEPENDENCIES_PATH, 'bin'); + const libDestPath = path.join(GLOBAL_DEPENDENCIES_PATH, 'lib'); + + return new Promise((resolve, reject) => { + process.send({ channel, data: { percent: 5, message: '压缩全局依赖中...', status: 'process' } }); + // 0. compress dependencies + gulp + .src(path.join(currentGlobalDepsPath, 'lib', '**')) + .pipe(gulpZip('lib.zip')) + .pipe(gulp.dest(GLOBAL_DEPENDENCIES_PATH)) + .on('finish', () => { + resolve(path.join(GLOBAL_DEPENDENCIES_PATH, 'lib.zip')); + }) + .on('error', (err) => { + reject(err); + }); + }) + .then((zipPath: string) => { + // 1. decompress dependencies to the target path + process.send({ channel, data: { percent: 30, message: '解压全局 npm 依赖中...', status: 'process' } }); + const zip = new AdmZip(zipPath); + zip.extractAllTo(libDestPath, true); + return Promise.resolve(); + }) + .then(() => { + return fse.emptyDir(binDestPath); + }) + .then(() => { + // 2. copy symlink + process.send({ channel, data: { percent: 60, message: '复制 bin 目录软链接中...', status: 'process' } }); + return fse.copy(path.join(currentGlobalDepsPath, 'bin'), path.join(GLOBAL_DEPENDENCIES_PATH, 'bin')); + }) + .catch((err) => { + throw err; + }); +} + +async function writePrefixToNpmrc(channel: string) { + process.send({ channel, data: { percent: 80, message: '写入配置信息至 npmrc...', status: 'process' } }); + const npmInfo = await getNpmInfo(); + npmInfo.prefix = GLOBAL_DEPENDENCIES_PATH; + await setNpmInfo(npmInfo); +} + +async function writePathToProfile(profilePath: string, channel: string) { + process.send({ channel, data: { percent: 90, message: `写入配置信息至 ${profilePath}...`, status: 'process' } }); + const profile = await fse.readFile(profilePath, 'utf-8'); + const exportPathStr = `export PATH=${GLOBAL_DEPENDENCIES_PATH}/bin:$PATH`; + const newProfile = `${exportPathStr}\n${profile}`; + await fse.writeFile(profilePath, newProfile); +} + +function processListener({ channel, currentGlobalDepsPath }) { + createCustomGlobalDependenciesDir(channel, currentGlobalDepsPath); +} + +process.on('message', processListener); diff --git a/main/npm/dependency/getInfo.ts b/main/npm/dependency/getInfo.ts index 303f230..f082432 100644 --- a/main/npm/dependency/getInfo.ts +++ b/main/npm/dependency/getInfo.ts @@ -1,8 +1,9 @@ import packageJSON, { AbbreviatedMetadata } from 'package-json'; +import getNpmRegistry from '../../utils/getNpmRegistry'; +import getVersionStatus from '../../utils/getVersionStatus'; import executeCommandJSON from '../../utils/executeCommandJSON'; import log from '../../utils/log'; import nodeCache from '../../utils/nodeCache'; -import { getCurrentRegistry } from '../registry'; interface InstalledDependency { version: string; @@ -30,7 +31,14 @@ export async function getGlobalDependencies(force: boolean) { const depsInfo = []; for (const dep of deps) { - const latestVersion = await getLatestVersion(dep); + let latestVersion = ''; + // avoid failing to get the latest version of one package + // so that other packages can't get the latest versions + try { + latestVersion = await getLatestVersion(dep); + } catch (err) { + log.error(err); + } const currentVersion = getCurrentVersion(installedDeps[dep]); depsInfo.push({ @@ -38,7 +46,7 @@ export async function getGlobalDependencies(force: boolean) { type: 'global', currentVersion, latestVersion, - isOutdated: latestVersion !== currentVersion, + isOutdated: getVersionStatus(currentVersion, latestVersion) === 'upgradeable', }); } @@ -54,12 +62,13 @@ export async function getGlobalDependencies(force: boolean) { function getCurrentVersion(installedDependency: InstalledDependency) { return installedDependency.version; } + interface PackageJSON extends AbbreviatedMetadata { version: string; } async function getLatestVersion(name: string) { - const registryUrl = await getCurrentRegistry(); + const registryUrl = await getNpmRegistry(); const { version: latest } = await packageJSON( name, { registryUrl }, diff --git a/main/npm/dependency/globalDependenciesPath.ts b/main/npm/dependency/globalDependenciesPath.ts new file mode 100644 index 0000000..c60ffbc --- /dev/null +++ b/main/npm/dependency/globalDependenciesPath.ts @@ -0,0 +1,24 @@ +import * as path from 'path'; +import * as execa from 'execa'; +import { HOME_DIR } from '../../constants'; +import { getNpmInfo } from '../npmInfo'; + +const GLOBAL_DEPENDENCIES_DIR = 'npm_global'; +export const GLOBAL_DEPENDENCIES_PATH = path.join(HOME_DIR, GLOBAL_DEPENDENCIES_DIR); + +export async function getGlobalDependenciesInfo() { + let prefix; + // first get from the .npmrc + const npmInfo = await getNpmInfo(); + prefix = npmInfo.prefix; + if (!npmInfo.prefix) { + // if prefix was not found in .npmrc, run the npm config command + const { stdout } = await execa('npm', ['config', 'get', 'prefix']); + prefix = stdout; + } + return { + recommendedPath: GLOBAL_DEPENDENCIES_PATH, + currentPath: prefix, + exists: prefix === GLOBAL_DEPENDENCIES_PATH, + }; +} diff --git a/main/npm/dependency/index.ts b/main/npm/dependency/index.ts index c520812..683b5dd 100644 --- a/main/npm/dependency/index.ts +++ b/main/npm/dependency/index.ts @@ -4,3 +4,5 @@ export * from './install'; export * from './update'; export * from './reinstall'; export * from './search'; +export * from './globalDependenciesPath'; +export * from './createCustomGlobalDepsDir'; diff --git a/main/npm/dependency/install.ts b/main/npm/dependency/install.ts index b495dff..30bb24a 100644 --- a/main/npm/dependency/install.ts +++ b/main/npm/dependency/install.ts @@ -1,5 +1,7 @@ import * as execa from 'execa'; import log from '../../utils/log'; +import getNpmRegistry from '../../utils/getNpmRegistry'; +import { record } from '../../recorder'; export async function installGlobalDependency(dependency: string, version: string) { if (!dependency) { @@ -8,11 +10,20 @@ export async function installGlobalDependency(dependency: string, version: strin throw new Error(errMsg); } + const npmRegistry = await getNpmRegistry(); try { - const command = `npm install ${dependency}@${version || 'latest'} -g`; + const command = `npm install ${dependency}@${version || 'latest'} -g --registry=${npmRegistry}`; log.info('Command: ', command); await execa.command(command); log.info(`Install ${dependency} successfully.`); + record({ + module: 'node', + action: 'installGlobalDependency', + data: { + dependency, + version, + }, + }); } catch (err) { log.error(err.message); throw new Error(err.message); diff --git a/main/npm/dependency/reinstall.ts b/main/npm/dependency/reinstall.ts index 94e418f..2e7a1b0 100644 --- a/main/npm/dependency/reinstall.ts +++ b/main/npm/dependency/reinstall.ts @@ -1,3 +1,4 @@ +import { record } from '../../recorder'; import log from '../../utils/log'; import { installGlobalDependency } from './install'; import { uninstallGlobalDependency } from './uninstall'; @@ -12,6 +13,14 @@ export async function reinstallGlobalDependency(dependency: string, version: str try { await uninstallGlobalDependency(dependency); await installGlobalDependency(dependency, version); + record({ + module: 'node', + action: 'reinstallGlobalDependency', + data: { + dependency, + version, + }, + }); } catch (err) { log.error(err.message); throw new Error(err.message); diff --git a/main/npm/dependency/search.ts b/main/npm/dependency/search.ts index e6be3be..09ba1be 100644 --- a/main/npm/dependency/search.ts +++ b/main/npm/dependency/search.ts @@ -1,18 +1,18 @@ import fetch from 'node-fetch'; import urljoin = require('url-join'); import log from '../../utils/log'; -import { getCurrentRegistry } from '../registry'; +import getNpmRegistry from '../../utils/getNpmRegistry'; import { getGlobalDependencies } from './getInfo'; export async function searchNpmDependencies(query: string) { if (!query) { - const errorMsg = 'The search content is empty. Please provide it.'; + const errorMsg = '请输入 npm 依赖名称'; log.error(errorMsg); throw new Error(errorMsg); } try { - const currentRegistry: string = await getCurrentRegistry(); - const url = urljoin(currentRegistry, query); + const npmRegistry: string = await getNpmRegistry(); + const url = urljoin(npmRegistry, query); const res = await fetch(url); const content = await res.json(); diff --git a/main/npm/dependency/uninstall.ts b/main/npm/dependency/uninstall.ts index 30afea0..fb7186f 100644 --- a/main/npm/dependency/uninstall.ts +++ b/main/npm/dependency/uninstall.ts @@ -1,5 +1,6 @@ import * as execa from 'execa'; import log from '../../utils/log'; +import { record } from '../../recorder'; export async function uninstallGlobalDependency(dependency: string) { if (!dependency) { @@ -13,6 +14,13 @@ export async function uninstallGlobalDependency(dependency: string) { log.info('Command: ', command); await execa.command(command); log.info(`Uninstall ${dependency} successfully.`); + record({ + module: 'node', + action: 'uninstallGlobalDependency', + data: { + dependency, + }, + }); } catch (err) { log.error(err.message); throw new Error(err.message); diff --git a/main/npm/dependency/update.ts b/main/npm/dependency/update.ts index 1f30dd4..0d8ae8a 100644 --- a/main/npm/dependency/update.ts +++ b/main/npm/dependency/update.ts @@ -1,5 +1,7 @@ import * as execa from 'execa'; +import { record } from '../../recorder'; import log from '../../utils/log'; +import getNpmRegistry from '../../utils/getNpmRegistry'; export async function updateGlobalDependency(dependency: string) { if (!dependency) { @@ -8,10 +10,18 @@ export async function updateGlobalDependency(dependency: string) { throw new Error(errMsg); } try { - const command = `npm update -g ${dependency}`; + const npmRegistry = await getNpmRegistry(); + const command = `npm update -g ${dependency} --registry=${npmRegistry}`; log.info('Command: ', command); await execa.command(command); log.info(`Update ${dependency} successfully.`); + record({ + module: 'node', + action: 'uninstallGlobalDependency', + data: { + dependency, + }, + }); } catch (err) { log.error(err.message); throw new Error(err.message); diff --git a/main/npm/npmInfo.ts b/main/npm/npmInfo.ts new file mode 100644 index 0000000..0724e49 --- /dev/null +++ b/main/npm/npmInfo.ts @@ -0,0 +1,13 @@ +import * as ini from 'ini'; +import * as fse from 'fs-extra'; +import { NPMRC_PATH } from '../constants'; + +export async function getNpmInfo() { + const npmrcExists = await fse.pathExists(NPMRC_PATH); + const npmrc = npmrcExists ? ini.parse(fse.readFileSync(NPMRC_PATH, 'utf-8')) : {}; + return npmrc; +} + +export async function setNpmInfo(npmrc: object) { + await fse.writeFile(NPMRC_PATH, ini.stringify(npmrc)); +} diff --git a/main/npm/registry.ts b/main/npm/registry.ts index dbffb32..70515b0 100644 --- a/main/npm/registry.ts +++ b/main/npm/registry.ts @@ -1,9 +1,9 @@ -import * as ini from 'ini'; -import * as fse from 'fs-extra'; import store, { packagesDataKey } from '../store'; +import { record } from '../recorder'; import { INPMRegistry } from '../types'; -import { NPMRC_PATH, NPM_REGISTRY } from '../constants'; +import { NPM_REGISTRY } from '../constants'; import checkIsAliInternal from '../utils/checkIsAliInternal'; +import { getNpmInfo, setNpmInfo } from './npmInfo'; const REGISTRY_FIELD = 'registry'; @@ -15,20 +15,20 @@ export async function getCurrentRegistry() { export async function setCurrentRegistry(registry: string) { const npmrc = await getNpmInfo(); npmrc[REGISTRY_FIELD] = registry; - await fse.writeFile(NPMRC_PATH, ini.stringify(npmrc)); + await setNpmInfo(npmrc); + record({ + module: 'node', + action: 'setNpmRegistry', + }); } export async function getAllRegistries() { const isAliInternal = await checkIsAliInternal(); const data = store.get(packagesDataKey); - const { npmRegistries = [] }: { npmRegistries: INPMRegistry[] } = data; - return npmRegistries.filter((npmRegistry) => { + const { npmRegistries: originNpmRegistries = [] }: { npmRegistries: INPMRegistry[] } = data; + const npmRegistries = originNpmRegistries.filter((npmRegistry) => { return isAliInternal ? true : !npmRegistry.isInternal; }); -} -async function getNpmInfo() { - const npmrcExists = await fse.pathExists(NPMRC_PATH); - const npmrc = npmrcExists ? ini.parse(fse.readFileSync(NPMRC_PATH, 'utf-8')) : {}; - return npmrc; + return npmRegistries; } diff --git a/main/packageInfo/cli/cli.ts b/main/packageInfo/cli/cli.ts index de4a49a..6c2a296 100644 --- a/main/packageInfo/cli/cli.ts +++ b/main/packageInfo/cli/cli.ts @@ -15,7 +15,7 @@ async function getLocalCliInfo(name: string, latestVersion: string | null) { } localCliInfo.localPath = cliPath; } catch (error) { - log.error(error.message); + log.error(error); return localCliInfo; } // get cli version @@ -28,7 +28,7 @@ async function getLocalCliInfo(name: string, latestVersion: string | null) { const cliVersionMatch = cliVersion.match(/(\d+(\.\d+)*)/); localCliInfo.localVersion = cliVersionMatch ? cliVersionMatch[1] : cliVersion; } catch (error) { - log.error(`Tool ${name} version is not found. Error: ${error.message}`); + log.error(error); } // get cli version status localCliInfo.versionStatus = getVersionStatus(localCliInfo.localVersion, latestVersion); diff --git a/main/packageInfo/cli/node.ts b/main/packageInfo/cli/node.ts index 18b6326..5569a08 100644 --- a/main/packageInfo/cli/node.ts +++ b/main/packageInfo/cli/node.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import * as execa from 'execa'; import { INodeVersionManagerInfo } from '../../types'; import log from '../../utils/log'; +import getShellName from '../../utils/getShellName'; import getLocalCliInfo from './cli'; const nodeManagerInfoProcessor = { @@ -41,12 +42,12 @@ async function getNvmInfo(): Promise { }; const shFilePath = path.resolve(__dirname, '../../data/shells', 'is-nvm-installed.sh'); try { - const { stdout } = await execa('sh', [shFilePath]); + const { stdout } = await execa(getShellName(), [shFilePath]); if (stdout === 'nvm') { nvmInfo.managerVersionStatus = 'installed'; } } catch (error) { - log.error(error.message); + log.error(error); } return nvmInfo; diff --git a/main/packageInfo/index.ts b/main/packageInfo/index.ts index ed65e73..cc0357e 100644 --- a/main/packageInfo/index.ts +++ b/main/packageInfo/index.ts @@ -21,7 +21,7 @@ export async function getPackageInfo(basePackageInfo: IBasePackageInfo): Promise const localPackageInfo: ILocalPackageInfo = await getLocalInfoFunc(basePackageInfo); return { ...ret, ...localPackageInfo }; } catch (error) { - log.error(error.message); + log.error(error); } } diff --git a/main/packageInstaller/CliInstaller.ts b/main/packageInstaller/CliInstaller.ts index 6d50d15..95de2e8 100644 --- a/main/packageInstaller/CliInstaller.ts +++ b/main/packageInstaller/CliInstaller.ts @@ -4,6 +4,7 @@ import { IPackageInfo, IPackageInstaller } from '../types'; import log from '../utils/log'; import ensureProfileExists from '../utils/ensureProfileExists'; import writeLog from '../utils/writeLog'; +import getShellName from '../utils/getShellName'; class CliInstaller implements IPackageInstaller { channel: string; @@ -53,7 +54,7 @@ class CliInstaller implements IPackageInstaller { process.send({ channel: this.channel, data: { chunk, ln: false } }); }; - const cp = execa('sh', [shPath]); + const cp = execa(getShellName(), [shPath]); cp.stdout.on('data', listenFunc); diff --git a/main/packageInstaller/index.ts b/main/packageInstaller/index.ts index 1603b0d..a953ff1 100644 --- a/main/packageInstaller/index.ts +++ b/main/packageInstaller/index.ts @@ -92,7 +92,7 @@ async function installPackages({ process.send({ channel: processChannel, data: { currentIndex: i, status } }); } catch (error) { errMsg = error instanceof Error ? error.message : error; - log.error(errMsg); + log.error(error); status = 'error'; process.send({ channel: processChannel, data: { currentIndex: i, status: 'error', errMsg } }); } finally { diff --git a/main/preload.ts b/main/preload.ts index 81031e6..1e5fa9b 100644 --- a/main/preload.ts +++ b/main/preload.ts @@ -1,5 +1,6 @@ window.addEventListener('DOMContentLoaded', () => { ['chrome', 'node', 'electron'].forEach((type) => { + // eslint-disable-next-line no-console console.log(`${type}-version`, process.versions[type]); }); diff --git a/main/recorder/index.ts b/main/recorder/index.ts index f60c2b5..f025f9f 100644 --- a/main/recorder/index.ts +++ b/main/recorder/index.ts @@ -1,12 +1,12 @@ -import { recordUV } from '@appworks/recorder'; +import { recordUV, IGoldlogParam, ILogParam } from '@appworks/recorder'; import store, { recordKey } from '../store'; const MAIN_KEY = 'main'; const RECORD_MODULE_KEY = 'logger'; -function getRecordUrl(action: string) { - return `http://gm.mmstat.com/appworks.toolkit.${action}?t=${(new Date()).valueOf()}`; +function getGoldlogUrl(goldlogKey: string) { + return `http://gm.mmstat.com/appworks.toolkit.${goldlogKey}?t=${(new Date()).valueOf()}`; } export function recordDAU() { @@ -19,7 +19,21 @@ export function recordDAU() { platform: process.platform, }, }; - const url = getRecordUrl(action); + const url = getGoldlogUrl(action); return recordUV(goldlogParam, store, recordKey, url); } + +/** + * UV 记录,上传至黄金令箭 + */ +export async function record(originParam: ILogParam) { + const goldlogParam: IGoldlogParam = { + ...originParam, + namespace: MAIN_KEY, + }; + + const url = getGoldlogUrl(goldlogParam.module); + + await recordUV(goldlogParam, store, recordKey, url); +} diff --git a/main/store/index.ts b/main/store/index.ts index 11c557b..95422c2 100644 --- a/main/store/index.ts +++ b/main/store/index.ts @@ -1,8 +1,8 @@ import { TOOLKIT_TMP_DIR } from '../constants'; + const Store = require('electron-store'); export const packagesDataKey = 'packagesData'; - export const recordKey = 'records'; const schema = { diff --git a/main/types/index.ts b/main/types/index.ts index d55cfea..e78e6c6 100644 --- a/main/types/index.ts +++ b/main/types/index.ts @@ -59,6 +59,7 @@ export interface INodeVersions { export interface INPMRegistry { name: string; registry: string; + recommended?: boolean; isInternal: boolean; } diff --git a/main/utils/ensureProfileExists.ts b/main/utils/ensureProfileExists.ts index d438835..8d9f956 100644 --- a/main/utils/ensureProfileExists.ts +++ b/main/utils/ensureProfileExists.ts @@ -1,8 +1,9 @@ import * as path from 'path'; import * as fse from 'fs-extra'; import * as shell from 'shelljs'; -import { PROFILE_FILES, DEFAULT_PROFILE_FILE } from '../constants'; +import { PROFILE_FILES, BASH_PROFILE_FILE_NAME, ZSHRC_FILE_NAME } from '../constants'; import log from './log'; +import getShellName from './getShellName'; /** * Ensure profile file exists. Otherwise, create ~/.bash_profile file by default. @@ -12,9 +13,10 @@ function ensureProfileExists() { return fse.pathExistsSync(path.join(process.env.HOME, bashConfigFile)); }); if (!isProfileExists) { - const defaultBashConfigFilePath = path.join(process.env.HOME, DEFAULT_PROFILE_FILE); - log.info(`${PROFILE_FILES.join(',')} were not found. Create ${defaultBashConfigFilePath}.`); - shell.touch(defaultBashConfigFilePath); + const shellName = getShellName(); + const profilePath = path.join(process.env.HOME, shellName === 'zsh' ? ZSHRC_FILE_NAME : BASH_PROFILE_FILE_NAME); + log.info(`${PROFILE_FILES.join(',')} were not found. Create ${profilePath}.`); + shell.touch(profilePath); } } diff --git a/main/utils/getNpmRegistry.ts b/main/utils/getNpmRegistry.ts index 0f5808e..0314352 100644 --- a/main/utils/getNpmRegistry.ts +++ b/main/utils/getNpmRegistry.ts @@ -1,9 +1,10 @@ -import { TAOBAO_NPM_REGISTRY, ALI_NPM_REGISTRY } from '../constants'; +import { ALI_NPM_REGISTRY } from '@appworks/constant'; +import { getCurrentRegistry } from '../npm/registry'; import checkIsAliInternal from './checkIsAliInternal'; async function getNpmRegistry() { const isAliInternal = await checkIsAliInternal(); - return isAliInternal ? ALI_NPM_REGISTRY : TAOBAO_NPM_REGISTRY; + return isAliInternal ? ALI_NPM_REGISTRY : await getCurrentRegistry(); } export default getNpmRegistry; diff --git a/main/utils/getShellName.ts b/main/utils/getShellName.ts new file mode 100644 index 0000000..7305054 --- /dev/null +++ b/main/utils/getShellName.ts @@ -0,0 +1,10 @@ +/** + * get current shell name. e.g. zsh/sh/bash + */ +function getShellName(): string { + const shellPath = process.env.SHELL; + const splitPaths = shellPath.split('/'); + return splitPaths[splitPaths.length - 1]; +} + +export default getShellName; diff --git a/main/utils/installCommandToPath.ts b/main/utils/installCommandToPath.ts index e85f38d..771dae1 100644 --- a/main/utils/installCommandToPath.ts +++ b/main/utils/installCommandToPath.ts @@ -38,7 +38,7 @@ async function installCommandToPath(target: string, command: string) { const linkSourceCommand = `osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'${target}\' \'${source}\'\\" with administrator privileges"`; execSync(linkSourceCommand); } catch (err) { - log.error(`Error: Unable to link ${target} to ${source}.`); + log.error(new Error(`Error: Unable to link ${target} to ${source}.`)); throw err; } } diff --git a/main/utils/log.ts b/main/utils/log.ts index 30c460f..b23a49e 100644 --- a/main/utils/log.ts +++ b/main/utils/log.ts @@ -1,5 +1,60 @@ +import * as util from 'util'; import * as log from 'electron-log'; +import fetch from 'node-fetch'; + +const { version } = require('../../package.json'); log.transports.console.format = '{y}-{m}-{d} {h}:{i}:{s} {text}'; +log.transports.file.maxSize = 30 * 1024 * 1024; + +// @ts-ignore ignore the level type is not found +log.transports.sls = ({ data, level }) => { + // SLS config 通过 HTTP GET 请求上传日志 + // 配置参照: https://help.aliyun.com/document_detail/31752.html + const error = level === 'error' ? data[0] : {}; + const message = level !== 'error' ? util.format(...data) : error.message; + + const project = 'appworks'; + const logstore = 'toolkit'; + const host = 'cn-hangzhou.log.aliyuncs.com'; + let url = `http://${project}.${host}/logstores/${logstore}/track?`; + + // log info + const body = { + error_name: error.name, + error_stack: error.stack, + error_data: JSON.stringify(error), + message, + __topic__: level, // log level + APIVersion: '0.6.0', // sls param + platform: `${process.platform}_${process.arch}`, + toolkit_version: version, + }; + + const dataKeyArray = Object.keys(body); + const paramsStr = dataKeyArray.reduce((finnalStr, currentKey, index) => { + const currentData = typeof body[currentKey] === 'string' + ? body[currentKey] + : JSON.stringify(body[currentKey]); + return `${finnalStr}${currentKey}=${currentData}${dataKeyArray.length - 1 === index ? '' : '&'}`; + }, ''); + + url += paramsStr; + + fetch(url, { timeout: 2000, method: 'GET' }) + // eslint-disable-next-line no-console + .catch((e: Error) => console.error(e.message)); +}; + +log.transports.sls.level = 'info'; + +// handle the uncaught error +process.on('uncaughtException', (error: Error) => { + log.error('uncaughtException', error); +}); +// handle the uncaught promise error +process.on('unhandledRejection', (error: Error) => { + log.error('unhandledRejection', error); +}); export default log; diff --git a/package.json b/package.json index 2467016..407c402 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "appworks-toolkit", "private": true, - "version": "0.2.0", + "version": "0.2.1", "description": "The Frontend Developer Toolkit", "scripts": { "setup": "npm run install:main && npm run install:renderer", @@ -77,6 +77,7 @@ } }, "dependencies": { + "@appworks/constant": "^0.1.3", "@appworks/recorder": "^1.0.0", "@shockpkg/hdi-mac": "^1.6.1", "adm-zip": "^0.5.5", @@ -91,11 +92,14 @@ "execa": "^5.0.0", "fs-extra": "^9.1.0", "globby": "^11.0.3", + "gulp": "^4.0.2", + "gulp-zip": "^5.1.0", "ice-npm-utils": "^2.1.1", "ini": "^2.0.0", "node-cache": "^5.1.2", "node-fetch": "^2.6.1", "package-json": "^7.0.0", + "shell-profile": "^1.0.3", "shelljs": "^0.8.4", "ssh-config": "^4.0.6", "ssh-keygen": "^0.5.0", @@ -107,6 +111,8 @@ "@commitlint/cli": "^12.1.1", "@iceworks/spec": "^1.0.0", "@types/decompress": "^4.2.3", + "@types/gulp": "^4.0.9", + "@types/gulp-zip": "^4.0.2", "@types/ini": "^1.3.30", "@types/node-fetch": "^2.5.10", "@types/shelljs": "^0.8.8", diff --git a/renderer/src/components/BalloonConfirm/index.tsx b/renderer/src/components/BalloonConfirm/index.tsx index e9307ec..2ec75ff 100644 --- a/renderer/src/components/BalloonConfirm/index.tsx +++ b/renderer/src/components/BalloonConfirm/index.tsx @@ -1,8 +1,8 @@ import { FC, useState } from 'react'; -import { Button, Balloon } from '@alifd/next'; +import { Button, Balloon, Icon } from '@alifd/next'; interface IBallonConfirm { - title: string; + title: string | React.ReactNode; onConfirm?: any; onCancel?: any; style?: { [k: string]: string }; @@ -29,7 +29,7 @@ const BallonConfirm: FC = ({ return ( {children}} style={style} visible={disable ? false : visible}> -
{title}
+
{title}
- } + } align="t" > 安装 @@ -63,6 +68,17 @@ function InstallNpmDependency() { ); }; + useEffect(() => { + if (effectsState.searchNpmDependencies.error) { + Message.error(effectsState.searchNpmDependencies.error.message); + } + }, [effectsState.searchNpmDependencies.error]); + + useEffect(() => { + if (effectsState.installGlobalNpmDependency.error) { + Message.error(effectsState.installGlobalNpmDependency.error.message); + } + }, [effectsState.installGlobalNpmDependency.error]); return (
{ +const InstallResult = ({ goBack }) => { const [state] = store.useModel('nodeVersion'); const { nodeInstallStatus, installResult, nodeInstallErrMsg } = state; @@ -15,7 +15,6 @@ const InstallResult = ({ goBack, reinstallGlobalDeps }) => { 安装结果 - {/* Node.js install result */} {nodeInstallStatus.installNode === 'success' ? successTag : errorTag}} @@ -43,22 +42,6 @@ const InstallResult = ({ goBack, reinstallGlobalDeps }) => { )} - {/* npm package install result */} - {reinstallGlobalDeps && ( - {nodeInstallStatus.reinstallDependencies === 'success' ? successTag : errorTag}} - > - {nodeInstallStatus.reinstallDependencies === 'error' && ( - <> -
重装全局依赖失败,请自行安装依赖。详细日志如下:
- - {nodeInstallErrMsg.reinstallDependencies} - - - )} -
- )}
+ + ); let mainbody: JSX.Element; @@ -131,20 +143,16 @@ const NodeInstaller: FC = ({ goBack }) => { } - - 重装全局依赖 - } closable={false}> - 安装一个新版本的 Node.js 后,原来全局 npm 包可能会不可用。 - 选择此选项会自动把原来的 npm 包适配到新版本的 Node.js 中。 - - } - required - requiredMessage="请选择是否重装全局依赖" - > - - + {!globalDependenciesInfo.exists && ( + + + + )} 下一步 @@ -154,15 +162,14 @@ const NodeInstaller: FC = ({ goBack }) => { ); break; case 1: - case 2: mainbody = (
); break; - case 3: - mainbody = ; + case 2: + mainbody = ; break; default: break; @@ -175,9 +182,8 @@ const NodeInstaller: FC = ({ goBack }) => { aria-current={index === currentStep ? 'step' : null} key={item.name} title={item.title} - disabled={index === 2 && !nodeInstallFormValue.reinstallGlobalDeps} icon={ - ((index === 1 || index === 2) && currentStep === index) ? STEP_STATUS_ICON[nodeInstallStatus[item.name]] : undefined + (index === 1 && currentStep === index) ? STEP_STATUS_ICON[nodeInstallStatus[item.name]] : undefined } /> ), @@ -215,6 +221,16 @@ const NodeInstaller: FC = ({ goBack }) => { goNext(); } + useEffect(() => { + npmDependencyDispatchers.getGlobalDependenciesInfo(); + }, []); + + useEffect(() => { + if (npmDependencyEffectsState.getGlobalDependenciesInfo.error) { + Message.error(npmDependencyEffectsState.getGlobalDependenciesInfo.error.message); + } + }, [npmDependencyEffectsState.getGlobalDependenciesInfo.error]); + useEffect(() => { ipcRenderer.on(nodeInstallProcessStatusChannel, handleUpdateInstallStatus); return () => { @@ -224,6 +240,7 @@ const NodeInstaller: FC = ({ goBack }) => { ); }; }, []); + return (
@@ -232,6 +249,7 @@ const NodeInstaller: FC = ({ goBack }) => { {mainbody} +
); }; diff --git a/renderer/src/pages/Node/components/NpmDependency/index.module.scss b/renderer/src/pages/Node/components/NpmDependency/index.module.scss index 23882d1..bf72526 100644 --- a/renderer/src/pages/Node/components/NpmDependency/index.module.scss +++ b/renderer/src/pages/Node/components/NpmDependency/index.module.scss @@ -5,7 +5,7 @@ } .title { - font-size: 18px; + font-size: 20px; font-weight: 500; } diff --git a/renderer/src/pages/Node/components/NpmRegistry/index.module.scss b/renderer/src/pages/Node/components/NpmRegistry/index.module.scss index 1ed977a..6136e8f 100644 --- a/renderer/src/pages/Node/components/NpmRegistry/index.module.scss +++ b/renderer/src/pages/Node/components/NpmRegistry/index.module.scss @@ -12,3 +12,18 @@ min-width: 100%; } } + +.selectItemRender { + display: flex; + align-items: center; + + img { + margin-left: 20px; + } +} + + +.promptIcon { + color: #f9ca24; + margin-left: 4px; +} \ No newline at end of file diff --git a/renderer/src/pages/Node/components/NpmRegistry/index.tsx b/renderer/src/pages/Node/components/NpmRegistry/index.tsx index c51da96..4e75d88 100644 --- a/renderer/src/pages/Node/components/NpmRegistry/index.tsx +++ b/renderer/src/pages/Node/components/NpmRegistry/index.tsx @@ -1,16 +1,21 @@ import { useEffect } from 'react'; -import { Grid, Select, Message } from '@alifd/next'; +import { Grid, Select, Message, Icon, Balloon } from '@alifd/next'; import store from '../../store'; import styles from './index.module.scss'; const { Row, Col } = Grid; +const { Tooltip } = Balloon; function NpmRegistry() { const [state, dispatchers] = store.useModel('npmRegistry'); - const { npmInstalled, currentNpmRegistry, allNpmRegistries } = state; + const { npmInstalled, currentNpmRegistry, allNpmRegistries, isAliInternal } = state; const effectsState = store.useModelEffectsState('npmRegistry'); const effectsErrors = store.useModelEffectsError('npmRegistry'); + useEffect(() => { + dispatchers.checkIsAliInternal(); + }, []); + useEffect(() => { dispatchers.checkNpmInstalled(); }, []); @@ -50,10 +55,37 @@ function NpmRegistry() { Message.error(effectsErrors.setCurrentNpmRegistry.error.message); } }, [effectsErrors.setCurrentNpmRegistry.error]); + + const selectItemRender = (item) => { + return ( +
+ {item.label} + <> + {item.recommended && + recommendIcon + } + +
+ ); + }; + + if (isAliInternal) { + return null; + } + return ( -
npm 镜像源
+
+ 全局 npm 镜像源 + } + align="t" + delay={200} + > + 如果有发 npm 包的需求,请先切换至 npm 官方镜像源。 + +