aliyun-cli-npm 3.3.17
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/LICENSE +21 -0
- package/README.md +285 -0
- package/bin/.gitkeep +1 -0
- package/lib/downloader.js +257 -0
- package/lib/executor.js +62 -0
- package/lib/main.js +14 -0
- package/lib/platform.js +56 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vtxf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# aliyun-cli-npm
|
|
2
|
+
|
|
3
|
+
阿里云 CLI (Alibaba Cloud CLI) 的 npm 封装包,支持按需下载,轻量高效。
|
|
4
|
+
|
|
5
|
+
[aliyun-cli](https://github.com/aliyun/aliyun-cli) 是阿里云官方提供的命令行工具,用于管理和调用阿里云服务。本项目提供智能的按需下载机制,首次使用时自动下载对应平台的二进制文件。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- ✅ **智能下载**:首次运行时自动检测平台并下载对应二进制文件
|
|
10
|
+
- ✅ **多源支持**:支持阿里云官方 CDN、GitHub Releases 等多个下载源
|
|
11
|
+
- ✅ **跨平台支持**:支持 Windows、macOS、Linux 主要平台
|
|
12
|
+
- ✅ **轻量级**:npm 包仅包含 JavaScript 代码,几 KB 大小
|
|
13
|
+
- ✅ **自动更新**:支持下载最新版本的官方二进制文件
|
|
14
|
+
- ✅ **零依赖**:只使用 Node.js 原生模块
|
|
15
|
+
- ✅ **官方二进制**:直接使用阿里云官方发布的二进制文件
|
|
16
|
+
- ✅ **智能重试**:下载失败时自动尝试备用源
|
|
17
|
+
|
|
18
|
+
## 支持的平台
|
|
19
|
+
|
|
20
|
+
- **Windows 64位** (windows-amd64)
|
|
21
|
+
- **macOS Intel** (mac-amd64)
|
|
22
|
+
- **macOS Apple Silicon** (mac-arm64)
|
|
23
|
+
- **Linux 64位** (linux-amd64)
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
### 使用 npx(推荐)
|
|
28
|
+
|
|
29
|
+
无需安装,直接使用:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx aliyun-cli-npm version
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
首次运行时会自动下载对应平台的二进制文件。
|
|
36
|
+
|
|
37
|
+
### 全局安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g aliyun-cli-npm
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
安装后可以直接使用 `aliyun` 命令:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
aliyun version
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 项目中使用
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install aliyun-cli-npm
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
然后使用 npx:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx aliyun-cli-npm configure
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
或者在 package.json 的 scripts 中添加:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"scripts": {
|
|
66
|
+
"aliyun": "aliyun"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 使用示例
|
|
72
|
+
|
|
73
|
+
### 首次使用(自动下载)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx aliyun-cli-npm help
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
首次运行时会显示下载进度:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
🔍 Binary not found, initiating download...
|
|
83
|
+
|
|
84
|
+
📡 Trying source: Aliyun CDN (https://aliyuncli.alicdn.com/...)
|
|
85
|
+
⏳ Downloading: 45.2% (45.2MB/100.0MB) - 1250KB/s - 43s remaining
|
|
86
|
+
✅ Download completed!
|
|
87
|
+
📦 Extracting archive...
|
|
88
|
+
✅ Extraction completed!
|
|
89
|
+
✅ Successfully installed aliyun-cli to /path/to/bin/windows-amd64/aliyun.exe
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 查看版本
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx aliyun-cli-npm version
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 配置凭证
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx aliyun-cli-npm configure
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 调用阿里云 API
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 查询 ECS 区域列表
|
|
108
|
+
npx aliyun-cli-npm ecs DescribeRegions
|
|
109
|
+
|
|
110
|
+
# 查询 RDS 实例
|
|
111
|
+
npx aliyun-cli-npm rds DescribeDBInstances --PageSize 50
|
|
112
|
+
|
|
113
|
+
# 查询 OSS 存储空间
|
|
114
|
+
npx aliyun-cli-npm oss ListBuckets
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 获取帮助
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# 查看所有产品
|
|
121
|
+
npx aliyun-cli-npm help
|
|
122
|
+
|
|
123
|
+
# 查看特定产品的帮助
|
|
124
|
+
npx aliyun-cli-npm help ecs
|
|
125
|
+
|
|
126
|
+
# 查看特定 API 的帮助
|
|
127
|
+
npx aliyun-cli-npm help ecs CreateInstance
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 下载源优先级
|
|
131
|
+
|
|
132
|
+
系统按以下优先级尝试下载源:
|
|
133
|
+
|
|
134
|
+
1. **阿里云官方 CDN**(最快,推荐)
|
|
135
|
+
- 下载地址:`https://aliyuncli.alicdn.com/`
|
|
136
|
+
- 自动提供最新版本
|
|
137
|
+
|
|
138
|
+
2. **GitHub Releases**(备用源)
|
|
139
|
+
- 下载地址:`https://github.com/aliyun/aliyun-cli/releases`
|
|
140
|
+
- 可指定特定版本
|
|
141
|
+
|
|
142
|
+
3. **GitHub Mirror**(备用源)
|
|
143
|
+
- 使用国内镜像加速 GitHub 访问
|
|
144
|
+
- 适用于网络受限情况
|
|
145
|
+
|
|
146
|
+
如某个源下载失败,系统会自动尝试下一个源。
|
|
147
|
+
|
|
148
|
+
## 二进制文件存储
|
|
149
|
+
|
|
150
|
+
下载的二进制文件存储位置:
|
|
151
|
+
|
|
152
|
+
- **Windows**: `node_modules/aliyun-cli-npm/bin/windows-amd64/aliyun.exe`
|
|
153
|
+
- **macOS Intel**: `node_modules/aliyun-cli-npm/bin/mac-amd64/aliyun`
|
|
154
|
+
- **macOS Apple Silicon**: `node_modules/aliyun-cli-npm/bin/mac-arm64/aliyun`
|
|
155
|
+
- **Linux**: `node_modules/aliyun-cli-npm/bin/linux-amd64/aliyun`
|
|
156
|
+
|
|
157
|
+
### 手动管理二进制文件
|
|
158
|
+
|
|
159
|
+
如果需要重新下载或更换版本:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# 删除现有二进制文件
|
|
163
|
+
rm -rf node_modules/aliyun-cli-npm/bin/*/
|
|
164
|
+
|
|
165
|
+
# 下次运行时会自动重新下载
|
|
166
|
+
npx aliyun-cli-npm version
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 版本说明
|
|
170
|
+
|
|
171
|
+
本包支持两种版本模式:
|
|
172
|
+
|
|
173
|
+
1. **自动获取最新版本**:从阿里云官方 CDN 下载最新版本
|
|
174
|
+
2. **锁定特定版本**:从 GitHub Releases 下载指定版本
|
|
175
|
+
|
|
176
|
+
当前默认配置:从官方 CDN 获取最新版本。
|
|
177
|
+
|
|
178
|
+
## 故障排除
|
|
179
|
+
|
|
180
|
+
### 下载失败
|
|
181
|
+
|
|
182
|
+
如果遇到下载失败,请检查:
|
|
183
|
+
|
|
184
|
+
1. **网络连接**:确保能够访问互联网
|
|
185
|
+
2. **防火墙设置**:某些网络可能阻止外网访问
|
|
186
|
+
3. **代理配置**:如使用代理,确保 Node.js 环境变量正确配置
|
|
187
|
+
4. **重试机制**:系统会自动尝试多个下载源
|
|
188
|
+
|
|
189
|
+
### 找不到二进制文件
|
|
190
|
+
|
|
191
|
+
如果出现 "找不到对应平台的aliyun二进制文件" 错误:
|
|
192
|
+
|
|
193
|
+
1. 删除 `bin/` 目录内容,重新下载
|
|
194
|
+
2. 检查磁盘空间是否充足
|
|
195
|
+
3. 查看错误信息中的具体原因
|
|
196
|
+
|
|
197
|
+
### 权限错误(Unix 系统)
|
|
198
|
+
|
|
199
|
+
在 macOS 或 Linux 上,如果遇到权限错误:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
chmod +x node_modules/aliyun-cli-npm/bin/*/aliyun
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 其他问题
|
|
206
|
+
|
|
207
|
+
如遇到其他问题,请访问:
|
|
208
|
+
- [aliyun-cli GitHub Issues](https://github.com/aliyun/aliyun-cli/issues)
|
|
209
|
+
- [阿里云官方文档](https://www.alibabacloud.com/help)
|
|
210
|
+
|
|
211
|
+
## 包大小对比
|
|
212
|
+
|
|
213
|
+
**传统方案(内置所有平台)**:
|
|
214
|
+
- npm 包大小:~144 MB
|
|
215
|
+
- 下载时间:较慢
|
|
216
|
+
- 磁盘占用:~565 MB(包含所有平台)
|
|
217
|
+
|
|
218
|
+
**按需下载方案(当前)**:
|
|
219
|
+
- npm 包大小:~几 KB
|
|
220
|
+
- 下载时间:快速
|
|
221
|
+
- 磁盘占用:~95 MB(仅当前平台)
|
|
222
|
+
|
|
223
|
+
## 开发
|
|
224
|
+
|
|
225
|
+
### 项目结构
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
aliyun-cli-npm/
|
|
229
|
+
├── bin/ # 二进制文件目录(运行时下载)
|
|
230
|
+
│ ├── windows-amd64/ # Windows 64位
|
|
231
|
+
│ ├── mac-amd64/ # macOS Intel
|
|
232
|
+
│ ├── mac-arm64/ # macOS Apple Silicon
|
|
233
|
+
│ └── linux-amd64/ # Linux 64位
|
|
234
|
+
├── lib/ # JavaScript 封装代码
|
|
235
|
+
│ ├── main.js # CLI 入口点
|
|
236
|
+
│ ├── platform.js # 平台检测模块
|
|
237
|
+
│ ├── executor.js # 二进制执行器
|
|
238
|
+
│ └── downloader.js # 多源下载器
|
|
239
|
+
├── package.json
|
|
240
|
+
└── README.md
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 本地测试
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# 克隆项目
|
|
247
|
+
git clone https://github.com/vtxf/aliyun-cli-npm.git
|
|
248
|
+
cd aliyun-cli-npm
|
|
249
|
+
|
|
250
|
+
# 本地链接
|
|
251
|
+
npm link
|
|
252
|
+
|
|
253
|
+
# 测试(会触发首次下载)
|
|
254
|
+
aliyun version
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 许可证
|
|
258
|
+
|
|
259
|
+
MIT License
|
|
260
|
+
|
|
261
|
+
本项目使用 MIT 许可证。aliyun-cli 二进制文件的使用遵循阿里云官方许可证。
|
|
262
|
+
|
|
263
|
+
## 致谢
|
|
264
|
+
|
|
265
|
+
- [阿里云官方 aliyun-cli 项目](https://github.com/aliyun/aliyun-cli)
|
|
266
|
+
- [vtxf-ossutil 项目](https://github.com/vtxf/ossutil-npm) - 参考了其实现方式
|
|
267
|
+
|
|
268
|
+
## 链接
|
|
269
|
+
|
|
270
|
+
- [aliyun-cli GitHub](https://github.com/aliyun/aliyun-cli)
|
|
271
|
+
- [aliyun-cli 官方文档](https://www.alibabacloud.com/help/doc-detail/110341.htm)
|
|
272
|
+
- [npm 包页面](https://www.npmjs.com/package/aliyun-cli-npm)
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
**注意**:本项目为第三方封装的 npm 包,非阿里云官方产品。如有问题建议先到 [aliyun-cli 官方仓库](https://github.com/aliyun/aliyun-cli) 查找解决方案。
|
|
277
|
+
|
|
278
|
+
## 更新日志
|
|
279
|
+
|
|
280
|
+
### v3.3.17(按需下载版本)
|
|
281
|
+
- ✅ 实现多源下载机制
|
|
282
|
+
- ✅ 支持按需下载对应平台二进制文件
|
|
283
|
+
- ✅ 添加下载进度显示
|
|
284
|
+
- ✅ 智能重试和错误处理
|
|
285
|
+
- ✅ 大幅减小 npm 包体积(从 144MB 到几 KB)
|
package/bin/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bin/*
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { createReadStream, createWriteStream } = require('fs');
|
|
6
|
+
const { promisify } = require('util');
|
|
7
|
+
const pipeline = promisify(require('stream').pipeline);
|
|
8
|
+
const { getPlatformInfo } = require('./platform');
|
|
9
|
+
|
|
10
|
+
const VERSION = '3.3.17';
|
|
11
|
+
|
|
12
|
+
const DOWNLOAD_SOURCES = [
|
|
13
|
+
{
|
|
14
|
+
name: 'Aliyun CDN',
|
|
15
|
+
priority: 1,
|
|
16
|
+
urls: {
|
|
17
|
+
'windows-amd64': 'https://aliyuncli.alicdn.com/aliyun-cli-windows-latest-amd64.zip',
|
|
18
|
+
'mac-amd64': 'https://aliyuncli.alicdn.com/aliyun-cli-macosx-latest-universal.tgz',
|
|
19
|
+
'mac-arm64': 'https://aliyuncli.alicdn.com/aliyun-cli-macosx-latest-universal.tgz',
|
|
20
|
+
'linux-amd64': 'https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz'
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'GitHub Releases',
|
|
25
|
+
priority: 2,
|
|
26
|
+
urls: {
|
|
27
|
+
'windows-amd64': `https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-windows-amd64.zip`,
|
|
28
|
+
'mac-amd64': `https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-macosx-amd64.tgz`,
|
|
29
|
+
'mac-arm64': `https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-macosx-arm64.tgz`,
|
|
30
|
+
'linux-amd64': `https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-linux-amd64.tgz`
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'GitHub Mirror',
|
|
35
|
+
priority: 3,
|
|
36
|
+
urls: {
|
|
37
|
+
'windows-amd64': `https://ghproxy.com/https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-windows-amd64.zip`,
|
|
38
|
+
'mac-amd64': `https://ghproxy.com/https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-macosx-amd64.tgz`,
|
|
39
|
+
'mac-arm64': `https://ghproxy.com/https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-macosx-arm64.tgz`,
|
|
40
|
+
'linux-amd64': `https://ghproxy.com/https://github.com/aliyun/aliyun-cli/releases/download/v${VERSION}/aliyun-cli-linux-amd64.tgz`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function getDownloadSources(platformKey) {
|
|
46
|
+
return DOWNLOAD_SOURCES
|
|
47
|
+
.map(source => ({
|
|
48
|
+
...source,
|
|
49
|
+
url: source.urls[platformKey]
|
|
50
|
+
}))
|
|
51
|
+
.filter(source => source.url)
|
|
52
|
+
.sort((a, b) => a.priority - b.priority);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getDownloadUrl() {
|
|
56
|
+
const platformInfo = getPlatformInfo();
|
|
57
|
+
const platformKey = `${platformInfo.platform}-${platformInfo.arch}`;
|
|
58
|
+
const sources = getDownloadSources(platformKey);
|
|
59
|
+
return sources[0]?.url;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function downloadWithProgress(url, destPath) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
65
|
+
const tempPath = destPath + '.download';
|
|
66
|
+
|
|
67
|
+
const options = {
|
|
68
|
+
headers: {
|
|
69
|
+
'User-Agent': 'aliyun-cli-npm'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
protocol.get(url, options, (response) => {
|
|
74
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
75
|
+
const redirectUrl = response.headers.location;
|
|
76
|
+
console.log(`Following redirect to: ${redirectUrl}`);
|
|
77
|
+
downloadWithProgress(redirectUrl, destPath).then(resolve).catch(reject);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (response.statusCode !== 200) {
|
|
82
|
+
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const totalSize = parseInt(response.headers['content-length'], 10) || 0;
|
|
87
|
+
let downloadedSize = 0;
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
|
|
90
|
+
const file = createWriteStream(tempPath);
|
|
91
|
+
|
|
92
|
+
response.on('data', (chunk) => {
|
|
93
|
+
downloadedSize += chunk.length;
|
|
94
|
+
|
|
95
|
+
if (totalSize > 0) {
|
|
96
|
+
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
97
|
+
const speed = Math.round(downloadedSize / ((Date.now() - startTime) / 1000) / 1024);
|
|
98
|
+
const remaining = Math.round((totalSize - downloadedSize) / speed / 1024);
|
|
99
|
+
|
|
100
|
+
process.stdout.write(`\r⏳ Downloading: ${progress}% (${(downloadedSize / 1024 / 1024).toFixed(1)}MB/${(totalSize / 1024 / 1024).toFixed(1)}MB) - ${speed}KB/s - ${remaining}s remaining`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
response.pipe(file);
|
|
105
|
+
|
|
106
|
+
file.on('finish', () => {
|
|
107
|
+
file.close();
|
|
108
|
+
console.log('\n✅ Download completed!');
|
|
109
|
+
|
|
110
|
+
fs.renameSync(tempPath, destPath);
|
|
111
|
+
resolve(destPath);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
file.on('error', (err) => {
|
|
115
|
+
fs.unlink(tempPath, () => {});
|
|
116
|
+
reject(err);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
response.on('error', (err) => {
|
|
120
|
+
file.destroy();
|
|
121
|
+
fs.unlink(tempPath, () => {});
|
|
122
|
+
reject(err);
|
|
123
|
+
});
|
|
124
|
+
}).on('error', (err) => {
|
|
125
|
+
reject(new Error(`Network error: ${err.message}`));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function extractArchive(archivePath, destDir) {
|
|
131
|
+
const { execSync } = require('child_process');
|
|
132
|
+
|
|
133
|
+
console.log('📦 Extracting archive...');
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
if (archivePath.endsWith('.zip')) {
|
|
137
|
+
if (process.platform === 'win32') {
|
|
138
|
+
execSync(`tar -xf "${archivePath}" -C "${destDir}"`, { stdio: 'inherit' });
|
|
139
|
+
} else {
|
|
140
|
+
execSync(`unzip -o "${archivePath}" -d "${destDir}"`, { stdio: 'inherit' });
|
|
141
|
+
}
|
|
142
|
+
} else if (archivePath.endsWith('.tgz') || archivePath.endsWith('.tar.gz')) {
|
|
143
|
+
execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: 'inherit' });
|
|
144
|
+
} else {
|
|
145
|
+
throw new Error('Unsupported archive format');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log('✅ Extraction completed!');
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(`Extraction failed: ${error.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function findExecutableInDir(dir) {
|
|
156
|
+
const platformInfo = getPlatformInfo();
|
|
157
|
+
const executableName = platformInfo.platform === 'windows' ? 'aliyun.exe' : 'aliyun';
|
|
158
|
+
|
|
159
|
+
function searchDir(currentDir) {
|
|
160
|
+
const items = fs.readdirSync(currentDir);
|
|
161
|
+
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
const fullPath = path.join(currentDir, item);
|
|
164
|
+
const stat = fs.statSync(fullPath);
|
|
165
|
+
|
|
166
|
+
if (stat.isDirectory()) {
|
|
167
|
+
const result = searchDir(fullPath);
|
|
168
|
+
if (result) return result;
|
|
169
|
+
} else if (item === executableName) {
|
|
170
|
+
return fullPath;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return searchDir(dir);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function downloadBinary() {
|
|
181
|
+
const platformInfo = getPlatformInfo();
|
|
182
|
+
const platformKey = `${platformInfo.platform}-${platformInfo.arch}`;
|
|
183
|
+
|
|
184
|
+
if (!DOWNLOAD_SOURCES[0].urls[platformKey]) {
|
|
185
|
+
throw new Error(`Unsupported platform: ${platformKey}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
189
|
+
const platformDir = path.join(binDir, platformKey);
|
|
190
|
+
const tempDir = path.join(binDir, 'temp-' + platformKey);
|
|
191
|
+
|
|
192
|
+
const executableName = platformInfo.platform === 'windows' ? 'aliyun.exe' : 'aliyun';
|
|
193
|
+
const executablePath = path.join(platformDir, executableName);
|
|
194
|
+
|
|
195
|
+
if (fs.existsSync(executablePath)) {
|
|
196
|
+
console.log('✅ Binary already exists, skipping download.');
|
|
197
|
+
return executablePath;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const sources = getDownloadSources(platformKey);
|
|
201
|
+
|
|
202
|
+
for (const source of sources) {
|
|
203
|
+
try {
|
|
204
|
+
console.log(`📡 Trying source: ${source.name} (${source.url})`);
|
|
205
|
+
|
|
206
|
+
await fs.promises.mkdir(tempDir, { recursive: true });
|
|
207
|
+
await fs.promises.mkdir(platformDir, { recursive: true });
|
|
208
|
+
|
|
209
|
+
const fileName = path.basename(source.url);
|
|
210
|
+
const archivePath = path.join(tempDir, fileName);
|
|
211
|
+
|
|
212
|
+
await downloadWithProgress(source.url, archivePath);
|
|
213
|
+
|
|
214
|
+
await extractArchive(archivePath, tempDir);
|
|
215
|
+
|
|
216
|
+
const extractedBinary = await findExecutableInDir(tempDir);
|
|
217
|
+
|
|
218
|
+
if (!extractedBinary) {
|
|
219
|
+
throw new Error('Could not find executable in extracted archive');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fs.copyFileSync(extractedBinary, executablePath);
|
|
223
|
+
|
|
224
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
225
|
+
|
|
226
|
+
if (process.platform !== 'win32') {
|
|
227
|
+
fs.chmodSync(executablePath, '755');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log(`✅ Successfully installed aliyun-cli to ${executablePath}`);
|
|
231
|
+
return executablePath;
|
|
232
|
+
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(`❌ Failed to download from ${source.name}: ${error.message}`);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
if (fs.existsSync(tempDir)) {
|
|
238
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
} catch (cleanupError) {
|
|
241
|
+
console.error('Cleanup error:', cleanupError.message);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (source === sources[sources.length - 1]) {
|
|
245
|
+
throw new Error(`All download sources failed. Last error: ${error.message}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('⏭️ Trying next source...\n');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
downloadBinary,
|
|
255
|
+
getDownloadUrl,
|
|
256
|
+
DOWNLOAD_SOURCES
|
|
257
|
+
};
|
package/lib/executor.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { getExecutablePath } = require('./platform');
|
|
4
|
+
const { downloadBinary } = require('./downloader');
|
|
5
|
+
|
|
6
|
+
async function executeAliyunCli(args) {
|
|
7
|
+
let executablePath = getExecutablePath();
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(executablePath)) {
|
|
10
|
+
console.log('🔍 Binary not found, initiating download...\n');
|
|
11
|
+
try {
|
|
12
|
+
executablePath = await downloadBinary();
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('\n❌ Failed to download aliyun-cli binary.');
|
|
15
|
+
console.error('Error:', error.message);
|
|
16
|
+
console.error('\n💡 Troubleshooting tips:');
|
|
17
|
+
console.error('1. Check your internet connection');
|
|
18
|
+
console.error('2. Try again later - download sources might be temporarily unavailable');
|
|
19
|
+
console.error('3. If the problem persists, you can manually download and install:');
|
|
20
|
+
console.error(' - Visit: https://github.com/aliyun/aliyun-cli/releases');
|
|
21
|
+
console.error(' - Download the binary for your platform');
|
|
22
|
+
console.error(` - Place it in: ${executablePath}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(executablePath)) {
|
|
28
|
+
console.error(`❌ Error: Cannot find aliyun binary at: ${executablePath}`);
|
|
29
|
+
console.error('Please check if npm package is correctly installed.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const platform = process.platform;
|
|
34
|
+
|
|
35
|
+
if (platform !== 'win32') {
|
|
36
|
+
try {
|
|
37
|
+
fs.chmodSync(executablePath, '755');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.warn(`⚠️ Warning: Unable to set executable permissions: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const aliyun = spawn(executablePath, args, {
|
|
44
|
+
stdio: 'inherit'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
aliyun.on('error', (err) => {
|
|
48
|
+
console.error(`❌ Error starting aliyun: ${err.message}`);
|
|
49
|
+
console.error('The binary file might be corrupted or incompatible with your system.');
|
|
50
|
+
console.error('💡 Try deleting the binary and run the command again to re-download.');
|
|
51
|
+
console.error(`Binary location: ${executablePath}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
aliyun.on('exit', (code) => {
|
|
56
|
+
process.exit(code || 0);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
executeAliyunCli
|
|
62
|
+
};
|
package/lib/main.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { executeAliyunCli } = require('./executor');
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
(async () => {
|
|
8
|
+
try {
|
|
9
|
+
await executeAliyunCli(args);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error(`Error: ${error.message}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
})();
|
package/lib/platform.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function getPlatformInfo() {
|
|
5
|
+
const platform = os.platform();
|
|
6
|
+
const arch = os.arch();
|
|
7
|
+
|
|
8
|
+
const platformMap = {
|
|
9
|
+
'win32': 'windows',
|
|
10
|
+
'darwin': 'mac',
|
|
11
|
+
'linux': 'linux'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const archMap = {
|
|
15
|
+
'x32': '386',
|
|
16
|
+
'x64': 'amd64',
|
|
17
|
+
'arm': 'arm',
|
|
18
|
+
'arm64': 'arm64',
|
|
19
|
+
'ia32': '386'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const aliyunPlatform = platformMap[platform] || platform;
|
|
23
|
+
const aliyunArch = archMap[arch] || arch;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
platform: aliyunPlatform,
|
|
27
|
+
arch: aliyunArch,
|
|
28
|
+
original: { platform, arch }
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getExecutablePath() {
|
|
33
|
+
const platformInfo = getPlatformInfo();
|
|
34
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
35
|
+
const platformDir = `${platformInfo.platform}-${platformInfo.arch}`;
|
|
36
|
+
|
|
37
|
+
const executableDir = path.join(binDir, platformDir);
|
|
38
|
+
|
|
39
|
+
if (platformInfo.platform === 'windows') {
|
|
40
|
+
return path.join(executableDir, 'aliyun.exe');
|
|
41
|
+
} else {
|
|
42
|
+
return path.join(executableDir, 'aliyun');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getBinaryDirectory() {
|
|
47
|
+
const platformInfo = getPlatformInfo();
|
|
48
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
49
|
+
return path.join(binDir, `${platformInfo.platform}-${platformInfo.arch}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
getPlatformInfo,
|
|
54
|
+
getExecutablePath,
|
|
55
|
+
getBinaryDirectory
|
|
56
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aliyun-cli-npm",
|
|
3
|
+
"version": "3.3.17",
|
|
4
|
+
"description": "阿里云CLI统一入口 - 支持按需下载,轻量高效的npm封装包",
|
|
5
|
+
"main": "lib/main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aliyun": "./lib/main.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"aliyun",
|
|
11
|
+
"cli",
|
|
12
|
+
"alibabacloud",
|
|
13
|
+
"aliyun-cli",
|
|
14
|
+
"npm",
|
|
15
|
+
"command-line",
|
|
16
|
+
"cloud",
|
|
17
|
+
"download-on-demand",
|
|
18
|
+
"lightweight"
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
"lib",
|
|
22
|
+
"bin",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"author": "vtxf",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=14.0.0"
|
|
30
|
+
},
|
|
31
|
+
"os": ["win32", "darwin", "linux"],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/vtxf/aliyun-cli-npm.git"
|
|
35
|
+
}
|
|
36
|
+
}
|