zcode-idea 0.0.0-stage → 0.0.1
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 +295 -2
- package/bin/zcode-idea.mjs +463 -0
- package/lib/download.mjs +294 -0
- package/lib/errors.mjs +17 -0
- package/lib/installed.mjs +86 -0
- package/lib/launch.mjs +214 -0
- package/lib/log.mjs +36 -0
- package/lib/manifest.mjs +301 -0
- package/lib/plugin-signing-public.pem +3 -0
- package/lib/processes.mjs +194 -0
- package/lib/products.mjs +352 -0
- package/lib/prompt.mjs +37 -0
- package/lib/remote.mjs +356 -0
- package/lib/targets.mjs +415 -0
- package/lib/unzip.mjs +157 -0
- package/package.json +35 -4
package/lib/products.mjs
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JetBrains 产品注册表:配置目录前缀、进程名、安装目录名、可执行文件名、Toolbox key。
|
|
3
|
+
*
|
|
4
|
+
* 关键约定:
|
|
5
|
+
* - 配置目录名形如 `<Product><YYYY.N>`(例如 IntelliJIdea2026.2),用 parseConfigDirName 解析;
|
|
6
|
+
* - 安装目录名形如 `IntelliJ IDEA 2026.2`,用 inferProductFromName 解析;
|
|
7
|
+
* - 产品 -> 进程名映射供关闭 IDE 使用,同一产品的多个版本只关一次。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 名称匹配器按数组顺序生效:社区版/CE 必须排在商业版前面,
|
|
12
|
+
* 否则 `IntelliJ IDEA Community Edition` 会先命中 `^intellij idea`。
|
|
13
|
+
*/
|
|
14
|
+
export const PRODUCTS = [
|
|
15
|
+
{
|
|
16
|
+
id: "ideaCommunity",
|
|
17
|
+
name: "IntelliJ IDEA Community",
|
|
18
|
+
configPrefixes: ["IdeaIC"],
|
|
19
|
+
winProcesses: ["idea64.exe", "idea.exe"],
|
|
20
|
+
unixProcesses: ["idea"],
|
|
21
|
+
winExes: ["idea64.exe", "idea.exe"],
|
|
22
|
+
macApps: ["IntelliJ IDEA CE.app"],
|
|
23
|
+
macBins: ["idea"],
|
|
24
|
+
linuxScripts: ["idea.sh"],
|
|
25
|
+
toolboxKeys: ["IDEA-C"],
|
|
26
|
+
nameMatchers: [
|
|
27
|
+
/^intellij idea (community|ce)\b/,
|
|
28
|
+
/^intellijidea ?(community|ce)\b/,
|
|
29
|
+
/^idea-?c\b/,
|
|
30
|
+
/^idea-?i?c\b/,
|
|
31
|
+
/^ideaic\b/,
|
|
32
|
+
/intellij-idea-community/,
|
|
33
|
+
/^intellij idea ce\.app$/,
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "ideaUltimate",
|
|
38
|
+
name: "IntelliJ IDEA Ultimate",
|
|
39
|
+
configPrefixes: ["IntelliJIdea"],
|
|
40
|
+
winProcesses: ["idea64.exe", "idea.exe"],
|
|
41
|
+
unixProcesses: ["idea"],
|
|
42
|
+
winExes: ["idea64.exe", "idea.exe"],
|
|
43
|
+
macApps: ["IntelliJ IDEA.app"],
|
|
44
|
+
macBins: ["idea"],
|
|
45
|
+
linuxScripts: ["idea.sh"],
|
|
46
|
+
toolboxKeys: ["IDEA-U"],
|
|
47
|
+
nameMatchers: [/^intellij ?idea\b/, /^idea-?u\b/, /^idea-?i?u\b/, /intellij-idea-ultimate/],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "pycharmCommunity",
|
|
51
|
+
name: "PyCharm Community",
|
|
52
|
+
configPrefixes: ["PyCharmCE"],
|
|
53
|
+
winProcesses: ["pycharm64.exe"],
|
|
54
|
+
unixProcesses: ["pycharm"],
|
|
55
|
+
winExes: ["pycharm64.exe"],
|
|
56
|
+
macApps: ["PyCharm CE.app"],
|
|
57
|
+
macBins: ["pycharm"],
|
|
58
|
+
linuxScripts: ["pycharm.sh"],
|
|
59
|
+
toolboxKeys: ["PyCharm-C"],
|
|
60
|
+
nameMatchers: [
|
|
61
|
+
/^pycharm (community|ce|educational)\b/,
|
|
62
|
+
/^pycharm-?c\b/,
|
|
63
|
+
/^pycharmce\b/,
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "pycharm",
|
|
68
|
+
name: "PyCharm Professional",
|
|
69
|
+
configPrefixes: ["PyCharm"],
|
|
70
|
+
winProcesses: ["pycharm64.exe"],
|
|
71
|
+
unixProcesses: ["pycharm"],
|
|
72
|
+
winExes: ["pycharm64.exe"],
|
|
73
|
+
macApps: ["PyCharm.app"],
|
|
74
|
+
macBins: ["pycharm"],
|
|
75
|
+
linuxScripts: ["pycharm.sh"],
|
|
76
|
+
toolboxKeys: ["PyCharm-P"],
|
|
77
|
+
nameMatchers: [/^pycharm\b/, /^pycharm-?p\b/],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "androidStudio",
|
|
81
|
+
name: "Android Studio",
|
|
82
|
+
configPrefixes: ["AndroidStudio"],
|
|
83
|
+
winProcesses: ["studio64.exe"],
|
|
84
|
+
unixProcesses: ["studio"],
|
|
85
|
+
winExes: ["studio64.exe"],
|
|
86
|
+
macApps: ["Android Studio.app"],
|
|
87
|
+
macBins: ["studio"],
|
|
88
|
+
linuxScripts: ["studio.sh"],
|
|
89
|
+
toolboxKeys: ["AndroidStudio"],
|
|
90
|
+
nameMatchers: [/android ?studio/],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "webstorm",
|
|
94
|
+
name: "WebStorm",
|
|
95
|
+
configPrefixes: ["WebStorm"],
|
|
96
|
+
winProcesses: ["webstorm64.exe"],
|
|
97
|
+
unixProcesses: ["webstorm"],
|
|
98
|
+
winExes: ["webstorm64.exe"],
|
|
99
|
+
macApps: ["WebStorm.app"],
|
|
100
|
+
macBins: ["webstorm"],
|
|
101
|
+
linuxScripts: ["webstorm.sh"],
|
|
102
|
+
toolboxKeys: ["WebStorm"],
|
|
103
|
+
nameMatchers: [/webstorm/],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "phpstorm",
|
|
107
|
+
name: "PhpStorm",
|
|
108
|
+
configPrefixes: ["PhpStorm"],
|
|
109
|
+
winProcesses: ["phpstorm64.exe"],
|
|
110
|
+
unixProcesses: ["phpstorm"],
|
|
111
|
+
winExes: ["phpstorm64.exe"],
|
|
112
|
+
macApps: ["PhpStorm.app"],
|
|
113
|
+
macBins: ["phpstorm"],
|
|
114
|
+
linuxScripts: ["phpstorm.sh"],
|
|
115
|
+
toolboxKeys: ["PhpStorm"],
|
|
116
|
+
nameMatchers: [/phpstorm/],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "goland",
|
|
120
|
+
name: "GoLand",
|
|
121
|
+
configPrefixes: ["GoLand"],
|
|
122
|
+
winProcesses: ["goland64.exe"],
|
|
123
|
+
unixProcesses: ["goland"],
|
|
124
|
+
winExes: ["goland64.exe"],
|
|
125
|
+
macApps: ["GoLand.app"],
|
|
126
|
+
macBins: ["goland"],
|
|
127
|
+
linuxScripts: ["goland.sh"],
|
|
128
|
+
toolboxKeys: ["GoLand"],
|
|
129
|
+
nameMatchers: [/goland/],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "clion",
|
|
133
|
+
name: "CLion",
|
|
134
|
+
configPrefixes: ["CLion"],
|
|
135
|
+
winProcesses: ["clion64.exe"],
|
|
136
|
+
unixProcesses: ["clion"],
|
|
137
|
+
winExes: ["clion64.exe"],
|
|
138
|
+
macApps: ["CLion.app"],
|
|
139
|
+
macBins: ["clion"],
|
|
140
|
+
linuxScripts: ["clion.sh"],
|
|
141
|
+
toolboxKeys: ["CLion"],
|
|
142
|
+
nameMatchers: [/clion/],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "datagrip",
|
|
146
|
+
name: "DataGrip",
|
|
147
|
+
configPrefixes: ["DataGrip"],
|
|
148
|
+
winProcesses: ["datagrip64.exe"],
|
|
149
|
+
unixProcesses: ["datagrip"],
|
|
150
|
+
winExes: ["datagrip64.exe"],
|
|
151
|
+
macApps: ["DataGrip.app"],
|
|
152
|
+
macBins: ["datagrip"],
|
|
153
|
+
linuxScripts: ["datagrip.sh"],
|
|
154
|
+
toolboxKeys: ["DataGrip"],
|
|
155
|
+
nameMatchers: [/datagrip/],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "rider",
|
|
159
|
+
name: "Rider",
|
|
160
|
+
configPrefixes: ["Rider"],
|
|
161
|
+
winProcesses: ["rider64.exe"],
|
|
162
|
+
unixProcesses: ["rider"],
|
|
163
|
+
winExes: ["rider64.exe"],
|
|
164
|
+
macApps: ["Rider.app"],
|
|
165
|
+
macBins: ["rider"],
|
|
166
|
+
linuxScripts: ["rider.sh"],
|
|
167
|
+
toolboxKeys: ["Rider"],
|
|
168
|
+
nameMatchers: [/^rider\b/],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "rubymine",
|
|
172
|
+
name: "RubyMine",
|
|
173
|
+
configPrefixes: ["RubyMine"],
|
|
174
|
+
winProcesses: ["rubymine64.exe"],
|
|
175
|
+
unixProcesses: ["rubymine"],
|
|
176
|
+
winExes: ["rubymine64.exe"],
|
|
177
|
+
macApps: ["RubyMine.app"],
|
|
178
|
+
macBins: ["rubymine"],
|
|
179
|
+
linuxScripts: ["rubymine.sh"],
|
|
180
|
+
toolboxKeys: ["RubyMine"],
|
|
181
|
+
nameMatchers: [/rubymine/],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "aqua",
|
|
185
|
+
name: "Aqua",
|
|
186
|
+
configPrefixes: ["Aqua"],
|
|
187
|
+
winProcesses: ["aqua64.exe"],
|
|
188
|
+
unixProcesses: ["aqua"],
|
|
189
|
+
winExes: ["aqua64.exe"],
|
|
190
|
+
macApps: ["Aqua.app"],
|
|
191
|
+
macBins: ["aqua"],
|
|
192
|
+
linuxScripts: ["aqua.sh"],
|
|
193
|
+
toolboxKeys: ["Aqua"],
|
|
194
|
+
nameMatchers: [/^aqua\b/],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "rustrover",
|
|
198
|
+
name: "RustRover",
|
|
199
|
+
configPrefixes: ["RustRover"],
|
|
200
|
+
winProcesses: ["rustrover64.exe"],
|
|
201
|
+
unixProcesses: ["rustrover"],
|
|
202
|
+
winExes: ["rustrover64.exe"],
|
|
203
|
+
macApps: ["RustRover.app"],
|
|
204
|
+
macBins: ["rustrover"],
|
|
205
|
+
linuxScripts: ["rustrover.sh"],
|
|
206
|
+
toolboxKeys: ["RustRover"],
|
|
207
|
+
nameMatchers: [/rustrover/],
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "dataspell",
|
|
211
|
+
name: "DataSpell",
|
|
212
|
+
configPrefixes: ["DataSpell"],
|
|
213
|
+
winProcesses: ["dataspell64.exe"],
|
|
214
|
+
unixProcesses: ["dataspell"],
|
|
215
|
+
winExes: ["dataspell64.exe"],
|
|
216
|
+
macApps: ["DataSpell.app"],
|
|
217
|
+
macBins: ["dataspell"],
|
|
218
|
+
linuxScripts: ["dataspell.sh"],
|
|
219
|
+
toolboxKeys: ["DataSpell"],
|
|
220
|
+
nameMatchers: [/dataspell/],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "writerside",
|
|
224
|
+
name: "Writerside",
|
|
225
|
+
configPrefixes: ["Writerside"],
|
|
226
|
+
winProcesses: ["writerside64.exe"],
|
|
227
|
+
unixProcesses: ["writerside"],
|
|
228
|
+
winExes: ["writerside64.exe"],
|
|
229
|
+
macApps: ["Writerside.app"],
|
|
230
|
+
macBins: ["writerside"],
|
|
231
|
+
linuxScripts: ["writerside.sh"],
|
|
232
|
+
toolboxKeys: ["Writerside"],
|
|
233
|
+
nameMatchers: [/writerside/],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "mps",
|
|
237
|
+
name: "MPS",
|
|
238
|
+
configPrefixes: ["MPS"],
|
|
239
|
+
winProcesses: ["mps64.exe"],
|
|
240
|
+
unixProcesses: ["mps"],
|
|
241
|
+
winExes: ["mps64.exe"],
|
|
242
|
+
macApps: ["MPS.app"],
|
|
243
|
+
macBins: ["mps"],
|
|
244
|
+
linuxScripts: ["mps.sh"],
|
|
245
|
+
toolboxKeys: ["MPS"],
|
|
246
|
+
nameMatchers: [/^intellij mps\b/, /^mps\b/, /\bmps\b/],
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
export const PRODUCT_BY_ID = new Map(PRODUCTS.map((product) => [product.id, product]));
|
|
251
|
+
|
|
252
|
+
/** 配置目录前缀 -> 产品(小写键,匹配时大小写不敏感)。 */
|
|
253
|
+
export const CONFIG_PREFIX_MAP = new Map();
|
|
254
|
+
for (const product of PRODUCTS) {
|
|
255
|
+
for (const prefix of product.configPrefixes) {
|
|
256
|
+
CONFIG_PREFIX_MAP.set(prefix.toLowerCase(), product);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Toolbox 应用目录名 -> 产品(小写键)。 */
|
|
261
|
+
export const TOOLBOX_KEY_MAP = new Map();
|
|
262
|
+
for (const product of PRODUCTS) {
|
|
263
|
+
for (const key of product.toolboxKeys) {
|
|
264
|
+
TOOLBOX_KEY_MAP.set(key.toLowerCase(), product);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** 这些名字即使带年份后缀也不是 IDE 配置目录(防御性排除)。 */
|
|
269
|
+
const EXCLUDED_CONFIG_NAMES = new Set(
|
|
270
|
+
[
|
|
271
|
+
"toolbox",
|
|
272
|
+
"daemon",
|
|
273
|
+
"consentoptions",
|
|
274
|
+
"permanentdeviceid",
|
|
275
|
+
"permanentuserid",
|
|
276
|
+
"bl",
|
|
277
|
+
"crl",
|
|
278
|
+
"discovery",
|
|
279
|
+
"plugins",
|
|
280
|
+
"options",
|
|
281
|
+
].map((name) => name.toLowerCase()),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const CONFIG_DIR_RE = /^(.*?)(\d{4}\.\d+)$/;
|
|
285
|
+
// 只接受独立出现的版本号(前面不能紧跟数字),避免把 241.14494.240 这类 build 号误当版本
|
|
286
|
+
const INSTALL_VERSION_RE = /(?:^|[^0-9])(\d{4}\.\d+(?:\.\d+)?)(?![0-9])/;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 解析 JetBrains 配置目录名(`<Product><YYYY.N>`)。
|
|
290
|
+
* @returns {{product: object, version: string} | null}
|
|
291
|
+
*/
|
|
292
|
+
export function parseConfigDirName(name) {
|
|
293
|
+
if (typeof name !== "string" || !name) return null;
|
|
294
|
+
if (EXCLUDED_CONFIG_NAMES.has(name.toLowerCase())) return null;
|
|
295
|
+
const match = CONFIG_DIR_RE.exec(name);
|
|
296
|
+
if (!match) return null;
|
|
297
|
+
const product = CONFIG_PREFIX_MAP.get(match[1].toLowerCase());
|
|
298
|
+
if (!product) return null;
|
|
299
|
+
return { product, version: match[2] };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 从安装目录名 / .app 名 / Toolbox key 推断产品与版本。
|
|
304
|
+
* 解析不出产品时返回 null;版本缺失时 version 为空字符串。
|
|
305
|
+
*/
|
|
306
|
+
export function inferProductFromName(name) {
|
|
307
|
+
if (typeof name !== "string" || !name.trim()) return null;
|
|
308
|
+
const config = parseConfigDirName(name);
|
|
309
|
+
if (config) return config;
|
|
310
|
+
|
|
311
|
+
let base = name.trim();
|
|
312
|
+
if (/\.app$/i.test(base)) base = base.slice(0, -4);
|
|
313
|
+
const lower = base.toLowerCase();
|
|
314
|
+
const versionMatch = INSTALL_VERSION_RE.exec(base);
|
|
315
|
+
const version = versionMatch ? versionMatch[1] : "";
|
|
316
|
+
|
|
317
|
+
for (const product of PRODUCTS) {
|
|
318
|
+
if (product.nameMatchers.some((pattern) => pattern.test(lower))) {
|
|
319
|
+
return { product, version };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Toolbox key 精确匹配(IDEA-U / PyCharm-C / AndroidStudio 等)
|
|
323
|
+
const byToolboxKey = TOOLBOX_KEY_MAP.get(lower);
|
|
324
|
+
if (byToolboxKey) return { product: byToolboxKey, version };
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 从 plugins 目录反推产品。依次看自身、父目录、祖父目录,
|
|
330
|
+
* 以兼容 `.../<Product><Version>/plugins`、`.../<IDE 安装目录>/plugins`、`*.app/Contents/plugins`。
|
|
331
|
+
*/
|
|
332
|
+
export function inferProductFromPath(pluginsDir) {
|
|
333
|
+
if (typeof pluginsDir !== "string" || !pluginsDir) return null;
|
|
334
|
+
const parts = pluginsDir.split(/[\\/]+/).filter(Boolean);
|
|
335
|
+
const candidates = parts.slice(-4).reverse();
|
|
336
|
+
for (const part of candidates) {
|
|
337
|
+
const inferred = inferProductFromName(part);
|
|
338
|
+
if (inferred) return inferred;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** 产品对应的进程名:Windows 用 `.exe`,macOS/Linux 用无后缀的小写名。 */
|
|
344
|
+
export function getProcessNames(product, platform = process.platform) {
|
|
345
|
+
if (!product) return [];
|
|
346
|
+
return platform === "win32" ? product.winProcesses : product.unixProcesses;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function describeProduct(product, version = "") {
|
|
350
|
+
if (!product) return "未识别的 IDE";
|
|
351
|
+
return `${product.name}${version ? ` ${version}` : ""}`;
|
|
352
|
+
}
|
package/lib/prompt.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交互提示。只有交互模式才会真正加载 @inquirer/prompts,
|
|
3
|
+
* 非交互(CI/管道)路径不触发任何 TTY 依赖。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { info } from "./log.mjs";
|
|
7
|
+
import { formatTarget } from "./targets.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 多选目标;一个都不选时重新提问。
|
|
11
|
+
* @returns {Promise<Array>} 选中的目标
|
|
12
|
+
*/
|
|
13
|
+
export async function selectTargets(targets) {
|
|
14
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
15
|
+
for (;;) {
|
|
16
|
+
const selectedIds = await checkbox({
|
|
17
|
+
message: "选择要安装 ZCode 插件的 IDE(空格选择,回车确认)",
|
|
18
|
+
choices: targets.map((target) => ({
|
|
19
|
+
name: formatTarget(target),
|
|
20
|
+
value: target.id,
|
|
21
|
+
checked: targets.length === 1,
|
|
22
|
+
})),
|
|
23
|
+
pageSize: Math.min(15, Math.max(3, targets.length)),
|
|
24
|
+
loop: false,
|
|
25
|
+
});
|
|
26
|
+
if (selectedIds.length > 0) {
|
|
27
|
+
return targets.filter((target) => selectedIds.includes(target.id));
|
|
28
|
+
}
|
|
29
|
+
info("请至少选择一个 IDE。");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 关闭 IDE 前的二次确认。 */
|
|
34
|
+
export async function confirmClose(message) {
|
|
35
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
36
|
+
return confirm({ message, default: true });
|
|
37
|
+
}
|