editor-profile-sync 1.0.6 → 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 -189
- package/index.js +297 -286
- package/lib/constants.js +71 -71
- package/lib/editor-cli.js +151 -151
- package/lib/extensions-sync.js +44 -44
- package/lib/profile-paths.js +45 -45
- package/lib/prompts.js +51 -51
- package/lib/settings-sync.js +74 -74
- package/lib/snippets-sync.js +68 -68
- package/package.json +1 -1
package/lib/constants.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
export const SETTINGS_FILE = "settings.json";
|
|
2
|
-
export const SNIPPETS_DIR = "snippets";
|
|
3
|
-
export const KEYBINDINGS_FILE = "keybindings.json";
|
|
4
|
-
|
|
5
|
-
// Sorted alphabetically by display name.
|
|
6
|
-
export const EDITORS = [
|
|
7
|
-
{ id: "antigravity", name: "Antigravity", cmd: "antigravity" },
|
|
8
|
-
{ id: "cursor", name: "Cursor", cmd: "cursor" },
|
|
9
|
-
{ id: "kiro", name: "Kiro", cmd: "kiro" },
|
|
10
|
-
{ id: "trae", name: "Trae", cmd: "trae" },
|
|
11
|
-
{ id: "code", name: "VS Code", cmd: "code" },
|
|
12
|
-
{ id: "windsurf", name: "Windsurf", cmd: "windsurf" },
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
export const EXTENSION_MODES = [
|
|
16
|
-
{
|
|
17
|
-
value: "additive",
|
|
18
|
-
name: "Install extensions on top of existing (Recommended)",
|
|
19
|
-
short: "Additive",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
value: "strict",
|
|
23
|
-
name: "Exact sync (Replace all extensions)",
|
|
24
|
-
short: "Strict",
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
export const SNIPPET_MODES = [
|
|
29
|
-
{
|
|
30
|
-
value: "merge",
|
|
31
|
-
name: "Merge snippet files (Recommended)",
|
|
32
|
-
short: "Merge",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
value: "replace",
|
|
36
|
-
name: "Replace all target snippets",
|
|
37
|
-
short: "Replace",
|
|
38
|
-
},
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
export const SYNC_ITEMS = [
|
|
42
|
-
{
|
|
43
|
-
value: "extensions",
|
|
44
|
-
name: "Extensions",
|
|
45
|
-
short: "Extensions",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
value: "snippets",
|
|
49
|
-
name: "Snippets",
|
|
50
|
-
short: "Snippets",
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
value: "settings",
|
|
54
|
-
name: "settings.json",
|
|
55
|
-
short: "settings.json",
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
value: "keybindings",
|
|
59
|
-
name: "keybindings.json",
|
|
60
|
-
short: "keybindings.json",
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
export const EDITOR_DATA_DIRS = {
|
|
65
|
-
antigravity: ["Antigravity"],
|
|
66
|
-
code: ["Code", "Code - OSS"],
|
|
67
|
-
cursor: ["Cursor"],
|
|
68
|
-
kiro: ["Kiro"],
|
|
69
|
-
trae: ["Trae"],
|
|
70
|
-
windsurf: ["Windsurf"],
|
|
71
|
-
};
|
|
1
|
+
export const SETTINGS_FILE = "settings.json";
|
|
2
|
+
export const SNIPPETS_DIR = "snippets";
|
|
3
|
+
export const KEYBINDINGS_FILE = "keybindings.json";
|
|
4
|
+
|
|
5
|
+
// Sorted alphabetically by display name.
|
|
6
|
+
export const EDITORS = [
|
|
7
|
+
{ id: "antigravity", name: "Antigravity", cmd: "antigravity" },
|
|
8
|
+
{ id: "cursor", name: "Cursor", cmd: "cursor" },
|
|
9
|
+
{ id: "kiro", name: "Kiro", cmd: "kiro" },
|
|
10
|
+
{ id: "trae", name: "Trae", cmd: "trae" },
|
|
11
|
+
{ id: "code", name: "VS Code", cmd: "code" },
|
|
12
|
+
{ id: "windsurf", name: "Windsurf", cmd: "windsurf" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export const EXTENSION_MODES = [
|
|
16
|
+
{
|
|
17
|
+
value: "additive",
|
|
18
|
+
name: "Install extensions on top of existing (Recommended)",
|
|
19
|
+
short: "Additive",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
value: "strict",
|
|
23
|
+
name: "Exact sync (Replace all extensions)",
|
|
24
|
+
short: "Strict",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const SNIPPET_MODES = [
|
|
29
|
+
{
|
|
30
|
+
value: "merge",
|
|
31
|
+
name: "Merge snippet files (Recommended)",
|
|
32
|
+
short: "Merge",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
value: "replace",
|
|
36
|
+
name: "Replace all target snippets",
|
|
37
|
+
short: "Replace",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export const SYNC_ITEMS = [
|
|
42
|
+
{
|
|
43
|
+
value: "extensions",
|
|
44
|
+
name: "Extensions",
|
|
45
|
+
short: "Extensions",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
value: "snippets",
|
|
49
|
+
name: "Snippets",
|
|
50
|
+
short: "Snippets",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
value: "settings",
|
|
54
|
+
name: "settings.json",
|
|
55
|
+
short: "settings.json",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: "keybindings",
|
|
59
|
+
name: "keybindings.json",
|
|
60
|
+
short: "keybindings.json",
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export const EDITOR_DATA_DIRS = {
|
|
65
|
+
antigravity: ["Antigravity"],
|
|
66
|
+
code: ["Code", "Code - OSS"],
|
|
67
|
+
cursor: ["Cursor"],
|
|
68
|
+
kiro: ["Kiro"],
|
|
69
|
+
trae: ["Trae"],
|
|
70
|
+
windsurf: ["Windsurf"],
|
|
71
|
+
};
|
package/lib/editor-cli.js
CHANGED
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
import { execSync, spawn } from "child_process";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
// macOS app bundle names for each editor
|
|
6
|
-
const MACOS_APP_BUNDLES = {
|
|
7
|
-
antigravity: "Antigravity.app",
|
|
8
|
-
cursor: "Cursor.app",
|
|
9
|
-
kiro: "Kiro.app",
|
|
10
|
-
trae: "Trae.app",
|
|
11
|
-
code: "Visual Studio Code.app",
|
|
12
|
-
windsurf: "Windsurf.app",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function findMacOSCliPath(editor) {
|
|
16
|
-
if (process.platform !== "darwin") return null;
|
|
17
|
-
|
|
18
|
-
const appName = MACOS_APP_BUNDLES[editor.id];
|
|
19
|
-
if (!appName) return null;
|
|
20
|
-
|
|
21
|
-
const appPath = join("/Applications", appName);
|
|
22
|
-
if (!existsSync(appPath)) return null;
|
|
23
|
-
|
|
24
|
-
// VS Code-based editors typically have CLI at:
|
|
25
|
-
// /Applications/AppName.app/Contents/Resources/app/bin/command
|
|
26
|
-
const cliPath = join(
|
|
27
|
-
appPath,
|
|
28
|
-
"Contents",
|
|
29
|
-
"Resources",
|
|
30
|
-
"app",
|
|
31
|
-
"bin",
|
|
32
|
-
editor.cmd,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
if (existsSync(cliPath)) {
|
|
36
|
-
return cliPath;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function isEditorInstalled(editor) {
|
|
43
|
-
// First check PATH
|
|
44
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
45
|
-
try {
|
|
46
|
-
execSync(`${cmd} ${editor.cmd}`, { stdio: "ignore" });
|
|
47
|
-
return true;
|
|
48
|
-
} catch {
|
|
49
|
-
// On macOS, also check app bundles
|
|
50
|
-
if (process.platform === "darwin") {
|
|
51
|
-
return findMacOSCliPath(editor) !== null;
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function getEditorCliPath(editor) {
|
|
58
|
-
// First check PATH
|
|
59
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
60
|
-
try {
|
|
61
|
-
const result = execSync(`${cmd} ${editor.cmd}`, { encoding: "utf-8" });
|
|
62
|
-
const path = result.trim().split("\n")[0];
|
|
63
|
-
if (path) return path;
|
|
64
|
-
} catch {
|
|
65
|
-
// Fall through to fallback options
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// On macOS, fall back to app bundle path
|
|
69
|
-
if (process.platform === "darwin") {
|
|
70
|
-
const macPath = findMacOSCliPath(editor);
|
|
71
|
-
if (macPath) return macPath;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Final fallback: use command name (should work if in PATH)
|
|
75
|
-
return editor.cmd;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function escapeShellArg(arg) {
|
|
79
|
-
const s = String(arg);
|
|
80
|
-
return s.includes(" ") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function runEditorCmdAsync(editor, args, options = {}) {
|
|
84
|
-
const { ignoreError = false } = options;
|
|
85
|
-
const cliPath = getEditorCliPath(editor);
|
|
86
|
-
const cmdString = [escapeShellArg(cliPath), ...args.map(escapeShellArg)].join(
|
|
87
|
-
" ",
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
const child = spawn(cmdString, [], {
|
|
92
|
-
shell: true,
|
|
93
|
-
stdio: ["inherit", "pipe", "pipe"],
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
let stdout = "";
|
|
97
|
-
let stderr = "";
|
|
98
|
-
|
|
99
|
-
if (child.stdout) {
|
|
100
|
-
child.stdout.on("data", (d) => {
|
|
101
|
-
stdout += d.toString();
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (child.stderr) {
|
|
106
|
-
child.stderr.on("data", (d) => {
|
|
107
|
-
stderr += d.toString();
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
child.on("close", (code) => {
|
|
112
|
-
if (code === 0) resolve(stdout);
|
|
113
|
-
else if (ignoreError) resolve(null);
|
|
114
|
-
else reject(new Error(stderr || `Exit ${code}`));
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
child.on("error", (err) => reject(err));
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function getInstalledExtensions(editor) {
|
|
122
|
-
const out = await runEditorCmdAsync(editor, ["--list-extensions"], {
|
|
123
|
-
ignoreError: true,
|
|
124
|
-
});
|
|
125
|
-
if (out == null) return null;
|
|
126
|
-
return out
|
|
127
|
-
.split(/\r?\n/)
|
|
128
|
-
.map((s) => s.trim())
|
|
129
|
-
.filter(Boolean);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export async function installExtension(editor, extensionId) {
|
|
133
|
-
const out = await runEditorCmdAsync(
|
|
134
|
-
editor,
|
|
135
|
-
["--install-extension", extensionId],
|
|
136
|
-
{
|
|
137
|
-
ignoreError: true,
|
|
138
|
-
},
|
|
139
|
-
);
|
|
140
|
-
return out !== null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function uninstallExtension(editor, extensionId) {
|
|
144
|
-
const cliPath = getEditorCliPath(editor);
|
|
145
|
-
const cmd = [
|
|
146
|
-
escapeShellArg(cliPath),
|
|
147
|
-
"--uninstall-extension",
|
|
148
|
-
escapeShellArg(extensionId),
|
|
149
|
-
].join(" ");
|
|
150
|
-
execSync(cmd, { stdio: "ignore" });
|
|
151
|
-
}
|
|
1
|
+
import { execSync, spawn } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
// macOS app bundle names for each editor
|
|
6
|
+
const MACOS_APP_BUNDLES = {
|
|
7
|
+
antigravity: "Antigravity.app",
|
|
8
|
+
cursor: "Cursor.app",
|
|
9
|
+
kiro: "Kiro.app",
|
|
10
|
+
trae: "Trae.app",
|
|
11
|
+
code: "Visual Studio Code.app",
|
|
12
|
+
windsurf: "Windsurf.app",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function findMacOSCliPath(editor) {
|
|
16
|
+
if (process.platform !== "darwin") return null;
|
|
17
|
+
|
|
18
|
+
const appName = MACOS_APP_BUNDLES[editor.id];
|
|
19
|
+
if (!appName) return null;
|
|
20
|
+
|
|
21
|
+
const appPath = join("/Applications", appName);
|
|
22
|
+
if (!existsSync(appPath)) return null;
|
|
23
|
+
|
|
24
|
+
// VS Code-based editors typically have CLI at:
|
|
25
|
+
// /Applications/AppName.app/Contents/Resources/app/bin/command
|
|
26
|
+
const cliPath = join(
|
|
27
|
+
appPath,
|
|
28
|
+
"Contents",
|
|
29
|
+
"Resources",
|
|
30
|
+
"app",
|
|
31
|
+
"bin",
|
|
32
|
+
editor.cmd,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (existsSync(cliPath)) {
|
|
36
|
+
return cliPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isEditorInstalled(editor) {
|
|
43
|
+
// First check PATH
|
|
44
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
45
|
+
try {
|
|
46
|
+
execSync(`${cmd} ${editor.cmd}`, { stdio: "ignore" });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
// On macOS, also check app bundles
|
|
50
|
+
if (process.platform === "darwin") {
|
|
51
|
+
return findMacOSCliPath(editor) !== null;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getEditorCliPath(editor) {
|
|
58
|
+
// First check PATH
|
|
59
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
60
|
+
try {
|
|
61
|
+
const result = execSync(`${cmd} ${editor.cmd}`, { encoding: "utf-8" });
|
|
62
|
+
const path = result.trim().split("\n")[0];
|
|
63
|
+
if (path) return path;
|
|
64
|
+
} catch {
|
|
65
|
+
// Fall through to fallback options
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// On macOS, fall back to app bundle path
|
|
69
|
+
if (process.platform === "darwin") {
|
|
70
|
+
const macPath = findMacOSCliPath(editor);
|
|
71
|
+
if (macPath) return macPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Final fallback: use command name (should work if in PATH)
|
|
75
|
+
return editor.cmd;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function escapeShellArg(arg) {
|
|
79
|
+
const s = String(arg);
|
|
80
|
+
return s.includes(" ") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runEditorCmdAsync(editor, args, options = {}) {
|
|
84
|
+
const { ignoreError = false } = options;
|
|
85
|
+
const cliPath = getEditorCliPath(editor);
|
|
86
|
+
const cmdString = [escapeShellArg(cliPath), ...args.map(escapeShellArg)].join(
|
|
87
|
+
" ",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const child = spawn(cmdString, [], {
|
|
92
|
+
shell: true,
|
|
93
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
let stdout = "";
|
|
97
|
+
let stderr = "";
|
|
98
|
+
|
|
99
|
+
if (child.stdout) {
|
|
100
|
+
child.stdout.on("data", (d) => {
|
|
101
|
+
stdout += d.toString();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (child.stderr) {
|
|
106
|
+
child.stderr.on("data", (d) => {
|
|
107
|
+
stderr += d.toString();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
child.on("close", (code) => {
|
|
112
|
+
if (code === 0) resolve(stdout);
|
|
113
|
+
else if (ignoreError) resolve(null);
|
|
114
|
+
else reject(new Error(stderr || `Exit ${code}`));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
child.on("error", (err) => reject(err));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getInstalledExtensions(editor) {
|
|
122
|
+
const out = await runEditorCmdAsync(editor, ["--list-extensions"], {
|
|
123
|
+
ignoreError: true,
|
|
124
|
+
});
|
|
125
|
+
if (out == null) return null;
|
|
126
|
+
return out
|
|
127
|
+
.split(/\r?\n/)
|
|
128
|
+
.map((s) => s.trim())
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function installExtension(editor, extensionId) {
|
|
133
|
+
const out = await runEditorCmdAsync(
|
|
134
|
+
editor,
|
|
135
|
+
["--install-extension", extensionId],
|
|
136
|
+
{
|
|
137
|
+
ignoreError: true,
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
return out !== null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function uninstallExtension(editor, extensionId) {
|
|
144
|
+
const cliPath = getEditorCliPath(editor);
|
|
145
|
+
const cmd = [
|
|
146
|
+
escapeShellArg(cliPath),
|
|
147
|
+
"--uninstall-extension",
|
|
148
|
+
escapeShellArg(extensionId),
|
|
149
|
+
].join(" ");
|
|
150
|
+
execSync(cmd, { stdio: "ignore" });
|
|
151
|
+
}
|
package/lib/extensions-sync.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getInstalledExtensions,
|
|
3
|
-
installExtension,
|
|
4
|
-
uninstallExtension,
|
|
5
|
-
} from "./editor-cli.js";
|
|
6
|
-
|
|
7
|
-
export async function exportExtensions(sourceEditor) {
|
|
8
|
-
const list = await getInstalledExtensions(sourceEditor);
|
|
9
|
-
if (list == null) {
|
|
10
|
-
throw new Error(`${sourceEditor.name} CLI not found or failed.`);
|
|
11
|
-
}
|
|
12
|
-
return [...list].sort();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function syncExtensions(editor, desired, mode, onProgress) {
|
|
16
|
-
const current = await getInstalledExtensions(editor);
|
|
17
|
-
if (current == null) {
|
|
18
|
-
throw new Error(`${editor.name} CLI not found or failed.`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (mode === "strict") {
|
|
22
|
-
const toRemove = current.filter((id) => !desired.includes(id));
|
|
23
|
-
for (const extensionId of toRemove) {
|
|
24
|
-
try {
|
|
25
|
-
uninstallExtension(editor, extensionId);
|
|
26
|
-
} catch {
|
|
27
|
-
// Keep going; failures are naturally reflected when reinstalling.
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let synced = 0;
|
|
33
|
-
const failed = [];
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < desired.length; i++) {
|
|
36
|
-
const extensionId = desired[i];
|
|
37
|
-
if (onProgress) onProgress(i, extensionId);
|
|
38
|
-
const ok = await installExtension(editor, extensionId);
|
|
39
|
-
if (ok) synced++;
|
|
40
|
-
else failed.push(extensionId);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { synced, failed };
|
|
44
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
getInstalledExtensions,
|
|
3
|
+
installExtension,
|
|
4
|
+
uninstallExtension,
|
|
5
|
+
} from "./editor-cli.js";
|
|
6
|
+
|
|
7
|
+
export async function exportExtensions(sourceEditor) {
|
|
8
|
+
const list = await getInstalledExtensions(sourceEditor);
|
|
9
|
+
if (list == null) {
|
|
10
|
+
throw new Error(`${sourceEditor.name} CLI not found or failed.`);
|
|
11
|
+
}
|
|
12
|
+
return [...list].sort();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function syncExtensions(editor, desired, mode, onProgress) {
|
|
16
|
+
const current = await getInstalledExtensions(editor);
|
|
17
|
+
if (current == null) {
|
|
18
|
+
throw new Error(`${editor.name} CLI not found or failed.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (mode === "strict") {
|
|
22
|
+
const toRemove = current.filter((id) => !desired.includes(id));
|
|
23
|
+
for (const extensionId of toRemove) {
|
|
24
|
+
try {
|
|
25
|
+
uninstallExtension(editor, extensionId);
|
|
26
|
+
} catch {
|
|
27
|
+
// Keep going; failures are naturally reflected when reinstalling.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let synced = 0;
|
|
33
|
+
const failed = [];
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < desired.length; i++) {
|
|
36
|
+
const extensionId = desired[i];
|
|
37
|
+
if (onProgress) onProgress(i, extensionId);
|
|
38
|
+
const ok = await installExtension(editor, extensionId);
|
|
39
|
+
if (ok) synced++;
|
|
40
|
+
else failed.push(extensionId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { synced, failed };
|
|
44
|
+
}
|
package/lib/profile-paths.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import {
|
|
5
|
-
EDITOR_DATA_DIRS,
|
|
6
|
-
KEYBINDINGS_FILE,
|
|
7
|
-
SETTINGS_FILE,
|
|
8
|
-
SNIPPETS_DIR,
|
|
9
|
-
} from "./constants.js";
|
|
10
|
-
|
|
11
|
-
function getConfigBaseDir() {
|
|
12
|
-
if (process.platform === "win32") {
|
|
13
|
-
return process.env.APPDATA || join(homedir(), "AppData", "Roaming");
|
|
14
|
-
}
|
|
15
|
-
if (process.platform === "darwin") {
|
|
16
|
-
return join(homedir(), "Library", "Application Support");
|
|
17
|
-
}
|
|
18
|
-
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getEditorUserDir(editor, { createIfMissing = false } = {}) {
|
|
22
|
-
const base = getConfigBaseDir();
|
|
23
|
-
const names = EDITOR_DATA_DIRS[editor.id] || [editor.name];
|
|
24
|
-
const candidates = names.map((name) => join(base, name, "User"));
|
|
25
|
-
const existing = candidates.find((p) => existsSync(p));
|
|
26
|
-
const selected = existing || candidates[0];
|
|
27
|
-
|
|
28
|
-
if (createIfMissing) {
|
|
29
|
-
mkdirSync(selected, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return selected;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function getSettingsPath(editor, options = {}) {
|
|
36
|
-
return join(getEditorUserDir(editor, options), SETTINGS_FILE);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getSnippetsPath(editor, options = {}) {
|
|
40
|
-
return join(getEditorUserDir(editor, options), SNIPPETS_DIR);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getKeybindingsPath(editor, options = {}) {
|
|
44
|
-
return join(getEditorUserDir(editor, options), KEYBINDINGS_FILE);
|
|
45
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import {
|
|
5
|
+
EDITOR_DATA_DIRS,
|
|
6
|
+
KEYBINDINGS_FILE,
|
|
7
|
+
SETTINGS_FILE,
|
|
8
|
+
SNIPPETS_DIR,
|
|
9
|
+
} from "./constants.js";
|
|
10
|
+
|
|
11
|
+
function getConfigBaseDir() {
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
return process.env.APPDATA || join(homedir(), "AppData", "Roaming");
|
|
14
|
+
}
|
|
15
|
+
if (process.platform === "darwin") {
|
|
16
|
+
return join(homedir(), "Library", "Application Support");
|
|
17
|
+
}
|
|
18
|
+
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getEditorUserDir(editor, { createIfMissing = false } = {}) {
|
|
22
|
+
const base = getConfigBaseDir();
|
|
23
|
+
const names = EDITOR_DATA_DIRS[editor.id] || [editor.name];
|
|
24
|
+
const candidates = names.map((name) => join(base, name, "User"));
|
|
25
|
+
const existing = candidates.find((p) => existsSync(p));
|
|
26
|
+
const selected = existing || candidates[0];
|
|
27
|
+
|
|
28
|
+
if (createIfMissing) {
|
|
29
|
+
mkdirSync(selected, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return selected;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getSettingsPath(editor, options = {}) {
|
|
36
|
+
return join(getEditorUserDir(editor, options), SETTINGS_FILE);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getSnippetsPath(editor, options = {}) {
|
|
40
|
+
return join(getEditorUserDir(editor, options), SNIPPETS_DIR);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getKeybindingsPath(editor, options = {}) {
|
|
44
|
+
return join(getEditorUserDir(editor, options), KEYBINDINGS_FILE);
|
|
45
|
+
}
|