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