import type { checkIDEResultDto, checkIDEsResultDto, checkIDEsVersionDto, checkIDEVersionDto, IDECode } from '@my-type/settings' import { execSync } from 'node:child_process' import path from 'path' import { loadJsonFile } from 'load-json-file' import os from 'os' import { JetBrainsIDEDisplayNameEnum as JIN, JetBrainsProductCode, JetBrainsStateDto, toProductDisplayName } from '@my-type/jetbrains-state-tools' import { settingsManager } from '../settings' import { isNodeError } from '@my-type/node-error' import { JetBrainsDataProductDto } from '@my-type/jetbrains-data-products' const JETBRAINS_TOOLBOX_STATE_JSON_PATH = path.join( os.homedir(), 'AppData/Local/JetBrains/Toolbox/state.json' ) /** * 通过 where.exe 检查系统中的指定 IDE 是否安装可用。 * @param display IDE 的正式显示名称,例如 `Visual Studio Code`。 * @param command IDE 的命令行别名,例如 `code`。 * @param code (JetBrains IDE)的产品代号;对于其他 IDE 为空字符串 */ function checkIDE(display: string, command: string, code: IDECode): checkIDEResultDto | null { try { const paths = execSync(`where.exe ${command}`).toString().split('\n').slice(0, -1) return { code, display: display, command: command, paths: paths } } catch { return null } } /** * 检查 JetBrains IDEs 是否安装可用。 */ async function checkJetBrainsIDEs(): Promise { // 构建数据结构的辅助函数 const _ = (command: string, code: JetBrainsProductCode): checkIDEResultDto => { return { code, command, display: JIN[command], paths: [] } } const result: checkIDEsResultDto = { PY: _('pycharm', 'PY'), CL: _('clion', 'CL'), WS: _('webstorm', 'WS'), PS: _('phpstorm', 'PS'), IU: _('idea', 'IU') } // 优先从 JBTState.json 查找 if (settingsManager._settings?.codeLaunchpadIDESearchPolicy.includes('JBTState.json')) { // 处理文件不存在,即 JetBrains Toolbox 相关问题 try { const data: JetBrainsStateDto = await loadJsonFile(JETBRAINS_TOOLBOX_STATE_JSON_PATH) for (const ide of data.tools) { for (const resultElement of Object.values(result)) { if (resultElement?.code === ide.productCode) { resultElement.paths = [ide.launchCommand] } } } } catch (error) { if (isNodeError(error)) { if (error.code === 'ENOENT') { console.log('由于 JBTState.json 不存在,回退到 where.exe。') } } else { console.error(error) } } } // 从 where.exe 查找 // 此分支确保在设置中启用 where.exe 方式,以及 JBTState.json 方式没有结果(未找到任何,或出现错误)时进入 if ( settingsManager._settings?.codeLaunchpadIDESearchPolicy.includes('where.exe') && Object.entries(result).length === 0 ) { result['clion'] = checkIDE(JIN.clion, 'clion', 'CL') result['pycharm'] = checkIDE(JIN.pycharm, 'pycharm', 'PY') result['webstorm'] = checkIDE(JIN.webstorm, 'webstorm', 'WS') result['phpstorm'] = checkIDE(JIN.phpstorm, 'phpstorm', 'PS') result['idea'] = checkIDE(JIN.idea, 'idea', 'IU') } return result } /** * 获取系统中的各 IDE 是否安装可用。 */ export async function getIDEs(): Promise { return global.installedIDEs === null ? await checkIDEs() : global.installedIDEs } /** * 检查系统中的各 IDE 是否安装可用。 * 原理是运行 `where.exe ${command}` 并检查返回值。 */ export async function checkIDEs(): Promise { console.log('在系统中查找已安装的 IDE ...') const vscodeIDEs = { VSC: checkIDE('Visual Studio Code', 'code', 'VSC') } global.installedIDEs = { ...vscodeIDEs, ...(await checkJetBrainsIDEs()) } return global.installedIDEs } /** * 检查 VSCode 的版本信息,通过 code --version。 */ async function checkVSCodeVersion(): Promise { const install = execSync('code --version').toString().split('\n')[0] let latest = 'unknown' try { latest = await fetch('https://update.code.visualstudio.com/api/releases/stable') .then((res) => res.json()) .then((data) => data[0]) } catch (error) { console.error('获取 VSCode 版本列表时出现错误。错误提供如下。') console.error(error) } return { code: 'VSC', install, latest, display: 'Visual Studio Code' } } /** * 检查 JetBrains IDE 的版本信息 */ export async function checkJetBrainsIDEsVersion(): Promise { // 构建数据结构的辅助函数 const _ = (code: JetBrainsProductCode): checkIDEVersionDto => { return { code, display: toProductDisplayName(code) as string, install: 'unknown', latest: 'unknown' } } const result: checkIDEsVersionDto = { PY: _('PY'), CL: _('CL'), WS: _('WS'), PS: _('PS'), IU: _('IU') } // 尝试从 JBTState.json 获取已安装的 JetBrains IDEs 的版本 // TODO:添加更多获取 JetBrains IDEs 已安装版本的方案 try { const data: JetBrainsStateDto = await loadJsonFile(JETBRAINS_TOOLBOX_STATE_JSON_PATH) for (const tool of data.tools) { for (const resultElement of Object.values(result)) { if (resultElement?.code === tool.productCode) { resultElement.install = tool.displayVersion } } } } catch (error) { if (isNodeError(error)) { if (error.code === 'ENOENT') { console.log('由于 JBTState.json 不存在,检查 JetBrains IDEs 已安装版本的任务失败。') } } else { console.error(error) } } // 从命令行获取 JetBrains IDE 的已安装版本的方案仍然在科研中 // 所以这个方法就暂时先结束了~ // 从网络接口获取 JetBrains IDEs 的版本列表 const codes: JetBrainsProductCode[] = [] for (const resultElement of Object.values(result)) { codes.push(resultElement?.code) } try { const params = new URLSearchParams({ type: 'release', code: codes.join(',') }) const data: JetBrainsDataProductDto[] = await fetch( `https://data.services.jetbrains.com/products?${params}` ).then((res) => res.json()) for (const datum of data) { for (const resultElement of Object.values(result)) { if (resultElement?.code === datum.intellijProductCode) { resultElement.latest = datum.releases[0].version } } } } catch (error) { console.error('从 JetBrains IDEs 版本列表接口获取数据失败。错误提供如下。') console.error(error) } return result } /** * 获取各 IDE 的版本信息。 */ export async function getIDEsVersion(): Promise { return global.installedIDEsVersion === null ? await checkIDEsVersion() : global.installedIDEsVersion } /** * 检查各 IDE 的版本信息。 */ export async function checkIDEsVersion(): Promise { let result: checkIDEsVersionDto = {} for (const ide in await getIDEs()) { switch (ide) { case 'VSC': result['VSC'] = await checkVSCodeVersion() break } } result = { ...result, ...(await checkJetBrainsIDEsVersion()) } global.installedIDEsVersion = result return result }