editor-profile-sync 1.0.5 → 1.0.7
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 +189 -145
- package/index.js +297 -248
- package/lib/constants.js +71 -65
- package/lib/editor-cli.js +151 -151
- package/lib/extensions-sync.js +44 -44
- package/lib/keybindings-sync.js +57 -0
- package/lib/profile-paths.js +45 -36
- package/lib/prompts.js +51 -51
- package/lib/settings-sync.js +74 -74
- package/lib/snippets-sync.js +68 -68
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,248 +1,297 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "./lib/constants.js";
|
|
15
|
-
import { isEditorInstalled } from "./lib/editor-cli.js";
|
|
16
|
-
import { exportExtensions, syncExtensions } from "./lib/extensions-sync.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
console.error(
|
|
153
|
-
process.exit(1);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import updateNotifier from "update-notifier";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import {
|
|
10
|
+
EDITORS,
|
|
11
|
+
EXTENSION_MODES,
|
|
12
|
+
SNIPPET_MODES,
|
|
13
|
+
SYNC_ITEMS,
|
|
14
|
+
} from "./lib/constants.js";
|
|
15
|
+
import { isEditorInstalled } from "./lib/editor-cli.js";
|
|
16
|
+
import { exportExtensions, syncExtensions } from "./lib/extensions-sync.js";
|
|
17
|
+
import {
|
|
18
|
+
readSourceKeybindings,
|
|
19
|
+
syncKeybindings,
|
|
20
|
+
} from "./lib/keybindings-sync.js";
|
|
21
|
+
import {
|
|
22
|
+
getKeybindingsPath,
|
|
23
|
+
getSettingsPath,
|
|
24
|
+
getSnippetsPath,
|
|
25
|
+
} from "./lib/profile-paths.js";
|
|
26
|
+
import {
|
|
27
|
+
promptExtensionMode,
|
|
28
|
+
promptSnippetMode,
|
|
29
|
+
promptSourceEditor,
|
|
30
|
+
promptSyncItems,
|
|
31
|
+
promptTargetEditors,
|
|
32
|
+
} from "./lib/prompts.js";
|
|
33
|
+
import { readSourceSettings, syncSettings } from "./lib/settings-sync.js";
|
|
34
|
+
import { syncSnippets } from "./lib/snippets-sync.js";
|
|
35
|
+
|
|
36
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
37
|
+
const __dirname = dirname(__filename);
|
|
38
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf-8"));
|
|
39
|
+
|
|
40
|
+
const notifier = updateNotifier({ pkg });
|
|
41
|
+
notifier.notify();
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
|
|
46
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
47
|
+
console.log(`
|
|
48
|
+
${pkg.name} v${pkg.version}
|
|
49
|
+
${pkg.description}
|
|
50
|
+
|
|
51
|
+
Usage: ${Object.keys(pkg.bin)[0]} [options]
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
-h, --help Show this help message
|
|
55
|
+
-v, --version Show version number
|
|
56
|
+
`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
61
|
+
console.log(pkg.version);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log("\n Editor profile sync\n");
|
|
66
|
+
|
|
67
|
+
const detectSpinner = ora({
|
|
68
|
+
text: "Detecting installed editors...",
|
|
69
|
+
color: "cyan",
|
|
70
|
+
}).start();
|
|
71
|
+
|
|
72
|
+
const availableEditors = EDITORS.filter((editor) =>
|
|
73
|
+
isEditorInstalled(editor),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (availableEditors.length === 0) {
|
|
77
|
+
detectSpinner.fail("No supported editors found.");
|
|
78
|
+
if (process.platform === "darwin") {
|
|
79
|
+
console.log(
|
|
80
|
+
chalk.yellow(
|
|
81
|
+
"\n On macOS, you have two options:\n" +
|
|
82
|
+
" 1. Install terminal command (recommended):\n" +
|
|
83
|
+
" • Open your editor\n" +
|
|
84
|
+
" • Press Cmd + Shift + P\n" +
|
|
85
|
+
" • Type: shell command\n" +
|
|
86
|
+
" • Select: Shell Command: Install '[command]' command in PATH\n" +
|
|
87
|
+
" • Restart your terminal\n" +
|
|
88
|
+
" 2. Or make sure the editor is installed in /Applications/[EditorName].app\n",
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
console.log(
|
|
93
|
+
chalk.yellow(
|
|
94
|
+
"\n Install at least one supported editor and make sure its CLI is on your PATH.\n",
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
detectSpinner.succeed(
|
|
102
|
+
`Found ${availableEditors.length} editor${availableEditors.length === 1 ? "" : "s"}: ${availableEditors
|
|
103
|
+
.map((e) => e.name)
|
|
104
|
+
.join(", ")}`,
|
|
105
|
+
);
|
|
106
|
+
console.log("");
|
|
107
|
+
|
|
108
|
+
const sourceId = await promptSourceEditor(availableEditors);
|
|
109
|
+
const sourceEditor = availableEditors.find((e) => e.id === sourceId);
|
|
110
|
+
|
|
111
|
+
const syncItems = await promptSyncItems(SYNC_ITEMS, chalk);
|
|
112
|
+
|
|
113
|
+
let extensionMode = "additive";
|
|
114
|
+
if (syncItems.includes("extensions")) {
|
|
115
|
+
extensionMode = await promptExtensionMode(EXTENSION_MODES);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let snippetMode = "merge";
|
|
119
|
+
if (syncItems.includes("snippets")) {
|
|
120
|
+
snippetMode = await promptSnippetMode(SNIPPET_MODES);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const targetChoices = availableEditors
|
|
124
|
+
.filter((e) => e.id !== sourceId)
|
|
125
|
+
.map((e) => ({ name: e.name, value: e.id }));
|
|
126
|
+
|
|
127
|
+
if (targetChoices.length === 0) {
|
|
128
|
+
console.log(
|
|
129
|
+
chalk.yellow("\n No other installed editors found to sync to.\n"),
|
|
130
|
+
);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const targetIds = await promptTargetEditors(targetChoices, chalk);
|
|
135
|
+
const targetEditors = availableEditors.filter((e) =>
|
|
136
|
+
targetIds.includes(e.id),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
let activeItems = [...syncItems];
|
|
140
|
+
|
|
141
|
+
let desiredExtensions = [];
|
|
142
|
+
if (activeItems.includes("extensions")) {
|
|
143
|
+
const exportSpinner = ora({
|
|
144
|
+
text: `Exporting extensions from ${sourceEditor.name}...`,
|
|
145
|
+
color: "cyan",
|
|
146
|
+
}).start();
|
|
147
|
+
try {
|
|
148
|
+
desiredExtensions = await exportExtensions(sourceEditor);
|
|
149
|
+
exportSpinner.succeed(`${desiredExtensions.length} extensions detected`);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
exportSpinner.fail("Extension export failed");
|
|
152
|
+
console.error(" Error:", err.message, "\n");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let sourceSettings = {};
|
|
158
|
+
if (activeItems.includes("settings")) {
|
|
159
|
+
try {
|
|
160
|
+
sourceSettings = readSourceSettings(getSettingsPath(sourceEditor));
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.warn(chalk.yellow(` settings.json skipped: ${err.message}`));
|
|
163
|
+
activeItems = activeItems.filter((item) => item !== "settings");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let sourceSnippetsDir = "";
|
|
168
|
+
if (activeItems.includes("snippets")) {
|
|
169
|
+
sourceSnippetsDir = getSnippetsPath(sourceEditor);
|
|
170
|
+
if (!existsSync(sourceSnippetsDir)) {
|
|
171
|
+
console.warn(
|
|
172
|
+
chalk.yellow(
|
|
173
|
+
` snippets skipped: source folder not found: ${sourceSnippetsDir}`,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
activeItems = activeItems.filter((item) => item !== "snippets");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let sourceKeybindings = [];
|
|
181
|
+
if (activeItems.includes("keybindings")) {
|
|
182
|
+
try {
|
|
183
|
+
sourceKeybindings = readSourceKeybindings(
|
|
184
|
+
getKeybindingsPath(sourceEditor),
|
|
185
|
+
);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn(chalk.yellow(` keybindings.json skipped: ${err.message}`));
|
|
188
|
+
activeItems = activeItems.filter((item) => item !== "keybindings");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (activeItems.length === 0) {
|
|
193
|
+
console.error(
|
|
194
|
+
chalk.red("\n All selected sync items were skipped due to errors.\n"),
|
|
195
|
+
);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log("");
|
|
200
|
+
|
|
201
|
+
for (const editor of targetEditors) {
|
|
202
|
+
try {
|
|
203
|
+
console.log(chalk.bold(editor.name) + ":");
|
|
204
|
+
|
|
205
|
+
if (activeItems.includes("settings")) {
|
|
206
|
+
const spinner = ora({
|
|
207
|
+
text: "Syncing settings.json...",
|
|
208
|
+
color: "cyan",
|
|
209
|
+
}).start();
|
|
210
|
+
try {
|
|
211
|
+
const targetPath = getSettingsPath(editor, { createIfMissing: true });
|
|
212
|
+
const merged = syncSettings(sourceSettings, targetPath);
|
|
213
|
+
spinner.succeed(
|
|
214
|
+
`settings.json synced (${Object.keys(merged).length} keys)`,
|
|
215
|
+
);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
spinner.fail(`settings sync failed (${err.message})`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (activeItems.includes("snippets")) {
|
|
222
|
+
const spinner = ora({
|
|
223
|
+
text: `Syncing snippets (${snippetMode} mode)...`,
|
|
224
|
+
color: "cyan",
|
|
225
|
+
}).start();
|
|
226
|
+
try {
|
|
227
|
+
const targetSnippetsDir = getSnippetsPath(editor, {
|
|
228
|
+
createIfMissing: true,
|
|
229
|
+
});
|
|
230
|
+
const fileCount = syncSnippets(
|
|
231
|
+
sourceSnippetsDir,
|
|
232
|
+
targetSnippetsDir,
|
|
233
|
+
snippetMode,
|
|
234
|
+
);
|
|
235
|
+
spinner.succeed(
|
|
236
|
+
`snippets synced (${fileCount} file${fileCount === 1 ? "" : "s"}, ${snippetMode} mode)`,
|
|
237
|
+
);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
spinner.fail(`snippets sync failed (${err.message})`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (activeItems.includes("keybindings")) {
|
|
244
|
+
const spinner = ora({
|
|
245
|
+
text: "Syncing keybindings.json...",
|
|
246
|
+
color: "cyan",
|
|
247
|
+
}).start();
|
|
248
|
+
try {
|
|
249
|
+
const targetPath = getKeybindingsPath(editor, {
|
|
250
|
+
createIfMissing: true,
|
|
251
|
+
});
|
|
252
|
+
const merged = syncKeybindings(sourceKeybindings, targetPath);
|
|
253
|
+
spinner.succeed(
|
|
254
|
+
`keybindings.json synced (${merged.length} binding${merged.length === 1 ? "" : "s"})`,
|
|
255
|
+
);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
spinner.fail(`keybindings sync failed (${err.message})`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (activeItems.includes("extensions")) {
|
|
262
|
+
const spinner = ora({
|
|
263
|
+
text: "Installing extensions...",
|
|
264
|
+
color: "cyan",
|
|
265
|
+
}).start();
|
|
266
|
+
const result = await syncExtensions(
|
|
267
|
+
editor,
|
|
268
|
+
desiredExtensions,
|
|
269
|
+
extensionMode,
|
|
270
|
+
(i, extensionId) => {
|
|
271
|
+
spinner.text = `Installing extensions [${i + 1}/${desiredExtensions.length}] ${chalk.dim(extensionId)}`;
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
const successPart = chalk.green(`${result.synced} installed`);
|
|
275
|
+
const failedPart =
|
|
276
|
+
result.failed.length > 0
|
|
277
|
+
? `, ${chalk.red(`${result.failed.length} failed`)}`
|
|
278
|
+
: "";
|
|
279
|
+
spinner.succeed(`extensions synced (${successPart}${failedPart})`);
|
|
280
|
+
if (result.failed.length > 0) {
|
|
281
|
+
console.log(`${chalk.red("Failed:")} ${result.failed.join(", ")}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log("");
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.log(" ", editor.name, "error:", err.message, "\n");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.log(chalk.green("✔ Sync complete.\n"));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
main().catch((err) => {
|
|
295
|
+
console.error(err);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
});
|