@elliotxx/claude-list 0.1.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/.gitignore +2 -0
- package/LICENSE +21 -0
- package/README.md +405 -0
- package/binary-install.js +212 -0
- package/binary.js +126 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +519 -0
- package/package.json +92 -0
- package/run-claude-list.js +4 -0
package/.gitignore
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 elliotxx
|
|
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,405 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<div>
|
|
4
|
+
<img src="assets/logo.svg" alt="claude-list Logo" width="128" height="128">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<h1 style="margin-top: 10px;">claude-list</h1>
|
|
8
|
+
|
|
9
|
+
CLI tool for viewing installed plugins, skills, agents, and MCP servers in Claude Code.
|
|
10
|
+
|
|
11
|
+
<div align="center">
|
|
12
|
+
<a href="https://github.com/elliotxx/claude-list/actions"><img alt="CI Status" src="https://img.shields.io/github/actions/workflow/status/elliotxx/claude-list?logo=github"/></a>
|
|
13
|
+
<a href="https://crates.io/crates/claude-list"><img alt="Crates.io" src="https://img.shields.io/crates/v/claude-list"/></a>
|
|
14
|
+
<a href="https://github.com/elliotxx/claude-list/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-MIT-yellow.svg"/></a>
|
|
15
|
+
<a href="https://www.rust-lang.org/"><img alt="Rust" src="https://img.shields.io/badge/Rust-1.75+-orange.svg"/></a>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<p>
|
|
19
|
+
<a href="#why-claude-list">Why?</a>
|
|
20
|
+
◆ <a href="#quick-start">Quick Start</a>
|
|
21
|
+
◆ <a href="#features">Features</a>
|
|
22
|
+
◆ <a href="#installation">Installation</a>
|
|
23
|
+
◆ <a href="#architecture">Architecture</a>
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Latest News 🔥
|
|
30
|
+
|
|
31
|
+
- **[2026/01]** Published to crates.io - now installable via `cargo install claude-list`
|
|
32
|
+
- **[2026/01]** Added Homebrew support with cargo-dist multi-platform builds
|
|
33
|
+
- **[2026/01]** Released v0.1.0 with compact, detailed, and JSON output modes
|
|
34
|
+
- **[2026/01]** Implemented 7 component parsers (plugins, skills, sessions, mcp, hooks, agents, commands)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why claude-list?
|
|
39
|
+
|
|
40
|
+
A Rust CLI tool that follows Unix philosophy—do one thing well. It reads your Claude Code `.claude` directory and presents key information in a clean, minimal format with modern aesthetics.
|
|
41
|
+
|
|
42
|
+
- **🎨 Clean Output** - Human-readable compact format by default
|
|
43
|
+
- **🔍 Detailed Views** - Version, source, and path information on demand
|
|
44
|
+
- **🤖 Scriptable** - JSON output for automation and integration
|
|
45
|
+
- **⚡ Fast** - Sub-second execution (< 0.03s)
|
|
46
|
+
- **🔒 Safe** - Handles missing files and partial data gracefully
|
|
47
|
+
- **📦 Multi-Platform** - Pre-built binaries for Linux and macOS
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Install from crates.io
|
|
55
|
+
cargo install claude-list
|
|
56
|
+
|
|
57
|
+
# View your Claude Code environment
|
|
58
|
+
claude-list
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Need more details?** See [Installation](#installation) below for all installation options.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
### Output Modes
|
|
68
|
+
|
|
69
|
+
| Mode | Command | Description |
|
|
70
|
+
|------|---------|-------------|
|
|
71
|
+
| Compact | `claude-list` | Summary with counts |
|
|
72
|
+
| Detailed | `claude-list -l` | Full info with version, source, path |
|
|
73
|
+
| JSON | `claude-list --json` | Machine-readable output |
|
|
74
|
+
|
|
75
|
+
### Filtering
|
|
76
|
+
|
|
77
|
+
Filter to show specific component types:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
claude-list --plugins # Only plugins
|
|
81
|
+
claude-list --skills # Only skills
|
|
82
|
+
claude-list --sessions # Only sessions
|
|
83
|
+
claude-list --mcp # Only MCP servers
|
|
84
|
+
claude-list --hooks # Only hooks
|
|
85
|
+
claude-list --agents # Only agents
|
|
86
|
+
claude-list --commands # Only commands
|
|
87
|
+
|
|
88
|
+
# Combine filters
|
|
89
|
+
claude-list --plugins --skills
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Custom Configuration Directory
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
claude-list --config /path/to/.claude
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Demo
|
|
101
|
+
|
|
102
|
+
### Compact Mode (Default)
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
CLAUDE-LIST v0.1.0
|
|
106
|
+
|
|
107
|
+
CONFIG: /Users/user/.claude
|
|
108
|
+
|
|
109
|
+
PLUGINS 3 installed
|
|
110
|
+
context7
|
|
111
|
+
plugin_playwright
|
|
112
|
+
plugin_example
|
|
113
|
+
|
|
114
|
+
SKILLS 12 available
|
|
115
|
+
brainstorming
|
|
116
|
+
claude-code-guide
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
SESSIONS 47 recorded
|
|
120
|
+
MCP 2 servers
|
|
121
|
+
test-mcp
|
|
122
|
+
another-mcp
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Detailed Mode (`-l`)
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
CLAUDE-LIST v0.1.0
|
|
129
|
+
|
|
130
|
+
CONFIG: /Users/user/.claude
|
|
131
|
+
|
|
132
|
+
PLUGINS 3 installed
|
|
133
|
+
NAME VERSION SOURCE PATH
|
|
134
|
+
------------------- ------- --------- ---------------------------------
|
|
135
|
+
context7 2.1.0 official /Users/user/.claude/settings.json
|
|
136
|
+
plugin_playwright 1.0.0 third-party /Users/user/.claude/settings.json
|
|
137
|
+
plugin_example 0.5.0 community /Users/user/.claude/settings.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### JSON Mode (`--json`)
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"version": "0.1.0",
|
|
145
|
+
"config_dir": "/Users/user/.claude",
|
|
146
|
+
"plugins": [...],
|
|
147
|
+
"skills": [...],
|
|
148
|
+
"sessions": {...},
|
|
149
|
+
"mcp_servers": [...],
|
|
150
|
+
"hooks": [...],
|
|
151
|
+
"agents": [...],
|
|
152
|
+
"commands": [...]
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Installation
|
|
159
|
+
|
|
160
|
+
### Option 1: From crates.io (Recommended)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
cargo install claude-list
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Option 2: From Homebrew (macOS)
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
brew tap elliotxx/tap && brew install elliotxx/tap/claude-list
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Option 3: From GitHub Releases
|
|
173
|
+
|
|
174
|
+
Download pre-built binaries from [GitHub Releases](https://github.com/elliotxx/claude-list/releases):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Linux (x86_64)
|
|
178
|
+
wget https://github.com/elliotxx/claude-list/releases/latest/download/claude-list-x86_64-unknown-linux-gnu.tar.gz
|
|
179
|
+
tar -xzf claude-list-x86_64-unknown-linux-gnu.tar.gz
|
|
180
|
+
./claude-list
|
|
181
|
+
|
|
182
|
+
# macOS (Apple Silicon)
|
|
183
|
+
wget https://github.com/elliotxx/claude-list/releases/latest/download/claude-list-aarch64-apple-darwin.tar.gz
|
|
184
|
+
tar -xzf claude-list-aarch64-apple-darwin.tar.gz
|
|
185
|
+
./claude-list
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Option 4: From Source
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
git clone https://github.com/elliotxx/claude-list.git
|
|
192
|
+
cd claude-list
|
|
193
|
+
cargo build --release
|
|
194
|
+
cargo install --path .
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Architecture
|
|
200
|
+
|
|
201
|
+
### System Overview
|
|
202
|
+
|
|
203
|
+
<div align="center">
|
|
204
|
+
<img src="https://via.placeholder.com/750x400?text=claude-list+Architecture" alt="Architecture Diagram" width="750">
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
### Component Architecture
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
┌─────────────────────────────────────────────────────────┐
|
|
211
|
+
│ User Input │
|
|
212
|
+
│ (CLI Arguments) │
|
|
213
|
+
└──────────────────────┬──────────────────────────────────┘
|
|
214
|
+
│
|
|
215
|
+
▼
|
|
216
|
+
┌─────────────────────────────────────────────────────────┐
|
|
217
|
+
│ Main Entry Point (src/main.rs) │
|
|
218
|
+
│ • Parse CLI arguments │
|
|
219
|
+
│ • Dispatch to formatters │
|
|
220
|
+
└──────────────────────┬──────────────────────────────────┘
|
|
221
|
+
│
|
|
222
|
+
▼
|
|
223
|
+
┌─────────────────────────────────────────────────────────┐
|
|
224
|
+
│ Parser Layer (src/parsers/) │
|
|
225
|
+
│ • plugins.rs → Parse installed plugins │
|
|
226
|
+
│ • skills.rs → Parse skills │
|
|
227
|
+
│ • sessions.rs → Parse session history │
|
|
228
|
+
│ • mcp.rs → Parse MCP servers │
|
|
229
|
+
│ • hooks.rs → Parse hooks │
|
|
230
|
+
│ • agents.rs → Parse agents │
|
|
231
|
+
│ • commands.rs → Parse commands │
|
|
232
|
+
└──────────────────────┬──────────────────────────────────┘
|
|
233
|
+
│
|
|
234
|
+
▼
|
|
235
|
+
┌─────────────────────────────────────────────────────────┐
|
|
236
|
+
│ Formatter Layer (src/formatters/) │
|
|
237
|
+
│ • compact.rs → Human-readable summary │
|
|
238
|
+
│ • detailed.rs → Full info with version/source/path │
|
|
239
|
+
│ • json.rs → Machine-readable JSON output │
|
|
240
|
+
└─────────────────────────────────────────────────────────┘
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Key Design Decisions
|
|
244
|
+
|
|
245
|
+
- **Pattern Used**: Unix philosophy—single responsibility, compose simple tools
|
|
246
|
+
- **Technology Stack**: Rust 1.75+, clap (CLI), serde (JSON), anyhow (error handling)
|
|
247
|
+
- **Scalability**: Each parser is independent, easy to extend
|
|
248
|
+
- **Error Handling**: Graceful degradation for missing files
|
|
249
|
+
|
|
250
|
+
### Supported Data Sources
|
|
251
|
+
|
|
252
|
+
| Component | Format | Location |
|
|
253
|
+
|-----------|--------|----------|
|
|
254
|
+
| Plugins | JSON | `.claude/plugins/installed_plugins.json` |
|
|
255
|
+
| Skills | YAML | `.claude/skills/*/skill.yaml` |
|
|
256
|
+
| MCP Servers | Directory/YAML | `.claude/mcp-servers/*/` |
|
|
257
|
+
| Sessions | JSON Lines | `.claude/history.jsonl` |
|
|
258
|
+
| Commands | Markdown | `.claude/commands/*.md` |
|
|
259
|
+
| Agents | Markdown | `.claude/agents/*.md` |
|
|
260
|
+
| Hooks | Markdown | `.claude/hooks/*.md` |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Development
|
|
265
|
+
|
|
266
|
+
### Prerequisites
|
|
267
|
+
|
|
268
|
+
- Rust 1.75+
|
|
269
|
+
- Cargo
|
|
270
|
+
|
|
271
|
+
### Quick Start
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Run all checks
|
|
275
|
+
cargo fmt && cargo clippy && cargo test
|
|
276
|
+
|
|
277
|
+
# Run tests only
|
|
278
|
+
cargo test
|
|
279
|
+
|
|
280
|
+
# Run integration tests
|
|
281
|
+
cargo test --test cli_test
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Building
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Debug build
|
|
288
|
+
cargo build
|
|
289
|
+
|
|
290
|
+
# Release build
|
|
291
|
+
cargo build --release
|
|
292
|
+
|
|
293
|
+
# Check format
|
|
294
|
+
cargo fmt --check
|
|
295
|
+
|
|
296
|
+
# Run clippy
|
|
297
|
+
cargo clippy --all-features -- -D warnings
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Testing
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Run all tests
|
|
304
|
+
cargo test --all-features
|
|
305
|
+
|
|
306
|
+
# Run with verbose output
|
|
307
|
+
cargo test --all-features --verbose
|
|
308
|
+
|
|
309
|
+
# Run specific test
|
|
310
|
+
cargo test test_name
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Publishing
|
|
316
|
+
|
|
317
|
+
Releases are automated via [cargo-dist](https://dist.clap.rs/):
|
|
318
|
+
|
|
319
|
+
1. Push a git tag matching `x.y.z` (e.g., `0.1.1`)
|
|
320
|
+
2. CI pipeline triggers automatically:
|
|
321
|
+
- **Plan**: Generate build manifest
|
|
322
|
+
- **Build**: Multi-platform builds (x86_64 Linux, x86_64/aarch64 macOS)
|
|
323
|
+
- **Publish**: GitHub Release + Homebrew
|
|
324
|
+
|
|
325
|
+
### Release Process
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# 1. Update version in Cargo.toml
|
|
329
|
+
# Edit Cargo.toml: version = "0.1.0" → "0.1.1"
|
|
330
|
+
|
|
331
|
+
# 2. Commit version change
|
|
332
|
+
git add -A && git commit -m "chore: bump version to 0.1.1"
|
|
333
|
+
|
|
334
|
+
# 3. Create git tag
|
|
335
|
+
git tag 0.1.1
|
|
336
|
+
|
|
337
|
+
# 4. Push to GitHub (including tags)
|
|
338
|
+
git push && git push --tags
|
|
339
|
+
|
|
340
|
+
# 5. After CI completes, manually publish to crates.io
|
|
341
|
+
cargo publish
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
> **Note**: Publishing to crates.io requires manual `cargo publish` as cargo-dist does not support automatic crates.io publishing.
|
|
345
|
+
|
|
346
|
+
### CI Tokens Required
|
|
347
|
+
|
|
348
|
+
| Secret | Purpose |获取位置|
|
|
349
|
+
|--------|---------|--------|
|
|
350
|
+
| `CARGO_REGISTRY_TOKEN` | Publish to crates.io | [crates.io/settings/tokens](https://crates.io/settings/tokens) |
|
|
351
|
+
| `HOMEBREW_TAP_TOKEN` | Publish to Homebrew | [GitHub Settings](https://github.com/settings/tokens) |
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Contributing
|
|
356
|
+
|
|
357
|
+
We welcome contributions! Feel free to submit issues and pull requests.
|
|
358
|
+
|
|
359
|
+
### Contribution Areas
|
|
360
|
+
|
|
361
|
+
- **Feature Development**: Add new parsers or formatters
|
|
362
|
+
- **Bug Fixes**: Fix issues and improve stability
|
|
363
|
+
- **Documentation**: Improve guides and examples
|
|
364
|
+
- **Testing**: Add tests and improve coverage
|
|
365
|
+
|
|
366
|
+
### Quick Start for Contributors
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# Fork the repository on GitHub, then clone your fork
|
|
370
|
+
git clone https://github.com/YOUR_USERNAME/claude-list.git
|
|
371
|
+
cd claude-list
|
|
372
|
+
|
|
373
|
+
# Follow installation steps above
|
|
374
|
+
|
|
375
|
+
# Create feature branch
|
|
376
|
+
git checkout -b feature/your-feature-name
|
|
377
|
+
|
|
378
|
+
# Make changes, test, then commit and push
|
|
379
|
+
git add .
|
|
380
|
+
git commit -m "feat: description"
|
|
381
|
+
git push origin feature/your-feature-name
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## License
|
|
387
|
+
|
|
388
|
+
This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Acknowledgments
|
|
393
|
+
|
|
394
|
+
- [Claude Code](https://claude.com/claude-code) for the inspiration
|
|
395
|
+
- [clap](https://github.com/clap-rs/clap) for CLI argument parsing
|
|
396
|
+
- [cargo-dist](https://github.com/axodotdev/cargo-dist) for automated releases
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
<div align="center">
|
|
401
|
+
<p>
|
|
402
|
+
<strong>Built with ❤️ for the Claude Code community</strong><br>
|
|
403
|
+
<sub>Parse and display your Claude Code environment</sub>
|
|
404
|
+
</p>
|
|
405
|
+
</div>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
|
|
2
|
+
const { join, sep } = require("path");
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
const { tmpdir } = require("os");
|
|
5
|
+
|
|
6
|
+
const axios = require("axios");
|
|
7
|
+
const rimraf = require("rimraf");
|
|
8
|
+
const tmpDir = tmpdir();
|
|
9
|
+
|
|
10
|
+
const error = (msg) => {
|
|
11
|
+
console.error(msg);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
class Package {
|
|
16
|
+
constructor(platform, name, url, filename, zipExt, binaries) {
|
|
17
|
+
let errors = [];
|
|
18
|
+
if (typeof url !== "string") {
|
|
19
|
+
errors.push("url must be a string");
|
|
20
|
+
} else {
|
|
21
|
+
try {
|
|
22
|
+
new URL(url);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
errors.push(e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (name && typeof name !== "string") {
|
|
28
|
+
errors.push("package name must be a string");
|
|
29
|
+
}
|
|
30
|
+
if (!name) {
|
|
31
|
+
errors.push("You must specify the name of your package");
|
|
32
|
+
}
|
|
33
|
+
if (binaries && typeof binaries !== "object") {
|
|
34
|
+
errors.push("binaries must be a string => string map");
|
|
35
|
+
}
|
|
36
|
+
if (!binaries) {
|
|
37
|
+
errors.push("You must specify the binaries in the package");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
let errorMsg =
|
|
42
|
+
"One or more of the parameters you passed to the Binary constructor are invalid:\n";
|
|
43
|
+
errors.forEach((error) => {
|
|
44
|
+
errorMsg += error;
|
|
45
|
+
});
|
|
46
|
+
errorMsg +=
|
|
47
|
+
'\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
|
|
48
|
+
error(errorMsg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.platform = platform;
|
|
52
|
+
this.url = url;
|
|
53
|
+
this.name = name;
|
|
54
|
+
this.filename = filename;
|
|
55
|
+
this.zipExt = zipExt;
|
|
56
|
+
this.installDirectory = join(__dirname, "node_modules", ".bin_real");
|
|
57
|
+
this.binaries = binaries;
|
|
58
|
+
|
|
59
|
+
if (!existsSync(this.installDirectory)) {
|
|
60
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exists() {
|
|
65
|
+
for (const binaryName in this.binaries) {
|
|
66
|
+
const binRelPath = this.binaries[binaryName];
|
|
67
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
68
|
+
if (!existsSync(binPath)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
install(fetchOptions, suppressLogs = false) {
|
|
76
|
+
if (this.exists()) {
|
|
77
|
+
if (!suppressLogs) {
|
|
78
|
+
console.error(
|
|
79
|
+
`${this.name} is already installed, skipping installation.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (existsSync(this.installDirectory)) {
|
|
86
|
+
rimraf.sync(this.installDirectory);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
mkdirSync(this.installDirectory, { recursive: true });
|
|
90
|
+
|
|
91
|
+
if (!suppressLogs) {
|
|
92
|
+
console.error(`Downloading release from ${this.url}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
|
|
96
|
+
.then((res) => {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
|
|
99
|
+
let tempFile = join(directory, this.filename);
|
|
100
|
+
const sink = res.data.pipe(createWriteStream(tempFile));
|
|
101
|
+
sink.on("error", (err) => reject(err));
|
|
102
|
+
sink.on("close", () => {
|
|
103
|
+
if (/\.tar\.*/.test(this.zipExt)) {
|
|
104
|
+
const result = spawnSync("tar", [
|
|
105
|
+
"xf",
|
|
106
|
+
tempFile,
|
|
107
|
+
// The tarballs are stored with a leading directory
|
|
108
|
+
// component; we strip one component in the
|
|
109
|
+
// shell installers too.
|
|
110
|
+
"--strip-components",
|
|
111
|
+
"1",
|
|
112
|
+
"-C",
|
|
113
|
+
this.installDirectory,
|
|
114
|
+
]);
|
|
115
|
+
if (result.status == 0) {
|
|
116
|
+
resolve();
|
|
117
|
+
} else if (result.error) {
|
|
118
|
+
reject(result.error);
|
|
119
|
+
} else {
|
|
120
|
+
reject(
|
|
121
|
+
new Error(
|
|
122
|
+
`An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
} else if (this.zipExt == ".zip") {
|
|
127
|
+
let result;
|
|
128
|
+
if (this.platform.artifactName.includes("windows")) {
|
|
129
|
+
// Windows does not have "unzip" by default on many installations, instead
|
|
130
|
+
// we use Expand-Archive from powershell
|
|
131
|
+
result = spawnSync("powershell.exe", [
|
|
132
|
+
"-NoProfile",
|
|
133
|
+
"-NonInteractive",
|
|
134
|
+
"-Command",
|
|
135
|
+
`& {
|
|
136
|
+
param([string]$LiteralPath, [string]$DestinationPath)
|
|
137
|
+
Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
|
|
138
|
+
}`,
|
|
139
|
+
tempFile,
|
|
140
|
+
this.installDirectory,
|
|
141
|
+
]);
|
|
142
|
+
} else {
|
|
143
|
+
result = spawnSync("unzip", [
|
|
144
|
+
"-q",
|
|
145
|
+
tempFile,
|
|
146
|
+
"-d",
|
|
147
|
+
this.installDirectory,
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (result.status == 0) {
|
|
152
|
+
resolve();
|
|
153
|
+
} else if (result.error) {
|
|
154
|
+
reject(result.error);
|
|
155
|
+
} else {
|
|
156
|
+
reject(
|
|
157
|
+
new Error(
|
|
158
|
+
`An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
reject(
|
|
164
|
+
new Error(`Unrecognized file extension: ${this.zipExt}`),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
})
|
|
171
|
+
.then(() => {
|
|
172
|
+
if (!suppressLogs) {
|
|
173
|
+
console.error(`${this.name} has been installed!`);
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
.catch((e) => {
|
|
177
|
+
error(`Error fetching release: ${e.message}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
run(binaryName, fetchOptions) {
|
|
182
|
+
const promise = !this.exists()
|
|
183
|
+
? this.install(fetchOptions, true)
|
|
184
|
+
: Promise.resolve();
|
|
185
|
+
|
|
186
|
+
promise
|
|
187
|
+
.then(() => {
|
|
188
|
+
const [, , ...args] = process.argv;
|
|
189
|
+
|
|
190
|
+
const options = { cwd: process.cwd(), stdio: "inherit" };
|
|
191
|
+
|
|
192
|
+
const binRelPath = this.binaries[binaryName];
|
|
193
|
+
if (!binRelPath) {
|
|
194
|
+
error(`${binaryName} is not a known binary in ${this.name}`);
|
|
195
|
+
}
|
|
196
|
+
const binPath = join(this.installDirectory, binRelPath);
|
|
197
|
+
const result = spawnSync(binPath, args, options);
|
|
198
|
+
|
|
199
|
+
if (result.error) {
|
|
200
|
+
error(result.error);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
process.exit(result.status);
|
|
204
|
+
})
|
|
205
|
+
.catch((e) => {
|
|
206
|
+
error(e.message);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports.Package = Package;
|