@iflow-ai/iflow-cli 0.2.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iflow-ai/iflow-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "engines": {
5
5
  "node": ">=20.0.0"
6
6
  },
@@ -45,7 +45,7 @@
45
45
  "release:version": "node scripts/version.js",
46
46
  "telemetry": "node scripts/telemetry.js",
47
47
  "clean": "node scripts/clean.js",
48
- "postinstall": "node scripts/postinstall.js"
48
+ "postinstall": "node scripts/postinstall.js && node scripts/postinstallidea.js"
49
49
  },
50
50
  "bin": {
51
51
  "iflow": "bundle/iflow.js"
@@ -53,6 +53,7 @@
53
53
  "files": [
54
54
  "bundle/",
55
55
  "scripts/postinstall.js",
56
+ "scripts/postinstallidea.js",
56
57
  "README.md",
57
58
  "LICENSE"
58
59
  ],
@@ -87,7 +88,8 @@
87
88
  },
88
89
  "dependencies": {
89
90
  "gaxios": "^6.7.1",
90
- "jsonrepair": "^3.13.0"
91
+ "jsonrepair": "^3.13.0",
92
+ "unzipper": "^0.12.3"
91
93
  },
92
94
  "config": {
93
95
  "sandboxImageUri": "iflow-cli-sandbox"
@@ -0,0 +1,239 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { existsSync, mkdirSync, createWriteStream, statSync, readdirSync, createReadStream, unlinkSync, rmSync } from 'fs';
8
+ import { join, dirname, resolve } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import os from 'os';
11
+ import https from 'https';
12
+ import { createHash } from 'crypto';
13
+ import { pipeline } from 'stream/promises';
14
+ import { Extract } from 'unzipper';
15
+
16
+ // 常量定义
17
+ const PLUGIN_URL = 'https://cloud.iflow.cn/iflow-cli/iflow-idea-0.0.1.zip';
18
+ const PLUGIN_DIR_NAME = 'iflow-idea';
19
+ const TEMP_DIR_NAME = 'tstar-cli-idea-plugin';
20
+ const LOG_PREFIX = '[IDEA Extension]';
21
+ const IDEA_PATTERNS = ['IntelliJIdea', 'IdeaIC'];
22
+
23
+ // 获取当前脚本的目录
24
+ const __filename = fileURLToPath(import.meta.url);
25
+ const __dirname = dirname(__filename);
26
+
27
+ // 验证路径是否安全
28
+ function validatePath(path) {
29
+ if (!path || typeof path !== 'string') {
30
+ return false;
31
+ }
32
+
33
+ // 防止路径遍历攻击
34
+ const normalizedPath = resolve(path);
35
+ return !normalizedPath.includes('..');
36
+ }
37
+
38
+ // 检查文件是否存在且可执行
39
+ function isExecutable(filePath) {
40
+ try {
41
+ const stats = statSync(filePath);
42
+ return stats.isFile() && (stats.mode & parseInt('111', 8)) !== 0;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ // 获取 IntelliJ IDEA 插件目录的跨平台函数
49
+ function getIdeaPluginsDirectory() {
50
+ const platform = os.platform();
51
+ const homedir = os.homedir();
52
+
53
+ switch (platform) {
54
+ case 'darwin': // macOS
55
+ return join(homedir, 'Library', 'Application Support', 'JetBrains');
56
+ case 'win32': // Windows
57
+ return join(homedir, 'AppData', 'Roaming', 'JetBrains');
58
+ case 'linux': // Linux
59
+ return join(homedir, '.local', 'share', 'JetBrains');
60
+ default:
61
+ throw new Error(`Unsupported platform: ${platform}`);
62
+ }
63
+ }
64
+
65
+ // 查找最新版本的 IntelliJ IDEA 目录
66
+ function findLatestIdeaDirectory() {
67
+ const jetbrainsDir = getIdeaPluginsDirectory();
68
+
69
+ if (!existsSync(jetbrainsDir)) {
70
+ console.warn(`${LOG_PREFIX} JetBrains directory not found:`, jetbrainsDir);
71
+ return null;
72
+ }
73
+
74
+ try {
75
+ const dirs = readdirSync(jetbrainsDir)
76
+ .filter(dir => IDEA_PATTERNS.some(pattern => dir.startsWith(pattern)))
77
+ .map(dir => {
78
+ const fullPath = join(jetbrainsDir, dir);
79
+ try {
80
+ const stats = statSync(fullPath);
81
+ return { path: fullPath, mtime: stats.mtime };
82
+ } catch (error) {
83
+ console.warn(`${LOG_PREFIX} Cannot access directory ${fullPath}:`, error.message);
84
+ return null;
85
+ }
86
+ })
87
+ .filter(Boolean)
88
+ .sort((a, b) => b.mtime - a.mtime);
89
+
90
+ if (dirs.length === 0) {
91
+ console.warn(`${LOG_PREFIX} No IntelliJ IDEA installation found`);
92
+ return null;
93
+ }
94
+
95
+ return dirs[0].path;
96
+ } catch (error) {
97
+ console.error(`${LOG_PREFIX} Error finding IntelliJ IDEA directory:`, error.message);
98
+ return null;
99
+ }
100
+ }
101
+
102
+ // 下载插件
103
+ async function downloadPlugin(url, destPath) {
104
+ return new Promise((resolve, reject) => {
105
+ const file = createWriteStream(destPath);
106
+
107
+ https.get(url, (response) => {
108
+ if (response.statusCode !== 200) {
109
+ reject(new Error(`Failed to download plugin: ${response.statusCode}`));
110
+ return;
111
+ }
112
+
113
+ pipeline(response, file)
114
+ .then(() => resolve())
115
+ .catch(reject);
116
+ }).on('error', (err) => {
117
+ reject(err);
118
+ });
119
+ });
120
+ }
121
+
122
+ // 计算文件的 SHA-256 哈希值
123
+ async function calculateFileHash(filePath) {
124
+ return new Promise((resolve, reject) => {
125
+ try {
126
+ const hash = createHash('sha256');
127
+ const stream = createReadStream(filePath);
128
+
129
+ stream.on('data', (data) => hash.update(data));
130
+ stream.on('end', () => resolve(hash.digest('hex')));
131
+ stream.on('error', reject);
132
+ } catch (error) {
133
+ reject(error);
134
+ }
135
+ });
136
+ }
137
+
138
+ // 解压插件
139
+ async function extractPlugin(zipFilePath, destDir) {
140
+ return new Promise((resolve, reject) => {
141
+ createReadStream(zipFilePath)
142
+ .pipe(Extract({ path: destDir }))
143
+ .on('close', () => resolve())
144
+ .on('error', (err) => reject(err));
145
+ });
146
+ }
147
+
148
+ // 安装 IDEA 插件
149
+ async function installIdeaExtension() {
150
+ try {
151
+ // 查找 IDEA 目录
152
+ const ideaDir = findLatestIdeaDirectory();
153
+ if (!ideaDir) {
154
+ console.warn(`${LOG_PREFIX} Could not find IntelliJ IDEA installation, skipping plugin installation`);
155
+ return;
156
+ }
157
+
158
+ console.info(`${LOG_PREFIX} Found IntelliJ IDEA at: ${ideaDir}`);
159
+
160
+ // 创建插件目录路径
161
+ const pluginsDir = join(ideaDir, 'plugins');
162
+ if (!existsSync(pluginsDir)) {
163
+ try {
164
+ mkdirSync(pluginsDir, { recursive: true });
165
+ } catch (error) {
166
+ console.error(`${LOG_PREFIX} Failed to create plugins directory: ${pluginsDir}`, error.message);
167
+ return;
168
+ }
169
+ }
170
+
171
+ // 检查并删除已存在的插件目录
172
+ const existingPluginDir = join(pluginsDir, PLUGIN_DIR_NAME);
173
+ if (existsSync(existingPluginDir)) {
174
+ try {
175
+ console.info(`${LOG_PREFIX} Removing existing plugin directory: ${existingPluginDir}`);
176
+ rmSync(existingPluginDir, { recursive: true, force: true });
177
+ } catch (error) {
178
+ console.error(`${LOG_PREFIX} Failed to remove existing plugin directory: ${existingPluginDir}`, error.message);
179
+ // 继续安装,即使删除失败
180
+ }
181
+ }
182
+
183
+ // 创建临时下载目录
184
+ const tempDir = join(os.tmpdir(), TEMP_DIR_NAME);
185
+ if (!existsSync(tempDir)) {
186
+ try {
187
+ mkdirSync(tempDir, { recursive: true });
188
+ } catch (error) {
189
+ console.error(`${LOG_PREFIX} Failed to create temporary directory: ${tempDir}`, error.message);
190
+ return;
191
+ }
192
+ }
193
+
194
+ // 生成唯一的文件名
195
+ const pluginFileName = `tstar-idea-plugin-${Date.now()}.zip`;
196
+ const pluginFilePath = join(tempDir, pluginFileName);
197
+
198
+ console.info(`${LOG_PREFIX} Downloading plugin from: ${PLUGIN_URL}`);
199
+
200
+ try {
201
+ // 下载插件
202
+ await downloadPlugin(PLUGIN_URL, pluginFilePath);
203
+ console.info(`${LOG_PREFIX} Plugin downloaded to: ${pluginFilePath}`);
204
+
205
+ // 计算插件文件哈希值,用于验证下载是否完整
206
+ const fileHash = await calculateFileHash(pluginFilePath);
207
+ console.info(`${LOG_PREFIX} Plugin file hash: ${fileHash}`);
208
+
209
+ // 解压插件到插件目录
210
+ console.info(`${LOG_PREFIX} Extracting plugin to: ${pluginsDir}`);
211
+
212
+ // 直接解压到插件目录,不创建额外的子目录
213
+ await extractPlugin(pluginFilePath, pluginsDir);
214
+
215
+ console.info(`${LOG_PREFIX} ✅ Plugin installed successfully to: ${pluginsDir}`);
216
+
217
+ // 清理临时文件
218
+ try {
219
+ unlinkSync(pluginFilePath);
220
+ } catch (error) {
221
+ console.warn(`${LOG_PREFIX} Failed to clean up temporary file: ${pluginFilePath}`, error.message);
222
+ }
223
+
224
+ } catch (error) {
225
+ console.error(`${LOG_PREFIX} Failed to install plugin:`, error.message);
226
+ console.debug(`${LOG_PREFIX} Stack trace:`, error.stack);
227
+ }
228
+ } catch (error) {
229
+ console.error(`${LOG_PREFIX} Unexpected error during IntelliJ IDEA extension installation:`, error.message);
230
+ console.debug(`${LOG_PREFIX} Stack trace:`, error.stack);
231
+ }
232
+ }
233
+
234
+ // 只在非 CI 环境执行
235
+ if (!process.env.CI) {
236
+ installIdeaExtension().catch(error => {
237
+ console.error(`${LOG_PREFIX} Installation failed:`, error.message);
238
+ });
239
+ }