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.
@@ -0,0 +1,415 @@
1
+ /**
2
+ * 目标发现与 --target 解析。
3
+ *
4
+ * 一个“目标”就是某个 IDE 的 plugins 目录;安装时会在其下创建/替换 `zcode-ide/`。
5
+ * 目标分两类:
6
+ * - config:JetBrains 配置目录下的 plugins(推荐,升级 IDE 不会丢;始终保留);
7
+ * - install:IDE 安装目录下的 plugins(次级选项,升级/重装 IDE 可能被清掉,
8
+ * 且 Windows 下通常需要管理员权限)。
9
+ *
10
+ * 对 install 目标会做真实写探针:在 plugins 目录(不存在则父目录)创建并删除一个
11
+ * 临时文件。Windows 的 fs.access(W_OK) 只反映只读属性,不足以判断 ACL 权限,
12
+ * 因此必须实际写一次。不可写的安装目录会被跳过,并在 skipped 中返回给调用方提示。
13
+ */
14
+
15
+ import fs from "node:fs";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+ import { UsageError } from "./errors.mjs";
19
+ import {
20
+ inferProductFromName,
21
+ inferProductFromPath,
22
+ parseConfigDirName,
23
+ TOOLBOX_KEY_MAP,
24
+ } from "./products.mjs";
25
+
26
+ /* -------------------------------------------------------------------------- */
27
+ /* --target 解析 */
28
+ /* -------------------------------------------------------------------------- */
29
+
30
+ /**
31
+ * 解析 `--target`:支持重复传参、逗号分隔、去空、去重、规范化路径。
32
+ * 传入的是 plugins 目录本身(不是它的父目录)。
33
+ */
34
+ export function parseTargetArgs(raw, cwd = process.cwd()) {
35
+ const values = [];
36
+ const push = (value) => {
37
+ if (typeof value === "string") values.push(...value.split(","));
38
+ else if (Array.isArray(value)) value.forEach(push);
39
+ };
40
+ push(raw);
41
+
42
+ const result = [];
43
+ const seen = new Set();
44
+ for (const value of values) {
45
+ const trimmed = value.trim();
46
+ if (!trimmed) continue;
47
+ const normalized = path.normalize(path.resolve(cwd, trimmed));
48
+ if (path.parse(normalized).root === normalized) {
49
+ throw new UsageError(`--target 不能指向文件系统根目录:${normalized}`);
50
+ }
51
+ const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
52
+ if (seen.has(key)) continue;
53
+ seen.add(key);
54
+ result.push(normalized);
55
+ }
56
+ return result;
57
+ }
58
+
59
+ /** 把 --target 路径包装成与发现结果同构的目标对象。 */
60
+ export function makeExplicitTarget(pluginsDir) {
61
+ const inferred = inferProductFromPath(pluginsDir);
62
+ const product = inferred?.product ?? null;
63
+ const version = inferred?.version ?? "";
64
+ return {
65
+ id: `target:${pluginsDir}`,
66
+ pluginsDir,
67
+ source: "target",
68
+ explicit: true,
69
+ productId: product?.id ?? null,
70
+ productName: product?.name ?? "未识别的 IDE",
71
+ version,
72
+ note: "",
73
+ };
74
+ }
75
+
76
+ /** 控制台展示用:`产品名 版本 — plugins 路径(备注)`。 */
77
+ export function formatTarget(target) {
78
+ const version = target.version ? ` ${target.version}` : "";
79
+ const note = target.note ? `(${target.note})` : "";
80
+ return `${target.productName}${version} — ${target.pluginsDir}${note}`;
81
+ }
82
+
83
+ /* -------------------------------------------------------------------------- */
84
+ /* 写探针 */
85
+ /* -------------------------------------------------------------------------- */
86
+
87
+ /**
88
+ * 判断安装目录目标是否可写:在 plugins 目录(不存在则父目录)创建并删除临时文件。
89
+ * @param {string} pluginsDir
90
+ * @param {{probe?: (directory: string) => boolean}} [options] 测试可注入判定函数
91
+ */
92
+ export function probeInstallTargetWritable(pluginsDir, options = {}) {
93
+ const probe = options.probe ?? defaultWriteProbe;
94
+ if (fs.existsSync(pluginsDir) && !isDirectory(pluginsDir)) return false;
95
+ const base = isDirectory(pluginsDir) ? pluginsDir : path.dirname(pluginsDir);
96
+ if (!isDirectory(base)) return false;
97
+ try {
98
+ return probe(base) === true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ /** 实际写探针:创建后立即删除;无法创建或删除都判定不可写。 */
105
+ export function defaultWriteProbe(directory) {
106
+ const file = path.join(
107
+ directory,
108
+ `.zcode-idea-write-test-${process.pid}-${Date.now().toString(36)}-${Math.random()
109
+ .toString(36)
110
+ .slice(2, 8)}`,
111
+ );
112
+ try {
113
+ fs.writeFileSync(file, "", { flag: "wx" });
114
+ } catch {
115
+ return false;
116
+ }
117
+ try {
118
+ fs.unlinkSync(file);
119
+ return true;
120
+ } catch {
121
+ try {
122
+ fs.unlinkSync(file);
123
+ } catch {
124
+ /* 忽略清理失败 */
125
+ }
126
+ return false;
127
+ }
128
+ }
129
+
130
+ /* -------------------------------------------------------------------------- */
131
+ /* 发现 */
132
+ /* -------------------------------------------------------------------------- */
133
+
134
+ /** 只返回目标列表(兼容旧调用方)。 */
135
+ export function discoverTargets(options = {}) {
136
+ return discoverTargetsDetailed(options).targets;
137
+ }
138
+
139
+ /**
140
+ * 返回 `{ targets, skipped }`:
141
+ * - targets:可用目标(config 始终保留;install 需通过写探针);
142
+ * - skipped:因不可写被跳过的 install 目标,供 CLI 打印一行提示。
143
+ */
144
+ export function discoverTargetsDetailed(options = {}) {
145
+ const platform = options.platform ?? process.platform;
146
+ const env = options.env ?? process.env;
147
+ const home = options.home ?? os.homedir();
148
+ const context = {
149
+ isWritable: options.isWritable ?? ((pluginsDir) => probeInstallTargetWritable(pluginsDir)),
150
+ skipped: [],
151
+ };
152
+
153
+ const targets = [];
154
+ if (platform === "win32") discoverWindows(targets, env, home, context);
155
+ else if (platform === "darwin") discoverMac(targets, env, home, context);
156
+ else discoverLinux(targets, env, home, context);
157
+
158
+ return {
159
+ targets: dedupeAndSort(targets),
160
+ skipped: dedupeSkipped(context.skipped),
161
+ };
162
+ }
163
+
164
+ function discoverWindows(out, env, home, context) {
165
+ const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
166
+ const localAppData = env.LOCALAPPDATA || path.join(home, "AppData", "Local");
167
+ const programFiles = env.PROGRAMFILES || "C:\\Program Files";
168
+
169
+ addConfigDirs(out, path.join(appData, "JetBrains"));
170
+ addConfigDirs(out, path.join(appData, "Google"), "androidstudio");
171
+
172
+ addInstallDirs(out, path.join(programFiles, "JetBrains"), context);
173
+ addInstallDirs(out, path.join(localAppData, "Programs"), context);
174
+ addToolboxDirs(out, path.join(localAppData, "JetBrains", "Toolbox", "apps"), context);
175
+
176
+ // Android Studio 的默认安装位置不在 JetBrains 目录下
177
+ addInstallHome(
178
+ out,
179
+ path.join(programFiles, "Android", "Android Studio"),
180
+ { product: TOOLBOX_KEY_MAP.get("androidstudio"), version: "" },
181
+ context,
182
+ );
183
+ }
184
+
185
+ function discoverMac(out, env, home, context) {
186
+ const appSupport = path.join(home, "Library", "Application Support");
187
+ addConfigDirs(out, path.join(appSupport, "JetBrains"));
188
+ addConfigDirs(out, path.join(appSupport, "Google"), "androidstudio");
189
+
190
+ addMacApps(out, "/Applications", context);
191
+ addMacApps(out, path.join(home, "Applications"), context);
192
+ addMacToolboxDirs(out, path.join(appSupport, "JetBrains", "Toolbox", "apps"), context);
193
+ }
194
+
195
+ function discoverLinux(out, env, home, context) {
196
+ const configHome = env.XDG_CONFIG_HOME || path.join(home, ".config");
197
+ addConfigDirs(out, path.join(configHome, "JetBrains"));
198
+ addConfigDirs(out, path.join(configHome, "Google"), "androidstudio");
199
+
200
+ addInstallDirs(out, "/opt", context);
201
+ addToolboxDirs(out, path.join(home, ".local", "share", "JetBrains", "Toolbox", "apps"), context);
202
+ addSnapDirs(out, "/snap", context);
203
+ }
204
+
205
+ /* ------------------------------ 配置目录 ------------------------------ */
206
+
207
+ function addConfigDirs(out, root, namePrefix) {
208
+ for (const entry of listDirs(root)) {
209
+ if (namePrefix && !entry.name.toLowerCase().startsWith(namePrefix.toLowerCase())) continue;
210
+ const parsed = parseConfigDirName(entry.name);
211
+ if (!parsed) continue;
212
+ out.push({
213
+ id: `config:${entry.path}`,
214
+ pluginsDir: path.join(entry.path, "plugins"),
215
+ source: "config",
216
+ explicit: false,
217
+ productId: parsed.product.id,
218
+ productName: parsed.product.name,
219
+ version: parsed.version,
220
+ note: "",
221
+ });
222
+ }
223
+ }
224
+
225
+ /* ------------------------------ 安装目录 ------------------------------ */
226
+
227
+ function addInstallDirs(out, root, context) {
228
+ for (const entry of listDirs(root)) {
229
+ const inferred = inferProductFromName(entry.name);
230
+ if (!inferred) continue;
231
+ if (!isDirectory(path.join(entry.path, "bin"))) continue;
232
+ addInstallHome(out, entry.path, inferred, context);
233
+ }
234
+ }
235
+
236
+ function addInstallHome(out, homeDir, inferred, context) {
237
+ if (!inferred?.product) return;
238
+ if (!isDirectory(path.join(homeDir, "bin"))) return;
239
+ const pluginsDir = path.join(homeDir, "plugins");
240
+ if (!isTargetWritable(pluginsDir, context)) {
241
+ context.skipped.push({
242
+ productId: inferred.product.id,
243
+ productName: inferred.product.name,
244
+ version: inferred.version || "",
245
+ pluginsDir,
246
+ source: "install",
247
+ });
248
+ return;
249
+ }
250
+ out.push({
251
+ id: `install:${pluginsDir}`,
252
+ pluginsDir,
253
+ source: "install",
254
+ explicit: false,
255
+ productId: inferred.product.id,
256
+ productName: inferred.product.name,
257
+ version: inferred.version || "",
258
+ note: "IDE 安装目录,升级可能被清掉",
259
+ });
260
+ }
261
+
262
+ /** Toolbox:apps/<Product>/<channel>/<version>/,其下再分 Windows/Linux 布局与 macOS .app 布局。 */
263
+ function addToolboxDirs(out, appsRoot, context) {
264
+ for (const productDir of listDirs(appsRoot)) {
265
+ const product =
266
+ TOOLBOX_KEY_MAP.get(productDir.name.toLowerCase()) ??
267
+ inferProductFromName(productDir.name)?.product ??
268
+ null;
269
+ if (!product) continue;
270
+ for (const channelDir of listDirs(productDir.path)) {
271
+ for (const versionDir of listDirs(channelDir.path)) {
272
+ addInstallHome(out, versionDir.path, { product, version: versionDir.name }, context);
273
+ }
274
+ }
275
+ }
276
+ }
277
+
278
+ function addMacApps(out, root, context) {
279
+ for (const entry of listDirs(root)) {
280
+ if (!/\.app$/i.test(entry.name)) continue;
281
+ const inferred = inferProductFromName(entry.name);
282
+ if (!inferred) continue;
283
+ addMacAppHome(out, entry.path, inferred, context);
284
+ }
285
+ }
286
+
287
+ function addMacToolboxDirs(out, appsRoot, context) {
288
+ for (const productDir of listDirs(appsRoot)) {
289
+ const product =
290
+ TOOLBOX_KEY_MAP.get(productDir.name.toLowerCase()) ??
291
+ inferProductFromName(productDir.name)?.product ??
292
+ null;
293
+ if (!product) continue;
294
+ for (const channelDir of listDirs(productDir.path)) {
295
+ for (const versionDir of listDirs(channelDir.path)) {
296
+ for (const appDir of listDirs(versionDir.path)) {
297
+ if (!/\.app$/i.test(appDir.name)) continue;
298
+ addMacAppHome(out, appDir.path, { product, version: versionDir.name }, context);
299
+ }
300
+ // 少数 Toolbox 布局直接把 .app 内容铺在版本目录下
301
+ addMacAppHome(out, versionDir.path, { product, version: versionDir.name }, context);
302
+ }
303
+ }
304
+ }
305
+ }
306
+
307
+ function addMacAppHome(out, appDir, inferred, context) {
308
+ const contents = path.join(appDir, "Contents");
309
+ if (!isDirectory(path.join(contents, "MacOS"))) return;
310
+ const pluginsDir = path.join(contents, "plugins");
311
+ if (!isTargetWritable(pluginsDir, context)) {
312
+ context.skipped.push({
313
+ productId: inferred.product.id,
314
+ productName: inferred.product.name,
315
+ version: inferred.version || "",
316
+ pluginsDir,
317
+ source: "install",
318
+ });
319
+ return;
320
+ }
321
+ out.push({
322
+ id: `install:${pluginsDir}`,
323
+ pluginsDir,
324
+ source: "install",
325
+ explicit: false,
326
+ productId: inferred.product.id,
327
+ productName: inferred.product.name,
328
+ version: inferred.version || "",
329
+ note: "IDE 安装目录,升级可能被清掉",
330
+ });
331
+ }
332
+
333
+ /** Snap:/snap/<package>/current/。产品从 snap 包名推断。 */
334
+ function addSnapDirs(out, snapRoot, context) {
335
+ for (const entry of listDirs(snapRoot)) {
336
+ const inferred = inferProductFromName(entry.name);
337
+ if (!inferred) continue;
338
+ const current = path.join(entry.path, "current");
339
+ if (!isDirectory(current)) continue;
340
+ addInstallHome(out, current, { product: inferred.product, version: inferred.version }, context);
341
+ }
342
+ }
343
+
344
+ /* ------------------------------ 工具函数 ------------------------------ */
345
+
346
+ function readDirents(dir) {
347
+ try {
348
+ return fs.readdirSync(dir, { withFileTypes: true });
349
+ } catch {
350
+ return [];
351
+ }
352
+ }
353
+
354
+ function listDirs(dir) {
355
+ return readDirents(dir)
356
+ .filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
357
+ .map((entry) => ({ name: entry.name, path: path.join(dir, entry.name) }))
358
+ .filter((entry) => isDirectory(entry.path));
359
+ }
360
+
361
+ function isDirectory(target) {
362
+ try {
363
+ return fs.statSync(target).isDirectory();
364
+ } catch {
365
+ return false;
366
+ }
367
+ }
368
+
369
+ function isTargetWritable(pluginsDir, context) {
370
+ try {
371
+ return context.isWritable(pluginsDir) === true;
372
+ } catch {
373
+ return false;
374
+ }
375
+ }
376
+
377
+ function compareVersionDesc(a, b) {
378
+ const left = String(a || "").split(".").map((part) => Number.parseInt(part, 10) || 0);
379
+ const right = String(b || "").split(".").map((part) => Number.parseInt(part, 10) || 0);
380
+ const length = Math.max(left.length, right.length);
381
+ for (let index = 0; index < length; index += 1) {
382
+ const diff = (right[index] || 0) - (left[index] || 0);
383
+ if (diff !== 0) return diff;
384
+ }
385
+ return 0;
386
+ }
387
+
388
+ function dedupeAndSort(targets) {
389
+ const byPath = new Map();
390
+ for (const target of targets) {
391
+ const key = process.platform === "win32" ? target.pluginsDir.toLowerCase() : target.pluginsDir;
392
+ const existing = byPath.get(key);
393
+ // 同一路径优先保留配置目录(推荐路径)
394
+ if (!existing || (existing.source === "install" && target.source === "config")) {
395
+ byPath.set(key, target);
396
+ }
397
+ }
398
+ return [...byPath.values()].sort((a, b) => {
399
+ const byName = a.productName.localeCompare(b.productName, "en");
400
+ if (byName !== 0) return byName;
401
+ return compareVersionDesc(a.version, b.version);
402
+ });
403
+ }
404
+
405
+ function dedupeSkipped(skipped) {
406
+ const seen = new Set();
407
+ const result = [];
408
+ for (const item of skipped) {
409
+ const key = process.platform === "win32" ? item.pluginsDir.toLowerCase() : item.pluginsDir;
410
+ if (seen.has(key)) continue;
411
+ seen.add(key);
412
+ result.push(item);
413
+ }
414
+ return result;
415
+ }
package/lib/unzip.mjs ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * 解压插件包到 plugins 目录。
3
+ *
4
+ * 行为:先删 `<pluginsDir>/zcode-ide`,再把 zip 中 `zcode-ide/` 前缀去掉后解压到
5
+ * `<pluginsDir>`,最终形成 `<pluginsDir>/zcode-ide/...`。
6
+ * 保留 unix 权限位;失败时清理半成品目录。
7
+ */
8
+
9
+ import { createWriteStream } from "node:fs";
10
+ import fsp from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { pipeline } from "node:stream/promises";
13
+ import { InstallError } from "./errors.mjs";
14
+ import { formatBytes, info } from "./log.mjs";
15
+ import {
16
+ isSymlinkEntry,
17
+ iterateZip,
18
+ openReadStream,
19
+ openZip,
20
+ validateZipEntryName,
21
+ } from "./manifest.mjs";
22
+
23
+ export const PLUGIN_DIR_NAME = "zcode-ide";
24
+
25
+ /**
26
+ * @param {object} params
27
+ * @param {string} params.zipPath
28
+ * @param {string} params.pluginsDir 目标 plugins 目录
29
+ * @param {number} [params.expectedEntries] 用于进度显示
30
+ * @param {number} [params.expectedBytes]
31
+ */
32
+ export async function extractPluginZip({
33
+ zipPath,
34
+ pluginsDir,
35
+ expectedEntries = 0,
36
+ expectedBytes = 0,
37
+ }) {
38
+ try {
39
+ await fsp.mkdir(pluginsDir, { recursive: true });
40
+ } catch (err) {
41
+ throw new InstallError(`无法创建目标目录 ${pluginsDir}:${err.message}`);
42
+ }
43
+
44
+ const pluginDir = path.join(pluginsDir, PLUGIN_DIR_NAME);
45
+ try {
46
+ await fsp.rm(pluginDir, { recursive: true, force: true, maxRetries: 3 });
47
+ } catch (err) {
48
+ throw new InstallError(
49
+ `无法删除旧版本目录 ${pluginDir}:${err.message}。请关闭占用该目录的 IDE 后重试。`,
50
+ );
51
+ }
52
+
53
+ const progress = createProgress(expectedEntries, expectedBytes);
54
+ let entries = 0;
55
+ let files = 0;
56
+ let bytes = 0;
57
+ const zip = await openZip(zipPath);
58
+
59
+ try {
60
+ await iterateZip(zip, async (entry) => {
61
+ const nameError = validateZipEntryName(entry.fileName);
62
+ if (nameError) {
63
+ throw new InstallError(
64
+ `插件包内含不安全条目「${entry.fileName}」:${nameError}。已中止安装。`,
65
+ );
66
+ }
67
+ if (isSymlinkEntry(entry)) {
68
+ throw new InstallError(`插件包内含符号链接条目「${entry.fileName}」,拒绝安装。`);
69
+ }
70
+
71
+ entries += 1;
72
+ const destination = safeJoin(pluginsDir, entry.fileName);
73
+
74
+ if (entry.fileName === PLUGIN_DIR_NAME || entry.fileName.endsWith("/")) {
75
+ await fsp.mkdir(destination, { recursive: true });
76
+ } else {
77
+ await fsp.mkdir(path.dirname(destination), { recursive: true });
78
+ const source = await openReadStream(zip, entry);
79
+ await pipeline(source, createWriteStream(destination, { mode: 0o644 }));
80
+ const mode = (entry.externalFileAttributes >>> 16) & 0o777;
81
+ if (mode) {
82
+ try {
83
+ await fsp.chmod(destination, mode);
84
+ } catch {
85
+ /* Windows 上 chmod 能力有限,忽略 */
86
+ }
87
+ }
88
+ files += 1;
89
+ bytes += entry.uncompressedSize;
90
+ }
91
+ progress.update(entries, files, bytes);
92
+ });
93
+ } catch (err) {
94
+ progress.finish();
95
+ try {
96
+ await fsp.rm(pluginDir, { recursive: true, force: true });
97
+ } catch {
98
+ /* 清理失败不掩盖原始错误 */
99
+ }
100
+ if (err instanceof InstallError) throw err;
101
+ throw new InstallError(
102
+ `解压失败(${zipPath} → ${pluginsDir}):${err.message}。已清理半成品目录。`,
103
+ );
104
+ } finally {
105
+ try {
106
+ zip.close();
107
+ } catch {
108
+ /* 已关闭 */
109
+ }
110
+ }
111
+
112
+ progress.finish();
113
+ return { pluginDir, entries, files, bytes };
114
+ }
115
+
116
+ function safeJoin(root, entryName) {
117
+ const rootResolved = path.resolve(root);
118
+ const destination = path.resolve(rootResolved, ...entryName.split("/"));
119
+ const relative = path.relative(rootResolved, destination);
120
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
121
+ throw new InstallError(`拒绝越界解压路径:${entryName}`);
122
+ }
123
+ return destination;
124
+ }
125
+
126
+ function createProgress(totalEntries, totalBytes) {
127
+ const tty = Boolean(process.stdout.isTTY);
128
+ let lastBucket = -1;
129
+ let printed = false;
130
+ return {
131
+ update(entries, files, bytes) {
132
+ const percent = totalEntries > 0 ? Math.min(100, Math.floor((entries / totalEntries) * 100)) : 0;
133
+ if (tty) {
134
+ printed = true;
135
+ process.stdout.write(
136
+ `\r[zcode-idea] 解压中 ${String(percent).padStart(3, " ")}%|文件 ${files}` +
137
+ (expectedBytesSuffix(totalBytes, bytes)) +
138
+ " ",
139
+ );
140
+ } else {
141
+ const bucket = Math.floor(percent / 10);
142
+ if (bucket > lastBucket) {
143
+ lastBucket = bucket;
144
+ info(`解压进度 ${percent}%(${files} 个文件,${formatBytes(bytes)})`);
145
+ }
146
+ }
147
+ },
148
+ finish() {
149
+ if (tty && printed) process.stdout.write("\n");
150
+ },
151
+ };
152
+ }
153
+
154
+ function expectedBytesSuffix(totalBytes, bytes) {
155
+ if (!totalBytes) return `|已写入 ${formatBytes(bytes)}`;
156
+ return `|已写入 ${formatBytes(bytes)}/${formatBytes(totalBytes)}`;
157
+ }
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "zcode-idea",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "把 ZCode IDE 插件安装到本机已安装的 JetBrains IDE(IntelliJ IDEA / WebStorm / PyCharm 等)",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "bin": {
8
+ "zcode-idea": "bin/zcode-idea.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "lib",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "test": "node --test test/*.test.mjs"
20
+ },
21
+ "keywords": [
22
+ "zcode",
23
+ "jetbrains",
24
+ "intellij",
25
+ "idea",
26
+ "plugin",
27
+ "installer"
28
+ ],
29
+ "publishConfig": {
30
+ "registry": "https://registry.npmjs.org/"
31
+ },
32
+ "dependencies": {
33
+ "@inquirer/prompts": "7.10.1",
34
+ "cac": "6.7.14",
35
+ "yauzl": "3.4.0"
36
+ }
37
+ }