sync-code-editor
Advanced tools
+18
-16
| #!/usr/bin/env node | ||
| const chalk = require('chalk').default | ||
| const ora = require('ora').default | ||
| const chalk = require('chalk').default; | ||
| const ora = require('ora').default; | ||
| const process = require('node:process'); | ||
| const { detectInstalledEditors, getEditorList } = require('../lib/editors'); | ||
| const { selectSourceEditor, selectConfigurationList, selectTargetEditors, confirmSync } = require('../lib/prompts'); | ||
| const { syncConfigs, printSyncResults } = require('../lib/sync'); | ||
| const { detectInstalledEditors, getEditorList } = require('../lib/editors'); | ||
| const { renderLink } = require('../lib/utils'); | ||
| const { renderLink, printf } = require('../lib/utils'); | ||
| async function main() { | ||
| try { | ||
| console.log(chalk.blue('★★ 欢迎使用编辑器配置同步工具 ★★\n')); | ||
| printf(chalk.blue('★★ 欢迎使用编辑器配置同步工具 ★★\n')); | ||
@@ -20,6 +21,6 @@ // 检测已安装的编辑器 | ||
| if (installedEditors.length === 0) { | ||
| console.log(chalk.red('✗ 未检测到任何已安装的编辑器。')); | ||
| console.log('请确保至少已安装以下编辑器中的任意两个:'); | ||
| getEditorList().forEach(editor => { | ||
| console.log(' - ' + renderLink(editor.homepage, editor.name)); | ||
| printf(chalk.red('✗ 未检测到任何已安装的编辑器。')); | ||
| printf('请确保至少已安装以下编辑器中的任意两个:'); | ||
| getEditorList().forEach((editor) => { | ||
| printf(` - ${renderLink(editor.homepage, editor.name)}`); | ||
| }); | ||
@@ -30,3 +31,3 @@ process.exit(1); | ||
| if (installedEditors.length === 1) { | ||
| console.log(chalk.yellow(`✗ 您只安装了 ${installedEditors[0].name} ,不需要进行同步操作。`)); | ||
| printf(chalk.yellow(`✗ 您只安装了 ${installedEditors[0].name} ,不需要进行同步操作。`)); | ||
| process.exit(1); | ||
@@ -50,7 +51,7 @@ } | ||
| configurationKeys, | ||
| targetEditorKeys | ||
| targetEditorKeys, | ||
| ); | ||
| if (!confirm) { | ||
| console.log(chalk.yellow('已取消同步操作。')); | ||
| printf(chalk.yellow('已取消同步操作。')); | ||
| process.exit(0); | ||
@@ -66,6 +67,7 @@ } | ||
| printSyncResults(result); | ||
| } catch (error) { | ||
| console.log(chalk.red('\n✗ 发生错误:'), error.message); | ||
| } | ||
| catch (error) { | ||
| printf(chalk.red('\n✗ 发生错误:'), error.message); | ||
| if (error.stack) { | ||
| console.log(chalk.red('\n错误堆栈:'), error.stack); | ||
| printf(chalk.red('\n错误堆栈:'), error.stack); | ||
| } | ||
@@ -76,2 +78,2 @@ process.exit(1); | ||
| main(); | ||
| main(); |
@@ -1,2 +0,2 @@ | ||
| const path = require('path'); | ||
| const path = require('node:path'); | ||
| const { getEditorInfo } = require('./editors'); | ||
@@ -8,21 +8,12 @@ | ||
| name: '编辑器设置 (settings.json)', | ||
| files: [{ type: 'file', path: 'settings.json' }] | ||
| files: [{ type: 'file', path: 'settings.json' }], | ||
| }, | ||
| keybindings: { | ||
| name: '快捷键绑定 (keybindings.json)', | ||
| files: [{ type: 'file', path: 'keybindings.json' }] | ||
| files: [{ type: 'file', path: 'keybindings.json' }], | ||
| }, | ||
| snippets: { | ||
| name: '代码片段 (snippets/*)', | ||
| files: [{ type: 'dir', path: 'snippets' }] | ||
| files: [{ type: 'dir', path: 'snippets' }], | ||
| }, | ||
| 'project-manager': { | ||
| name: '项目管理器配置', | ||
| files: [ | ||
| { | ||
| type: 'file', | ||
| path: path.join('globalStorage', 'alefragnani.project-manager', 'projects.json') | ||
| } | ||
| ] | ||
| } | ||
| }; | ||
@@ -32,3 +23,3 @@ | ||
| * 获取所有可用的同步项列表 | ||
| * @returns {Array<{value: string, name: string}>} | ||
| * @returns {Array<{value: string, name: string}>} | ||
| */ | ||
@@ -76,2 +67,2 @@ function getConfigurationList() { | ||
| getConfigurationFileFullPath, | ||
| }; | ||
| }; |
+13
-14
@@ -6,21 +6,21 @@ const fs = require('fs-extra'); | ||
| const editorMap = { | ||
| vscode: { | ||
| 'vscode': { | ||
| name: 'Visual Studio Code', | ||
| path: getAppDataPathWithCache('Code'), | ||
| homepage: 'https://code.visualstudio.com/' | ||
| homepage: 'https://code.visualstudio.com/', | ||
| }, | ||
| cursor: { | ||
| 'cursor': { | ||
| name: 'Cursor', | ||
| path: getAppDataPathWithCache('Cursor'), | ||
| homepage: 'https://cursor.com/' | ||
| homepage: 'https://cursor.com/', | ||
| }, | ||
| windsurf: { | ||
| 'windsurf': { | ||
| name: 'Windsurf', | ||
| path: getAppDataPathWithCache('Windsurf'), | ||
| homepage: 'https://windsurf.com/' | ||
| homepage: 'https://windsurf.com/', | ||
| }, | ||
| trae: { | ||
| 'trae': { | ||
| name: 'Trae', | ||
| path: getAppDataPathWithCache('Trae'), | ||
| homepage: 'https://www.trae.ai/' | ||
| homepage: 'https://www.trae.ai/', | ||
| }, | ||
@@ -30,4 +30,4 @@ 'trae-cn': { | ||
| path: getAppDataPathWithCache('Trae CN'), | ||
| homepage: 'https://www.trae.cn/' | ||
| } | ||
| homepage: 'https://www.trae.cn/', | ||
| }, | ||
| }; | ||
@@ -37,3 +37,3 @@ | ||
| * 检测已安装的编辑器 | ||
| * @returns {Promise<Array<import('../types').Editor>>} 已安装的编辑器列表 | ||
| * @returns {Promise<Array<import('../types').Editor>>} 已安装的编辑器列表 | ||
| */ | ||
@@ -79,3 +79,3 @@ async function detectInstalledEditors() { | ||
| function getEditorList() { | ||
| return Object.keys(editorMap).map(key => { | ||
| return Object.keys(editorMap).map((key) => { | ||
| const editor = getEditorInfo(key); | ||
@@ -86,3 +86,2 @@ return { key, ...editor }; | ||
| module.exports = { | ||
@@ -92,2 +91,2 @@ getEditorInfo, | ||
| detectInstalledEditors, | ||
| }; | ||
| }; |
+13
-13
| const inquirer = require('inquirer').default; | ||
| const { getEditorInfo, } = require('./editors'); | ||
| const { getConfigurationList, getConfigurationInfo } = require('./configurations'); | ||
| const { getEditorInfo } = require('./editors'); | ||
@@ -23,4 +23,4 @@ /** | ||
| name: editor.name, | ||
| })) | ||
| } | ||
| })), | ||
| }, | ||
| ]); | ||
@@ -49,4 +49,4 @@ | ||
| return true; | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| ]); | ||
@@ -66,4 +66,4 @@ | ||
| const sourceEditorName = getEditorInfo(sourceEditorKey, 'name'); | ||
| const configurationNames = configurationKeys.map(item => ' - ' + getConfigurationInfo(item, 'name')).join('\n'); | ||
| const targetEditorNames = targetEditorKeys.map(editor => ' - ' + getEditorInfo(editor, 'name')).join('\n'); | ||
| const configurationNames = configurationKeys.map(item => ` - ${getConfigurationInfo(item, 'name')}`).join('\n'); | ||
| const targetEditorNames = targetEditorKeys.map(editor => ` - ${getEditorInfo(editor, 'name')}`).join('\n'); | ||
@@ -75,4 +75,4 @@ const { confirmed } = await inquirer.prompt([ | ||
| message: `确认要将 ${sourceEditorName} 中的:\n${configurationNames} \n 同步到\n${targetEditorNames}\n`, | ||
| default: true | ||
| } | ||
| default: true, | ||
| }, | ||
| ]); | ||
@@ -111,4 +111,4 @@ | ||
| return true; | ||
| } | ||
| } | ||
| }, | ||
| }, | ||
| ]); | ||
@@ -123,3 +123,3 @@ | ||
| selectTargetEditors, | ||
| confirmSync | ||
| }; | ||
| confirmSync, | ||
| }; |
+39
-36
@@ -0,6 +1,7 @@ | ||
| const path = require('node:path'); | ||
| const fs = require('fs-extra'); | ||
| const path = require('path'); | ||
| const chalk = require('chalk').default; | ||
| const { getEditorInfo, } = require('./editors'); | ||
| const { getConfigurationInfo, getConfigurationFileFullPath } = require('./configurations'); | ||
| const { getConfigurationInfo, getConfigurationFileFullPath } = require('./configurations'); | ||
| const { getEditorInfo } = require('./editors'); | ||
| const { printf } = require('./utils'); | ||
@@ -17,5 +18,5 @@ /** | ||
| const configurationInfo = getConfigurationInfo(configurationKey); | ||
| const sourceEditorConfigurationFiles = configurationInfo.files.map((file) => ({ | ||
| const sourceEditorConfigurationFiles = configurationInfo.files.map(file => ({ | ||
| ...file, | ||
| path: getConfigurationFileFullPath(sourceEditorKey, file.path) | ||
| path: getConfigurationFileFullPath(sourceEditorKey, file.path), | ||
| })); | ||
@@ -26,6 +27,6 @@ | ||
| let promises = sourceEditorConfigurationFiles.map((config) => { | ||
| const promises = sourceEditorConfigurationFiles.map((config) => { | ||
| const filePath = config.path; | ||
| return fs.pathExists(filePath) | ||
| .then(exists => { | ||
| .then((exists) => { | ||
| if (!exists) { | ||
@@ -39,6 +40,6 @@ missingFiles.push(filePath); | ||
| return false; | ||
| }) | ||
| }) | ||
| }); | ||
| }); | ||
| await Promise.all(promises) | ||
| await Promise.all(promises); | ||
@@ -48,4 +49,4 @@ if (missingFiles.length > 0 || errorFiles.length > 0) { | ||
| success: false, | ||
| reason: { missingFiles, errorFiles } | ||
| } | ||
| reason: { missingFiles, errorFiles }, | ||
| }; | ||
| } | ||
@@ -59,4 +60,4 @@ | ||
| from: sourceEditorConfigurationFiles[index].path, | ||
| to: getConfigurationFileFullPath(targetEditorKey, config.path) | ||
| } | ||
| to: getConfigurationFileFullPath(targetEditorKey, config.path), | ||
| }; | ||
| }); | ||
@@ -74,12 +75,13 @@ | ||
| await fs.copy(task.from, task.to); | ||
| } catch (error) { | ||
| } | ||
| catch { | ||
| errorFiles.push(task.from); | ||
| return { | ||
| success: false, | ||
| reason: { errorFiles } | ||
| } | ||
| reason: { errorFiles }, | ||
| }; | ||
| } | ||
| } | ||
| return { success: true } | ||
| return { success: true }; | ||
| } | ||
@@ -93,3 +95,3 @@ | ||
| * @param {Array<import('../types').EditorKeys>} targetEditorKeys - 目标编辑器键名数组 | ||
| * @returns {Promise<import('../types').SyncResult>} 同步结果 | ||
| * @returns {Promise<import('../types').SyncResult>} 同步结果 | ||
| */ | ||
@@ -112,3 +114,2 @@ async function syncConfigs(sourceEditorKey, configurationKeys, installedEditors, targetEditorKeys) { | ||
| for (const targetEditor of targetEditors) { | ||
| const targetEditorName = targetEditor.name; | ||
@@ -123,9 +124,10 @@ const targetEditorKey = targetEditor.key; | ||
| configurationName, | ||
| ...result | ||
| ...result, | ||
| }); | ||
| } catch (error) { | ||
| } | ||
| catch (error) { | ||
| results[targetEditorName].push({ | ||
| configurationName, | ||
| success: false, | ||
| message: error.message || `${error}` | ||
| message: error.message || `${error}`, | ||
| }); | ||
@@ -143,3 +145,3 @@ } | ||
| function printSyncResults(syncResult) { | ||
| console.log('\n同步结果:'); | ||
| printf('\n同步结果:'); | ||
@@ -157,12 +159,13 @@ // const list = Object.values(syncResult).flatMap(results => results); | ||
| targetEditorName, | ||
| results: results.sort((a, b) => b.success - a.success) | ||
| } | ||
| results: results.sort((a, b) => b.success - a.success), | ||
| }; | ||
| }).forEach(({ targetEditorName, results }) => { | ||
| console.log(`\n${targetEditorName}:`); | ||
| printf(`\n${targetEditorName}:`); | ||
| results.forEach((result) => { | ||
| if (result.success) { | ||
| console.log(chalk.green(` ✓ ${result.configurationName}`)); | ||
| printf(chalk.green(` ✓ ${result.configurationName}`)); | ||
| successCount++; | ||
| } else if (!result.success) { | ||
| console.log(chalk.red(` ✗ ${result.configurationName}: ${result.message}`)); | ||
| } | ||
| else if (!result.success) { | ||
| printf(chalk.red(` ✗ ${result.configurationName}: ${result.message}`)); | ||
| errorCount++; | ||
@@ -172,8 +175,8 @@ } | ||
| // TODO | ||
| console.log(chalk.yellow(` ⊘ ${result.message}`)); | ||
| printf(chalk.yellow(` ⊘ ${result.message}`)); | ||
| } | ||
| }) | ||
| }) | ||
| }); | ||
| }); | ||
| console.log(chalk.blue(`\n总计: ${successCount} 成功, ${errorCount} 失败\n`)); | ||
| printf(chalk.blue(`\n总计: ${successCount} 成功, ${errorCount} 失败\n`)); | ||
| } | ||
@@ -183,3 +186,3 @@ | ||
| syncConfigs, | ||
| printSyncResults | ||
| }; | ||
| printSyncResults, | ||
| }; |
+16
-7
@@ -1,4 +0,5 @@ | ||
| const path = require('path'); | ||
| const os = require('node:os'); | ||
| const path = require('node:path'); | ||
| const process = require('node:process'); | ||
| const fs = require('fs-extra'); | ||
| const os = require('os'); | ||
@@ -28,7 +29,9 @@ const platform = os.platform(); | ||
| appDataPath = path.join(homeDir, 'Library', 'Application Support', appPathName, 'User'); | ||
| } else if (platform === 'win32') { | ||
| } | ||
| else if (platform === 'win32') { | ||
| // Windows | ||
| const appDataDir = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'); | ||
| appDataPath = path.join(appDataDir, appPathName, 'User'); | ||
| } else { | ||
| } | ||
| else { | ||
| // Linux | ||
@@ -47,3 +50,3 @@ appDataPath = path.join(homeDir, '.config', appPathName, 'User'); | ||
| return getAppDataPathWithCache | ||
| return getAppDataPathWithCache; | ||
| } | ||
@@ -57,5 +60,11 @@ | ||
| function printf(...args) { | ||
| // eslint-disable-next-line no-console | ||
| console.log(...args); | ||
| } | ||
| module.exports = { | ||
| getAppDataPathWithCache, | ||
| renderLink | ||
| } | ||
| renderLink, | ||
| printf, | ||
| }; |
+24
-12
| { | ||
| "name": "sync-code-editor", | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "description": "同步您的设备上所有以 Visual Studio Code 为内核的编辑器配置", | ||
| "author": "onlymisaky@gmail.com", | ||
| "license": "ISC", | ||
| "homepage": "https://github.com/onlymisaky/sync-code-editor", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/onlymisaky/sync-code-editor.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/onlymisaky/sync-code-editor/issues" | ||
| }, | ||
| "main": "index.js", | ||
@@ -8,5 +19,10 @@ "bin": { | ||
| }, | ||
| "author": "onlymisaky@gmail.com", | ||
| "license": "ISC", | ||
| "description": "同步您的设备上所有以 Visual Studio Code 为内核的编辑器配置", | ||
| "files": [ | ||
| "bin", | ||
| "lib" | ||
| ], | ||
| "scripts": { | ||
| "lint": "eslint .", | ||
| "lint:fix": "eslint . --fix" | ||
| }, | ||
| "dependencies": { | ||
@@ -18,10 +34,6 @@ "chalk": "^5.6.2", | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/onlymisaky/sync-code-editor.git" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/onlymisaky/sync-code-editor/issues" | ||
| }, | ||
| "homepage": "https://github.com/onlymisaky/sync-code-editor" | ||
| "devDependencies": { | ||
| "@antfu/eslint-config": "^6.2.0", | ||
| "eslint": "^9.39.1" | ||
| } | ||
| } |
| name: Publish NPM Package | ||
| permissions: | ||
| id-token: write | ||
| contents: write | ||
| on: | ||
| push: | ||
| branches: | ||
| - master | ||
| jobs: | ||
| publish: | ||
| # if: "${{ startsWith(github.event.head_commit.message, 'chore: release v') }}" | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
| registry-url: https://registry.npmjs.org/ | ||
| - run: npm publish -r --access public | ||
| env: | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
| NPM_CONFIG_PROVENANCE: true |
-41
| export type EditorKeys = 'vscode' | 'cursor' | 'windsurf' | 'trae' | 'tran-cn'; | ||
| export interface Editor { | ||
| key: EditorKeys; | ||
| /** 编辑器显示名称 */ | ||
| name: string; | ||
| /** 编辑器文件路径 */ | ||
| path: string; | ||
| /** 编辑器首页 URL */ | ||
| homepage: string; | ||
| } | ||
| export type EditorMap = Record<EditorKeys, Omit<Editor, 'key'>>; | ||
| export type ConfigurationKeys = 'settings' | 'keybindings' | 'snippets' | 'project-manager'; | ||
| export interface Configuration { | ||
| key: ConfigurationKeys; | ||
| /** 同步项显示名称 */ | ||
| name: string; | ||
| /** 要同步的内容数组,每个元素为文件路径或目录路径 */ | ||
| files: { type: 'file' | 'dir'; path: string }[]; | ||
| } | ||
| export type ConfigurationMap = Record<ConfigurationKeys, Omit<Configuration, 'key'>>; | ||
| export interface SyncResultItem { | ||
| reason?: { errorFiles?: string[]; missingFiles?: string[] }; | ||
| configurationName?: string; | ||
| success: boolean; | ||
| message?: string; | ||
| } | ||
| export interface SyncResult { | ||
| [editorName: string]: Array<SyncResultItem>; | ||
| } |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
19953
-6.4%2
Infinity%8
-20%507
-3.43%