@zackees/soldr 0.7.5
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 +29 -0
- package/README.md +212 -0
- package/bin/soldr.js +38 -0
- package/package.json +42 -0
- package/scripts/install.js +240 -0
- package/scripts/test-npm-package.js +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019, zackees
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Soldr / Rust
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
<img width="1536" height="1024" alt="ChatGPT Image Apr 19, 2026, 09_43_32 PM" src="https://github.com/user-attachments/assets/87d94693-3542-4f4f-8b02-600bf0b9810e" />
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
*A tool to download rust tool sets and aggressive cache your build. Instant warm compiles. Gh and local builds. Just add soldr before all your build commands*
|
|
8
|
+
|
|
9
|
+
Rendered benchmarks: [zackees.github.io/soldr](https://zackees.github.io/soldr/)
|
|
10
|
+
|
|
11
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-linux-x64.yml)
|
|
12
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-linux-arm64.yml)
|
|
13
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-linux-x64-musl.yml)
|
|
14
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-linux-arm64-musl.yml)
|
|
15
|
+
|
|
16
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-macos-x64.yml)
|
|
17
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-macos-arm64.yml)
|
|
18
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-windows-x64.yml)
|
|
19
|
+
[](https://github.com/zackees/soldr/actions/workflows/build-windows-arm64.yml)
|
|
20
|
+
|
|
21
|
+
**Instant tools. Instant builds. One command.**
|
|
22
|
+
|
|
23
|
+
soldr = [crgx](https://crgx.dev/) + [zccache](https://github.com/zackees/zccache) in a single tool.
|
|
24
|
+
|
|
25
|
+
**Where correct builds meet instant builds.**
|
|
26
|
+
|
|
27
|
+
The point of soldr is not to invent some brand-new primitive. The point is to combine the pieces that already work into one tool that people can actually rely on every day.
|
|
28
|
+
|
|
29
|
+
[zccache](https://github.com/zackees/zccache) is already excellent. [crgx](https://crgx.dev/) already proved the value of instant Rust tooling. soldr turns those into one front door:
|
|
30
|
+
|
|
31
|
+
- get the right Rust tool for the job
|
|
32
|
+
- get the right Windows ABI without thinking about it
|
|
33
|
+
- get transparent compilation caching without separate setup
|
|
34
|
+
|
|
35
|
+
That is the same reason [uv](https://github.com/astral-sh/uv) is compelling. uv did not win because it invented packaging, virtual environments, or Python installation. It won because it made the whole workflow feel like one tool instead of a pile of separate ones.
|
|
36
|
+
|
|
37
|
+
soldr aims for the same outcome in the Rust toolchain world.
|
|
38
|
+
|
|
39
|
+
Current release line:
|
|
40
|
+
|
|
41
|
+
- `0.5.x` is the secure front-door, tool-fetch, and built-in zccache-backed cache release line
|
|
42
|
+
- `1.0.0-rc` remains reserved for broader release hardening and bootstrap validation
|
|
43
|
+
- the supported external integration boundary remains the `soldr` executable, not the internal Rust crates; see [docs/API_BOUNDARY.md](./docs/API_BOUNDARY.md)
|
|
44
|
+
- practical integration examples for local builds and GitHub Actions live in [INTEGRATION.md](./INTEGRATION.md)
|
|
45
|
+
|
|
46
|
+
## Install from npm
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g @zackees/soldr
|
|
50
|
+
soldr --version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The npm package is a small launcher that downloads the matching `soldr` GitHub
|
|
54
|
+
Release binary for your OS and architecture during install, verifies it against
|
|
55
|
+
the published `SHA256SUMS` file, and exposes the `soldr` command.
|
|
56
|
+
|
|
57
|
+
## GitHub Actions setup
|
|
58
|
+
|
|
59
|
+
The current GitHub Actions entry point is the repository root action in this repository:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
- uses: zackees/soldr@<ref>
|
|
63
|
+
with:
|
|
64
|
+
version: 0.7.4
|
|
65
|
+
cache: true
|
|
66
|
+
|
|
67
|
+
- run: soldr cargo build --locked --release
|
|
68
|
+
- run: soldr cargo test --locked
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That action:
|
|
72
|
+
|
|
73
|
+
- installs `soldr`
|
|
74
|
+
- bootstraps `rustup` into the cached runner-local root when the runner does not already have it
|
|
75
|
+
- preinstalls the exact Rust toolchain from `rust-toolchain.toml` by default via `rustup`
|
|
76
|
+
- restores a cacheable runner-local root for Soldr, Cargo, and rustup state
|
|
77
|
+
- restores and saves the zccache compilation artifact cache at `~/.zccache` by default; set `build-cache: false` to disable it
|
|
78
|
+
- restores and saves the Cargo target directory by default for no-op CI fast paths; set `target-cache: false` to disable it or `target-dir:` to choose another target directory
|
|
79
|
+
- puts `soldr` on `PATH` for later steps
|
|
80
|
+
- is the extraction source for the planned public `zackees/setup-soldr` action product
|
|
81
|
+
|
|
82
|
+
If your project pins Rust in `rust-toolchain.toml`, let the action read that file or pass the exact value with `toolchain:`. Do not preinstall a different generic toolchain such as `stable` and assume `soldr` will reconcile it later. The action exports `RUSTUP_TOOLCHAIN` after installation so later `cargo`, `rustc`, and `soldr cargo ...` steps stay on the toolchain it just installed instead of asking `rustup` to resolve a pinned file lazily.
|
|
83
|
+
|
|
84
|
+
On GitHub-hosted runners, this means you usually do not need a separate toolchain setup action for the normal path. The action still uses `rustup` under the hood today, but it bootstraps `rustup` itself when the runner does not already have it.
|
|
85
|
+
On runners without `rustup`, the action downloads and installs it into the cached runner-local root before provisioning the requested toolchain.
|
|
86
|
+
|
|
87
|
+
For same-repository validation, use `uses: ./`. This repository smoke-tests that path in [setup-soldr-action.yml](./.github/workflows/setup-soldr-action.yml). GitHub Marketplace publication still requires extracting this action into a separate public action repository because GitHub requires a single root `action.yml` and no workflow files in the published repository. The repo-contained extraction plan and intended `zackees/setup-soldr@v1` contract live in [docs/SETUP_SOLDR_PUBLIC_ACTION.md](./docs/SETUP_SOLDR_PUBLIC_ACTION.md). Until that public repo exists, treat `zackees/soldr@<ref>` as the current contract and pin a full commit SHA or explicit release tag instead of assuming `@v1`. For fuller examples and fallback patterns, see [INTEGRATION.md](./INTEGRATION.md).
|
|
88
|
+
|
|
89
|
+
### CI cache lineage
|
|
90
|
+
|
|
91
|
+
GitHub Actions caches are not shared across arbitrary sibling feature branches. A workflow run can restore caches from its own branch, the default branch, and for pull requests the PR base branch. It cannot directly restore caches created on another feature branch.
|
|
92
|
+
|
|
93
|
+
That means Soldr treats `main` as the canonical warm-cache source:
|
|
94
|
+
|
|
95
|
+
- CI runs on pushes to `main` and feature branches.
|
|
96
|
+
- A feature-branch push can save a branch-local cache entry in its own branch scope.
|
|
97
|
+
- Later pushes and PRs for that same branch restore that branch-local cache first.
|
|
98
|
+
- If the feature branch has no exact cache yet, GitHub falls back to the `main` cache lineage through the same stable keys.
|
|
99
|
+
- The heavy cache-producing CI runs on branch pushes, not `pull_request`, so each feature branch gets one useful cache lineage instead of a duplicate PR merge-ref lineage.
|
|
100
|
+
|
|
101
|
+
In practice this gives the exact parent/child model we want: `main` acts as the shared parent cache, feature branches read from that parent on miss, and each feature branch may also save its own preferred child cache when the workflow runs on `push`. Pull requests then reflect the branch-push CI state instead of creating a second heavy cache path. This repository is the first reference implementation of that pattern. For the full wiring and rollout notes, see [docs/CI_CACHE.md](./docs/CI_CACHE.md).
|
|
102
|
+
|
|
103
|
+
## Why soldr exists
|
|
104
|
+
|
|
105
|
+
On Windows, the real problem is not "how do I cache builds?" or "how do I download a tool binary?" in isolation.
|
|
106
|
+
|
|
107
|
+
The real problem is that the execution path is messy:
|
|
108
|
+
|
|
109
|
+
- the wrong `cargo` can win on `PATH`
|
|
110
|
+
- the wrong Windows target can get selected
|
|
111
|
+
- GNU can leak in where MSVC should have been used
|
|
112
|
+
- users end up debugging their toolchain instead of shipping code
|
|
113
|
+
|
|
114
|
+
soldr exists to make that path boring.
|
|
115
|
+
|
|
116
|
+
When you run `soldr`, the tool should do the obvious thing:
|
|
117
|
+
|
|
118
|
+
- pick MSVC on Windows by default
|
|
119
|
+
- fetch the tool you asked for
|
|
120
|
+
- cache it locally
|
|
121
|
+
- fetch and manage zccache so Rust builds get transparent caching without manual wrapper setup
|
|
122
|
+
|
|
123
|
+
If soldr solves that one problem well, it becomes a super tool: the command you reach for first, because it makes the rest of the stack behave.
|
|
124
|
+
|
|
125
|
+
- **Tool acquisition** (the crgx half): Need `maturin`, `cargo-dylint`, or any crate binary? soldr fetches a pre-built binary from GitHub Releases in seconds. No `cargo install` from source. Cached locally for instant reuse. On `0.5.x`, this is still an upstream trust decision rather than a repo-side trust guarantee; see [docs/TRUST_BOUNDARIES.md](./docs/TRUST_BOUNDARIES.md).
|
|
126
|
+
|
|
127
|
+
- **Compilation caching** (the zccache half): `soldr cargo ...` now fetches and manages a pinned `zccache` release for Rust builds. soldr owns the zccache daemon/session wiring; zccache's artifact store still uses its current default cache root.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Build through soldr's front door:
|
|
131
|
+
soldr cargo build --release
|
|
132
|
+
soldr cargo test
|
|
133
|
+
soldr --no-cache cargo test
|
|
134
|
+
SOLDR_RUSTC_WRAPPER=sccache soldr cargo build
|
|
135
|
+
SOLDR_RUSTC_WRAPPER=none soldr cargo build
|
|
136
|
+
|
|
137
|
+
# Fetch and run any Rust tool instantly:
|
|
138
|
+
soldr maturin build --release
|
|
139
|
+
soldr cargo-dylint check
|
|
140
|
+
soldr rustfmt src/main.rs
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## How it works
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
soldr cargo build --release
|
|
147
|
+
+-- resolve the real cargo binary
|
|
148
|
+
+-- fetch/start managed zccache when cache is enabled
|
|
149
|
+
+-- set soldr as the compiler wrapper for this build
|
|
150
|
+
+-- have soldr wrapper mode delegate to managed zccache
|
|
151
|
+
+-- delegate to cargo with your existing flags
|
|
152
|
+
|
|
153
|
+
soldr maturin build --release
|
|
154
|
+
+-- maturin cached? --> run instantly
|
|
155
|
+
+-- not cached? --> download pre-built binary (2s) --> run
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Design goals
|
|
159
|
+
|
|
160
|
+
- **One obvious command**: Fetch tools, pick the right Windows target, and run through managed zccache through the same entry point.
|
|
161
|
+
- **Front-door builds**: `soldr cargo ...` is the primary build UX.
|
|
162
|
+
- **Invisible caching**: `soldr cargo ...` uses a soldr-managed zccache by default, with `soldr --no-cache cargo ...` as the opt-out.
|
|
163
|
+
- **Real cache controls**: `soldr status`, `soldr cache`, and `soldr clean` report and manage the soldr-managed zccache state instead of placeholder behavior.
|
|
164
|
+
- **One cache boundary, eventually**: soldr keeps its own tools and zccache session state in `~/.soldr/`. Current zccache artifacts still live in zccache's default cache root until upstream exposes a supported cache-dir override.
|
|
165
|
+
- **Pre-built first**: Download a pre-built binary before compiling from source. Fall back gracefully.
|
|
166
|
+
- **Cargo-compatible**: soldr preserves normal cargo arguments instead of forcing a separate workflow.
|
|
167
|
+
- **Cross-platform**: Linux, macOS, Windows (x86_64 + aarch64).
|
|
168
|
+
- **MSVC by default on Windows**: Always targets `x86_64-pc-windows-msvc` (or `aarch64-pc-windows-msvc`) unless the active project explicitly selects another target in `.cargo/config.toml`, `.cargo/config`, or `rust-toolchain.toml`. MSVC links against `vcruntime140.dll` which ships with every modern Windows install. The GNU target requires shipping `libgcc_s_seh-1.dll` and `libwinpthread-1.dll` with every binary, which is extra baggage for no benefit. This matches the Rust ecosystem default: rustup, cargo-binstall, and nearly all published release binaries target MSVC. crgx gets this wrong by baking the target at compile time, causing it to look for GNU binaries when compiled under MSYS2.
|
|
169
|
+
|
|
170
|
+
## Architecture
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
soldr/
|
|
174
|
+
|-- crates/
|
|
175
|
+
| |-- soldr-core/ # Shared types, config, cache directory layout
|
|
176
|
+
| |-- soldr-fetch/ # Binary resolution + download (the crgx half)
|
|
177
|
+
| |-- soldr-cache/ # Compilation caching (the zccache half)
|
|
178
|
+
| `-- soldr-cli/ # CLI entry point + wrapper mode
|
|
179
|
+
|-- src/soldr/ # Python package (maturin bin bindings)
|
|
180
|
+
`-- tests/
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
| Crate | Role |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `soldr-core` | Cache paths, config, version types |
|
|
186
|
+
| `soldr-fetch` | Resolve crate binaries from crates.io metadata and GitHub Releases. Download and cache. |
|
|
187
|
+
| `soldr-cache` | zccache integration helpers, cache policy, session plumbing. |
|
|
188
|
+
| `soldr-cli` | Mode detection, cargo front door, built-in commands (`status`, `clean`, `config`, `cache`), tool fetch dispatch. |
|
|
189
|
+
|
|
190
|
+
These workspace crates are implementation details. They are not a supported public Rust library API.
|
|
191
|
+
|
|
192
|
+
## Prior art
|
|
193
|
+
|
|
194
|
+
Built on lessons from:
|
|
195
|
+
- [zccache](https://github.com/zackees/zccache) - 2.4x faster warm builds than sccache ([benchmark](https://github.com/zackees/zccache/issues/20))
|
|
196
|
+
- [crgx](https://crgx.dev/) - the npx of Rust, instant tool execution
|
|
197
|
+
- [cargo-binstall](https://github.com/cargo-bins/cargo-binstall) - pre-built binary resolution
|
|
198
|
+
- [sccache](https://github.com/mozilla/sccache) - the original Rust compilation cache
|
|
199
|
+
|
|
200
|
+
## Security And Verification
|
|
201
|
+
|
|
202
|
+
- [SECURITY.md](./SECURITY.md) describes the current hardening posture and release policy.
|
|
203
|
+
- [docs/API_BOUNDARY.md](./docs/API_BOUNDARY.md) defines the supported machine-facing integration boundary.
|
|
204
|
+
- [docs/PYPI_TRUSTED_PUBLISHING.md](./docs/PYPI_TRUSTED_PUBLISHING.md) describes the optional Trusted Publishing path for hardened PyPI wheels.
|
|
205
|
+
- [`.github/workflows/release-auto.yml`](./.github/workflows/release-auto.yml) is the only release workflow: when a reviewed version bump lands on `main`, it derives the version from `Cargo.toml`, reruns the release gate, and performs final publication through the `release` environment where the release credentials live.
|
|
206
|
+
- [RELEASE.md](./RELEASE.md) documents the intended maximum-security release setup and owner workflow.
|
|
207
|
+
- [docs/RELEASE_VERIFICATION.md](./docs/RELEASE_VERIFICATION.md) explains how to verify published release artifacts.
|
|
208
|
+
- [docs/TRUST_BOUNDARIES.md](./docs/TRUST_BOUNDARIES.md) inventories the external systems and artifacts `soldr` currently trusts, including the current `0.5.x` limits of runtime fetched-binary trust.
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
BSD-3-Clause.
|
package/bin/soldr.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const childProcess = require("child_process");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const binaryName = process.platform === "win32" ? "soldr.exe" : "soldr";
|
|
9
|
+
const binaryPath = path.join(__dirname, "native", binaryName);
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(binaryPath)) {
|
|
12
|
+
console.error(
|
|
13
|
+
[
|
|
14
|
+
"soldr: native binary is missing from the npm package install.",
|
|
15
|
+
`expected: ${binaryPath}`,
|
|
16
|
+
"Try reinstalling the package, or run `npm rebuild soldr`.",
|
|
17
|
+
].join("\n"),
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const child = childProcess.spawn(binaryPath, process.argv.slice(2), {
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
windowsHide: false,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.on("error", (error) => {
|
|
28
|
+
console.error(`soldr: failed to launch ${binaryPath}: ${error.message}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
child.on("exit", (code, signal) => {
|
|
33
|
+
if (signal) {
|
|
34
|
+
process.kill(process.pid, signal);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
process.exit(code === null ? 1 : code);
|
|
38
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zackees/soldr",
|
|
3
|
+
"version": "0.7.5",
|
|
4
|
+
"description": "Instant Rust tools and builds from one command.",
|
|
5
|
+
"license": "BSD-3-Clause",
|
|
6
|
+
"homepage": "https://github.com/zackees/soldr",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/zackees/soldr.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/zackees/soldr/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"soldr": "bin/soldr.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/soldr.js",
|
|
19
|
+
"scripts/install.js",
|
|
20
|
+
"scripts/test-npm-package.js",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"postinstall": "node scripts/install.js",
|
|
26
|
+
"prepack": "node scripts/test-npm-package.js",
|
|
27
|
+
"test:npm-package": "node scripts/test-npm-package.js"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=16"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"rust",
|
|
37
|
+
"cargo",
|
|
38
|
+
"build-cache",
|
|
39
|
+
"zccache",
|
|
40
|
+
"cli"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const childProcess = require("child_process");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const http = require("http");
|
|
8
|
+
const https = require("https");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
13
|
+
const PACKAGE_JSON = require(path.join(PACKAGE_ROOT, "package.json"));
|
|
14
|
+
|
|
15
|
+
const TARGETS = {
|
|
16
|
+
"linux-x64": {
|
|
17
|
+
triple: "x86_64-unknown-linux-gnu",
|
|
18
|
+
archive: "tar.gz",
|
|
19
|
+
binary: "soldr",
|
|
20
|
+
},
|
|
21
|
+
"linux-arm64": {
|
|
22
|
+
triple: "aarch64-unknown-linux-gnu",
|
|
23
|
+
archive: "tar.gz",
|
|
24
|
+
binary: "soldr",
|
|
25
|
+
},
|
|
26
|
+
"darwin-x64": {
|
|
27
|
+
triple: "x86_64-apple-darwin",
|
|
28
|
+
archive: "tar.gz",
|
|
29
|
+
binary: "soldr",
|
|
30
|
+
},
|
|
31
|
+
"darwin-arm64": {
|
|
32
|
+
triple: "aarch64-apple-darwin",
|
|
33
|
+
archive: "tar.gz",
|
|
34
|
+
binary: "soldr",
|
|
35
|
+
},
|
|
36
|
+
"win32-x64": {
|
|
37
|
+
triple: "x86_64-pc-windows-msvc",
|
|
38
|
+
archive: "zip",
|
|
39
|
+
binary: "soldr.exe",
|
|
40
|
+
},
|
|
41
|
+
"win32-arm64": {
|
|
42
|
+
triple: "aarch64-pc-windows-msvc",
|
|
43
|
+
archive: "zip",
|
|
44
|
+
binary: "soldr.exe",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function platformTarget(platform = process.platform, arch = process.arch) {
|
|
49
|
+
const target = TARGETS[`${platform}-${arch}`];
|
|
50
|
+
if (!target) {
|
|
51
|
+
throw new Error(`unsupported platform for soldr npm package: ${platform}-${arch}`);
|
|
52
|
+
}
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function releaseBaseUrl(version) {
|
|
57
|
+
const override = process.env.SOLDR_NPM_RELEASE_BASE_URL;
|
|
58
|
+
if (override) {
|
|
59
|
+
return override.replace(/\/+$/, "");
|
|
60
|
+
}
|
|
61
|
+
return `https://github.com/zackees/soldr/releases/download/v${version}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function download(url, redirects = 0) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const client = url.startsWith("https:") ? https : http;
|
|
67
|
+
const request = client.get(
|
|
68
|
+
url,
|
|
69
|
+
{
|
|
70
|
+
headers: {
|
|
71
|
+
"User-Agent": `soldr-npm/${PACKAGE_JSON.version}`,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
(response) => {
|
|
75
|
+
if (
|
|
76
|
+
response.statusCode >= 300 &&
|
|
77
|
+
response.statusCode < 400 &&
|
|
78
|
+
response.headers.location
|
|
79
|
+
) {
|
|
80
|
+
response.resume();
|
|
81
|
+
if (redirects >= 5) {
|
|
82
|
+
reject(new Error(`too many redirects while downloading ${url}`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
resolve(download(new URL(response.headers.location, url).toString(), redirects + 1));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (response.statusCode !== 200) {
|
|
90
|
+
response.resume();
|
|
91
|
+
reject(new Error(`download failed for ${url}: HTTP ${response.statusCode}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const chunks = [];
|
|
96
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
97
|
+
response.on("end", () => resolve(Buffer.concat(chunks)));
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
request.on("error", reject);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function checksumFor(checksumsText, filename) {
|
|
105
|
+
for (const line of checksumsText.split(/\r?\n/)) {
|
|
106
|
+
const trimmed = line.trim();
|
|
107
|
+
if (!trimmed) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const [hash, name] = trimmed.split(/\s+/, 2);
|
|
111
|
+
if (name === filename) {
|
|
112
|
+
return hash.toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`checksum entry not found for ${filename}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function run(command, args, options = {}) {
|
|
119
|
+
const result = childProcess.spawnSync(command, args, {
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
...options,
|
|
122
|
+
});
|
|
123
|
+
if (result.error) {
|
|
124
|
+
throw result.error;
|
|
125
|
+
}
|
|
126
|
+
if (result.status !== 0) {
|
|
127
|
+
throw new Error(`${command} ${args.join(" ")} failed with exit code ${result.status}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractArchive(archivePath, archiveType, destination) {
|
|
132
|
+
if (archiveType === "tar.gz") {
|
|
133
|
+
run("tar", ["-xzf", archivePath, "-C", destination]);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (archiveType === "zip" && process.platform === "win32") {
|
|
138
|
+
run("powershell", [
|
|
139
|
+
"-NoProfile",
|
|
140
|
+
"-NonInteractive",
|
|
141
|
+
"-ExecutionPolicy",
|
|
142
|
+
"Bypass",
|
|
143
|
+
"-Command",
|
|
144
|
+
"Expand-Archive -LiteralPath $env:SOLDR_NPM_ARCHIVE -DestinationPath $env:SOLDR_NPM_EXTRACT -Force",
|
|
145
|
+
], {
|
|
146
|
+
env: {
|
|
147
|
+
...process.env,
|
|
148
|
+
SOLDR_NPM_ARCHIVE: archivePath,
|
|
149
|
+
SOLDR_NPM_EXTRACT: destination,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
run("tar", ["-xf", archivePath, "-C", destination]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function findExtractedBinary(root, binaryName) {
|
|
159
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const candidate = path.join(root, entry.name);
|
|
162
|
+
if (entry.isFile() && entry.name === binaryName) {
|
|
163
|
+
return candidate;
|
|
164
|
+
}
|
|
165
|
+
if (entry.isDirectory()) {
|
|
166
|
+
const nested = findExtractedBinary(candidate, binaryName);
|
|
167
|
+
if (nested) {
|
|
168
|
+
return nested;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function install() {
|
|
176
|
+
if (process.env.SOLDR_NPM_SKIP_DOWNLOAD) {
|
|
177
|
+
console.log("soldr: skipping native binary download because SOLDR_NPM_SKIP_DOWNLOAD is set");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const version = PACKAGE_JSON.version;
|
|
182
|
+
const target = platformTarget();
|
|
183
|
+
const filename = `soldr-v${version}-${target.triple}.${target.archive}`;
|
|
184
|
+
const baseUrl = releaseBaseUrl(version);
|
|
185
|
+
const archiveUrl = `${baseUrl}/${filename}`;
|
|
186
|
+
const checksumUrl = `${baseUrl}/soldr-v${version}-SHA256SUMS.txt`;
|
|
187
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "soldr-npm-"));
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
console.log(`soldr: downloading ${archiveUrl}`);
|
|
191
|
+
const [archive, checksums] = await Promise.all([
|
|
192
|
+
download(archiveUrl),
|
|
193
|
+
download(checksumUrl).then((buffer) => buffer.toString("utf8")),
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
const expected = checksumFor(checksums, filename);
|
|
197
|
+
const actual = crypto.createHash("sha256").update(archive).digest("hex");
|
|
198
|
+
if (actual !== expected) {
|
|
199
|
+
throw new Error(`checksum mismatch for ${filename}: expected ${expected}, got ${actual}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const archivePath = path.join(tmp, filename);
|
|
203
|
+
const extractDir = path.join(tmp, "extract");
|
|
204
|
+
fs.writeFileSync(archivePath, archive);
|
|
205
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
206
|
+
extractArchive(archivePath, target.archive, extractDir);
|
|
207
|
+
|
|
208
|
+
const extracted = findExtractedBinary(extractDir, target.binary);
|
|
209
|
+
if (!extracted) {
|
|
210
|
+
throw new Error(`release archive did not contain ${target.binary}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const nativeDir = path.join(PACKAGE_ROOT, "bin", "native");
|
|
214
|
+
fs.rmSync(nativeDir, { recursive: true, force: true });
|
|
215
|
+
fs.mkdirSync(nativeDir, { recursive: true });
|
|
216
|
+
|
|
217
|
+
const destination = path.join(nativeDir, target.binary);
|
|
218
|
+
fs.copyFileSync(extracted, destination);
|
|
219
|
+
if (process.platform !== "win32") {
|
|
220
|
+
fs.chmodSync(destination, 0o755);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log(`soldr: installed ${target.triple} binary`);
|
|
224
|
+
} finally {
|
|
225
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (require.main === module) {
|
|
230
|
+
install().catch((error) => {
|
|
231
|
+
console.error(`soldr: npm install failed: ${error.message}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
checksumFor,
|
|
238
|
+
platformTarget,
|
|
239
|
+
releaseBaseUrl,
|
|
240
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const assert = require("assert");
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(__dirname, "..");
|
|
9
|
+
const pkg = require(path.join(root, "package.json"));
|
|
10
|
+
const install = require(path.join(root, "scripts", "install.js"));
|
|
11
|
+
|
|
12
|
+
const cargoToml = fs.readFileSync(path.join(root, "Cargo.toml"), "utf8");
|
|
13
|
+
const cargoVersion = cargoToml.match(/\[workspace\.package\][\s\S]*?^version = "([^"]+)"/m);
|
|
14
|
+
assert(cargoVersion, "workspace package version not found in Cargo.toml");
|
|
15
|
+
assert.strictEqual(pkg.version, cargoVersion[1], "package.json version must match Cargo.toml");
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(pkg.name, "@zackees/soldr");
|
|
18
|
+
assert.strictEqual(pkg.license, "BSD-3-Clause");
|
|
19
|
+
assert.strictEqual(pkg.bin.soldr, "bin/soldr.js");
|
|
20
|
+
assert.strictEqual(pkg.repository.url, "git+https://github.com/zackees/soldr.git");
|
|
21
|
+
assert.deepStrictEqual(pkg.files, [
|
|
22
|
+
"bin/soldr.js",
|
|
23
|
+
"scripts/install.js",
|
|
24
|
+
"scripts/test-npm-package.js",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const bin = fs.readFileSync(path.join(root, pkg.bin.soldr), "utf8");
|
|
30
|
+
assert(bin.startsWith("#!/usr/bin/env node"), "bin/soldr.js must have a node shebang");
|
|
31
|
+
|
|
32
|
+
assert.strictEqual(install.platformTarget("linux", "x64").triple, "x86_64-unknown-linux-gnu");
|
|
33
|
+
assert.strictEqual(install.platformTarget("linux", "arm64").triple, "aarch64-unknown-linux-gnu");
|
|
34
|
+
assert.strictEqual(install.platformTarget("darwin", "x64").triple, "x86_64-apple-darwin");
|
|
35
|
+
assert.strictEqual(install.platformTarget("darwin", "arm64").triple, "aarch64-apple-darwin");
|
|
36
|
+
assert.strictEqual(install.platformTarget("win32", "x64").triple, "x86_64-pc-windows-msvc");
|
|
37
|
+
assert.strictEqual(install.platformTarget("win32", "arm64").triple, "aarch64-pc-windows-msvc");
|
|
38
|
+
assert.throws(() => install.platformTarget("freebsd", "x64"), /unsupported platform/);
|
|
39
|
+
|
|
40
|
+
assert.strictEqual(
|
|
41
|
+
install.checksumFor("abc123 soldr-v0.7.5-x86_64-unknown-linux-gnu.tar.gz\n", "soldr-v0.7.5-x86_64-unknown-linux-gnu.tar.gz"),
|
|
42
|
+
"abc123",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
console.log("npm package checks passed");
|