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 +45 -1
- package/index.js +40 -2
- package/lib/constants.js +9 -3
- package/lib/keybindings-sync.js +57 -0
- package/lib/profile-paths.js +10 -1
- package/package.json +2 -2
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 {
|
|
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
|
|
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: "
|
|
53
|
-
name: "
|
|
54
|
-
short: "
|
|
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
|
+
}
|
package/lib/profile-paths.js
CHANGED
|
@@ -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 {
|
|
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.
|
|
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": {
|