editor-profile-sync 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@ You can sync:
9
9
  - extensions
10
10
  - snippets
11
11
  - `settings.json`
12
+ - `keybindings.json`
12
13
 
13
14
  ## Requirements
14
15
 
@@ -67,8 +68,9 @@ npm install
67
68
  2. Choose a source editor.
68
69
  3. Choose what to share:
69
70
  - `Extensions`
70
- - `settings.json`
71
71
  - `Snippets`
72
+ - `settings.json`
73
+ - `keybindings.json`
72
74
  4. Choose mode(s) for selected item types:
73
75
  - Extensions:
74
76
  - Install on top of existing (additive)
@@ -123,6 +125,48 @@ Result:
123
125
  }
124
126
  ```
125
127
 
128
+ ### Keybindings merge behavior
129
+
130
+ For `keybindings.json`, keybindings are merged intelligently:
131
+
132
+ - Source keybindings override target ones with the same `key` + `command` combination
133
+ - Existing target keybindings that don't conflict are preserved
134
+ - New source keybindings are added to the target
135
+
136
+ Example:
137
+
138
+ Target:
139
+
140
+ ```json
141
+ [{ "key": "ctrl+k", "command": "workbench.action.terminal.clear" }]
142
+ ```
143
+
144
+ Source:
145
+
146
+ ```json
147
+ [
148
+ {
149
+ "key": "ctrl+k",
150
+ "command": "workbench.action.terminal.clear",
151
+ "when": "terminalFocus"
152
+ },
153
+ { "key": "ctrl+shift+p", "command": "workbench.action.showCommands" }
154
+ ]
155
+ ```
156
+
157
+ Result:
158
+
159
+ ```json
160
+ [
161
+ {
162
+ "key": "ctrl+k",
163
+ "command": "workbench.action.terminal.clear",
164
+ "when": "terminalFocus"
165
+ },
166
+ { "key": "ctrl+shift+p", "command": "workbench.action.showCommands" }
167
+ ]
168
+ ```
169
+
126
170
  ## Supported editors
127
171
 
128
172
  These editors are currently supported:
package/index.js CHANGED
@@ -14,9 +14,17 @@ import {
14
14
  } from "./lib/constants.js";
15
15
  import { isEditorInstalled } from "./lib/editor-cli.js";
16
16
  import { exportExtensions, syncExtensions } from "./lib/extensions-sync.js";
17
- import { getSettingsPath, getSnippetsPath } from "./lib/profile-paths.js";
17
+ import {
18
+ getSettingsPath,
19
+ getSnippetsPath,
20
+ getKeybindingsPath,
21
+ } from "./lib/profile-paths.js";
18
22
  import { readSourceSettings, syncSettings } from "./lib/settings-sync.js";
19
23
  import { syncSnippets } from "./lib/snippets-sync.js";
24
+ import {
25
+ readSourceKeybindings,
26
+ syncKeybindings,
27
+ } from "./lib/keybindings-sync.js";
20
28
  import {
21
29
  promptExtensionMode,
22
30
  promptSnippetMode,
@@ -91,7 +99,7 @@ async function main() {
91
99
  }
92
100
 
93
101
  detectSpinner.succeed(
94
- `Found ${availableEditors.length} editor(s): ${availableEditors
102
+ `Found ${availableEditors.length} editor${availableEditors.length === 1 ? "" : "s"}: ${availableEditors
95
103
  .map((e) => e.name)
96
104
  .join(", ")}`,
97
105
  );
@@ -165,6 +173,18 @@ async function main() {
165
173
  }
166
174
  }
167
175
 
176
+ let sourceKeybindings = [];
177
+ if (syncItems.includes("keybindings")) {
178
+ try {
179
+ sourceKeybindings = readSourceKeybindings(
180
+ getKeybindingsPath(sourceEditor),
181
+ );
182
+ } catch (err) {
183
+ console.error(` ${err.message}\n`);
184
+ process.exit(1);
185
+ }
186
+ }
187
+
168
188
  console.log("");
169
189
 
170
190
  for (const editor of targetEditors) {
@@ -209,6 +229,24 @@ async function main() {
209
229
  }
210
230
  }
211
231
 
232
+ if (syncItems.includes("keybindings")) {
233
+ const spinner = ora({
234
+ text: "Syncing keybindings.json...",
235
+ color: "cyan",
236
+ }).start();
237
+ try {
238
+ const targetPath = getKeybindingsPath(editor, {
239
+ createIfMissing: true,
240
+ });
241
+ const merged = syncKeybindings(sourceKeybindings, targetPath);
242
+ spinner.succeed(
243
+ `keybindings.json synced (${merged.length} binding${merged.length === 1 ? "" : "s"})`,
244
+ );
245
+ } catch (err) {
246
+ spinner.fail(`keybindings sync failed (${err.message})`);
247
+ }
248
+ }
249
+
212
250
  if (syncItems.includes("extensions")) {
213
251
  const spinner = ora({
214
252
  text: "Installing extensions...",
package/lib/constants.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export const SETTINGS_FILE = "settings.json";
2
2
  export const SNIPPETS_DIR = "snippets";
3
+ export const KEYBINDINGS_FILE = "keybindings.json";
3
4
 
4
5
  // Sorted alphabetically by display name.
5
6
  export const EDITORS = [
@@ -43,15 +44,20 @@ export const SYNC_ITEMS = [
43
44
  name: "Extensions",
44
45
  short: "Extensions",
45
46
  },
47
+ {
48
+ value: "snippets",
49
+ name: "Snippets",
50
+ short: "Snippets",
51
+ },
46
52
  {
47
53
  value: "settings",
48
54
  name: "settings.json",
49
55
  short: "settings.json",
50
56
  },
51
57
  {
52
- value: "snippets",
53
- name: "Snippets",
54
- short: "Snippets",
58
+ value: "keybindings",
59
+ name: "keybindings.json",
60
+ short: "keybindings.json",
55
61
  },
56
62
  ];
57
63
 
@@ -0,0 +1,57 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2
+ import { parse, printParseErrorCode } from "jsonc-parser";
3
+
4
+ export function readSourceKeybindings(keybindingsPath) {
5
+ if (!existsSync(keybindingsPath)) {
6
+ throw new Error(`Source keybindings.json not found: ${keybindingsPath}`);
7
+ }
8
+
9
+ const raw = readFileSync(keybindingsPath, "utf-8");
10
+ const errors = [];
11
+ const parsed = parse(raw, errors, { allowTrailingComma: true });
12
+
13
+ if (errors.length > 0) {
14
+ const firstError = errors[0];
15
+ throw new Error(
16
+ `Invalid JSON in source keybindings.json: ${printParseErrorCode(firstError.error)} at offset ${firstError.offset}`,
17
+ );
18
+ }
19
+
20
+ if (!Array.isArray(parsed)) {
21
+ throw new Error("Source keybindings.json must be an array");
22
+ }
23
+
24
+ return parsed;
25
+ }
26
+
27
+ export function syncKeybindings(sourceKeybindings, targetPath) {
28
+ let targetKeybindings = [];
29
+
30
+ if (existsSync(targetPath)) {
31
+ const raw = readFileSync(targetPath, "utf-8");
32
+ const errors = [];
33
+ const parsed = parse(raw, errors, { allowTrailingComma: true });
34
+
35
+ if (errors.length === 0 && Array.isArray(parsed)) {
36
+ targetKeybindings = parsed;
37
+ }
38
+ }
39
+
40
+ // Merge: source keybindings override target ones with same key+command combo
41
+ const merged = [...targetKeybindings];
42
+
43
+ for (const sourceBinding of sourceKeybindings) {
44
+ const existingIndex = merged.findIndex(
45
+ (t) => t.key === sourceBinding.key && t.command === sourceBinding.command,
46
+ );
47
+
48
+ if (existingIndex >= 0) {
49
+ merged[existingIndex] = sourceBinding;
50
+ } else {
51
+ merged.push(sourceBinding);
52
+ }
53
+ }
54
+
55
+ writeFileSync(targetPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
56
+ return merged;
57
+ }
@@ -1,7 +1,12 @@
1
1
  import { existsSync, mkdirSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
- import { EDITOR_DATA_DIRS, SETTINGS_FILE, SNIPPETS_DIR } from "./constants.js";
4
+ import {
5
+ EDITOR_DATA_DIRS,
6
+ KEYBINDINGS_FILE,
7
+ SETTINGS_FILE,
8
+ SNIPPETS_DIR,
9
+ } from "./constants.js";
5
10
 
6
11
  function getConfigBaseDir() {
7
12
  if (process.platform === "win32") {
@@ -34,3 +39,7 @@ export function getSettingsPath(editor, options = {}) {
34
39
  export function getSnippetsPath(editor, options = {}) {
35
40
  return join(getEditorUserDir(editor, options), SNIPPETS_DIR);
36
41
  }
42
+
43
+ export function getKeybindingsPath(editor, options = {}) {
44
+ return join(getEditorUserDir(editor, options), KEYBINDINGS_FILE);
45
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "editor-profile-sync",
3
- "version": "1.0.5",
4
- "description": "Cross-platform extensions, settings.json, and snippets sync for VS Code-based editors",
3
+ "version": "1.0.6",
4
+ "description": "Cross-platform extensions, settings.json, keybindings, and snippets sync for VS Code-based editors",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {