aicodeswitch 1.2.0 → 1.2.2
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/bin/update.js +125 -44
- package/package.json +3 -2
package/bin/update.js
CHANGED
|
@@ -2,16 +2,17 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const https = require('https');
|
|
5
|
+
const http = require('http');
|
|
5
6
|
const { spawn } = require('child_process');
|
|
6
7
|
const chalk = require('chalk');
|
|
7
8
|
const ora = require('ora');
|
|
8
9
|
const boxen = require('boxen');
|
|
10
|
+
const tar = require('tar');
|
|
9
11
|
|
|
10
12
|
const AICOSWITCH_DIR = path.join(os.homedir(), '.aicodeswitch');
|
|
11
13
|
const RELEASES_DIR = path.join(AICOSWITCH_DIR, 'releases');
|
|
12
14
|
const CURRENT_FILE = path.join(AICOSWITCH_DIR, 'current');
|
|
13
15
|
const PACKAGE_NAME = 'aicodeswitch';
|
|
14
|
-
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
15
16
|
|
|
16
17
|
// 确保目录存在
|
|
17
18
|
const ensureDir = (dirPath) => {
|
|
@@ -96,40 +97,67 @@ const getLatestVersion = () => {
|
|
|
96
97
|
});
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
//
|
|
100
|
-
const
|
|
100
|
+
// 下载 tarball 文件
|
|
101
|
+
const downloadTarball = (url, destPath) => {
|
|
101
102
|
return new Promise((resolve, reject) => {
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
'
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
const urlObj = new URL(url);
|
|
104
|
+
const protocol = urlObj.protocol === 'http:' ? http : https;
|
|
105
|
+
|
|
106
|
+
const requestOptions = {
|
|
107
|
+
hostname: urlObj.hostname,
|
|
108
|
+
port: urlObj.port,
|
|
109
|
+
path: urlObj.pathname + urlObj.search,
|
|
110
|
+
method: 'GET',
|
|
111
|
+
headers: {
|
|
112
|
+
'User-Agent': 'aicodeswitch'
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const req = protocol.request(requestOptions, (res) => {
|
|
117
|
+
if (res.statusCode !== 200) {
|
|
118
|
+
reject(new Error(`Failed to download: HTTP ${res.statusCode}`));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
123
|
+
res.pipe(fileStream);
|
|
124
|
+
|
|
125
|
+
fileStream.on('finish', () => {
|
|
126
|
+
fileStream.close();
|
|
127
|
+
resolve(destPath);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
fileStream.on('error', (err) => {
|
|
131
|
+
fs.unlink(destPath, () => {});
|
|
132
|
+
reject(err);
|
|
133
|
+
});
|
|
116
134
|
});
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
const packageDir = path.join(targetDir, 'node_modules', PACKAGE_NAME);
|
|
122
|
-
if (fs.existsSync(packageDir)) {
|
|
123
|
-
resolve(packageDir);
|
|
124
|
-
} else {
|
|
125
|
-
reject(new Error('Package installation directory not found'));
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
reject(new Error(`npm install failed: ${stderr}`));
|
|
136
|
+
req.on('error', (err) => {
|
|
137
|
+
if (fs.existsSync(destPath)) {
|
|
138
|
+
fs.unlink(destPath, () => {});
|
|
129
139
|
}
|
|
140
|
+
reject(err);
|
|
130
141
|
});
|
|
131
142
|
|
|
132
|
-
|
|
143
|
+
req.setTimeout(60000, () => {
|
|
144
|
+
req.destroy();
|
|
145
|
+
if (fs.existsSync(destPath)) {
|
|
146
|
+
fs.unlink(destPath, () => {});
|
|
147
|
+
}
|
|
148
|
+
reject(new Error('Download timeout'));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
req.end();
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// 解压 tarball 到指定目录
|
|
156
|
+
const extractTarball = (tarballPath, destDir) => {
|
|
157
|
+
return tar.x({
|
|
158
|
+
file: tarballPath,
|
|
159
|
+
cwd: destDir,
|
|
160
|
+
strip: 1, // 去掉 package 目录层级
|
|
133
161
|
});
|
|
134
162
|
};
|
|
135
163
|
|
|
@@ -159,6 +187,37 @@ const restart = () => {
|
|
|
159
187
|
});
|
|
160
188
|
};
|
|
161
189
|
|
|
190
|
+
// 清理旧版本的下载文件(保留最近 3 个版本)
|
|
191
|
+
const cleanupOldVersions = () => {
|
|
192
|
+
try {
|
|
193
|
+
if (!fs.existsSync(RELEASES_DIR)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const versions = fs.readdirSync(RELEASES_DIR)
|
|
198
|
+
.filter(item => {
|
|
199
|
+
const itemPath = path.join(RELEASES_DIR, item);
|
|
200
|
+
return fs.statSync(itemPath).isDirectory();
|
|
201
|
+
})
|
|
202
|
+
.sort((a, b) => {
|
|
203
|
+
// 按版本号降序排序
|
|
204
|
+
return compareVersions(b, a);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 保留最近 3 个版本,删除其他版本
|
|
208
|
+
if (versions.length > 3) {
|
|
209
|
+
const versionsToDelete = versions.slice(3);
|
|
210
|
+
versionsToDelete.forEach(version => {
|
|
211
|
+
const versionPath = path.join(RELEASES_DIR, version);
|
|
212
|
+
fs.rmSync(versionPath, { recursive: true, force: true });
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
// 清理失败不影响更新流程
|
|
217
|
+
console.error(chalk.yellow(`Warning: Failed to cleanup old versions: ${err.message}`));
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
162
221
|
// 主更新逻辑
|
|
163
222
|
const update = async () => {
|
|
164
223
|
console.log('\n');
|
|
@@ -182,7 +241,6 @@ const update = async () => {
|
|
|
182
241
|
if (comparison <= 0) {
|
|
183
242
|
console.log(chalk.yellow(`\n✓ You are already on the latest version (${chalk.bold(currentVersion)})\n`));
|
|
184
243
|
process.exit(0);
|
|
185
|
-
return;
|
|
186
244
|
}
|
|
187
245
|
|
|
188
246
|
console.log(chalk.cyan(`\n📦 Update available: ${chalk.bold(currentVersion)} → ${chalk.bold(latestVersion)}\n`));
|
|
@@ -190,34 +248,58 @@ const update = async () => {
|
|
|
190
248
|
// 确保目录存在
|
|
191
249
|
ensureDir(RELEASES_DIR);
|
|
192
250
|
|
|
193
|
-
//
|
|
194
|
-
const
|
|
195
|
-
|
|
251
|
+
// 创建版本目录
|
|
252
|
+
const versionDir = path.join(RELEASES_DIR, latestVersion);
|
|
253
|
+
ensureDir(versionDir);
|
|
254
|
+
|
|
255
|
+
// 下载 tarball
|
|
256
|
+
const downloadSpinner = ora({
|
|
257
|
+
text: chalk.cyan('Downloading from npm...'),
|
|
196
258
|
color: 'cyan'
|
|
197
259
|
}).start();
|
|
198
260
|
|
|
199
|
-
const
|
|
200
|
-
ensureDir(versionDir);
|
|
261
|
+
const tarballPath = path.join(versionDir, 'package.tgz');
|
|
201
262
|
|
|
202
263
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
264
|
+
await downloadTarball(latestInfo.tarball, tarballPath);
|
|
265
|
+
downloadSpinner.succeed(chalk.green('Download completed'));
|
|
205
266
|
} catch (err) {
|
|
206
|
-
|
|
267
|
+
downloadSpinner.fail(chalk.red('Download failed'));
|
|
207
268
|
console.log(chalk.red(`Error: ${err.message}\n`));
|
|
208
269
|
process.exit(1);
|
|
209
|
-
return;
|
|
210
270
|
}
|
|
211
271
|
|
|
212
|
-
//
|
|
213
|
-
const
|
|
214
|
-
|
|
272
|
+
// 解压 tarball
|
|
273
|
+
const extractSpinner = ora({
|
|
274
|
+
text: chalk.cyan('Extracting package...'),
|
|
275
|
+
color: 'cyan'
|
|
276
|
+
}).start();
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
await extractTarball(tarballPath, versionDir);
|
|
280
|
+
extractSpinner.succeed(chalk.green('Package extracted'));
|
|
281
|
+
} catch (err) {
|
|
282
|
+
extractSpinner.fail(chalk.red('Extraction failed'));
|
|
283
|
+
console.log(chalk.red(`Error: ${err.message}\n`));
|
|
284
|
+
process.exit(1);
|
|
285
|
+
} finally {
|
|
286
|
+
// 删除 tarball 文件
|
|
287
|
+
if (fs.existsSync(tarballPath)) {
|
|
288
|
+
fs.unlinkSync(tarballPath);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 更新 current 文件
|
|
293
|
+
updateCurrentFile(versionDir);
|
|
294
|
+
|
|
295
|
+
// 清理旧版本
|
|
296
|
+
cleanupOldVersions();
|
|
215
297
|
|
|
216
298
|
// 显示更新成功信息
|
|
217
299
|
console.log(boxen(
|
|
218
300
|
chalk.green.bold('✨ Update Successful!\n\n') +
|
|
219
301
|
chalk.white('Version: ') + chalk.cyan.bold(latestVersion) + '\n' +
|
|
220
|
-
chalk.white('Location: ') + chalk.gray(
|
|
302
|
+
chalk.white('Location: ') + chalk.gray(versionDir) + '\n\n' +
|
|
221
303
|
chalk.gray('Restarting server with the new version...'),
|
|
222
304
|
{
|
|
223
305
|
padding: 1,
|
|
@@ -234,7 +316,6 @@ const update = async () => {
|
|
|
234
316
|
console.log(chalk.yellow(`\n⚠️ Update completed, but restart failed: ${err.message}`));
|
|
235
317
|
console.log(chalk.cyan('Please manually run: ') + chalk.yellow('aicos restart\n'));
|
|
236
318
|
process.exit(1);
|
|
237
|
-
return;
|
|
238
319
|
}
|
|
239
320
|
|
|
240
321
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicodeswitch",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "A tool to help you manage AI programming tools to access large language models locally. It allows your Claude Code, Codex and other tools to no longer be limited to official models.",
|
|
5
5
|
"author": "tangshuang",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"express": "^4.18.2",
|
|
56
56
|
"jsonwebtoken": "^9.0.3",
|
|
57
57
|
"level": "^8.0.0",
|
|
58
|
-
"ora": "^5.4.1"
|
|
58
|
+
"ora": "^5.4.1",
|
|
59
|
+
"tar": "^7.4.3"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@types/better-sqlite3": "^7.6.9",
|