@goldziher/voom 0.0.0 → 0.2.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Na'aman Hirschfeld
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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @goldziher/voom
2
2
 
3
- Fast, safe, parallel build-artifact pruning across 20+ language ecosystems.
3
+ Fast, safe, parallel build-artifact pruning across every major language ecosystem.
4
4
 
5
5
  ```bash
6
6
  npm install -g @goldziher/voom
package/bin/voom ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // The `voom` command. Resolves the binary — fetching it on first run — and hands over to it.
3
+ //
4
+ // A committed launcher rather than the binary itself, because npm links `bin` at install time
5
+ // and the binary does not exist yet at that point. See download.js for why the download is not
6
+ // a postinstall script.
7
+
8
+ const { spawn } = require("node:child_process");
9
+ const { ensureBinary } = require("../download.js");
10
+
11
+ ensureBinary()
12
+ .then((binaryPath) => {
13
+ const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
14
+ // Exit the way the binary did. A signal death has no exit status, and reporting 0 for one
15
+ // would tell a hook or a CI job that a sweep interrupted halfway through had succeeded.
16
+ child.on("exit", (code, signal) => {
17
+ if (signal) {
18
+ process.kill(process.pid, signal);
19
+ return;
20
+ }
21
+ process.exit(code ?? 1);
22
+ });
23
+ child.on("error", (error) => {
24
+ process.stderr.write(`voom: could not run ${binaryPath}: ${error.message}\n`);
25
+ process.exit(1);
26
+ });
27
+ })
28
+ .catch((error) => {
29
+ process.stderr.write(`voom: ${error.message}\n`);
30
+ process.exit(1);
31
+ });
package/download.js ADDED
@@ -0,0 +1,171 @@
1
+ // Resolves the voom binary, fetching it from the GitHub release on first use.
2
+ //
3
+ // This is deliberately *not* a postinstall script. npm 11.19 blocks install scripts by default
4
+ // (`allow-scripts`), and a blocked postinstall does not merely skip the download — npm declines
5
+ // to create the bin link at all, so `npm i -g @goldziher/voom` produced `voom: command not
6
+ // found` and `npx -y @goldziher/voom` silently did nothing. Fetching lazily from `bin/voom`
7
+ // instead means the package works with scripts disabled, which is now the default and will only
8
+ // get stricter. It is also what the PyPI wrapper has always done (pip-package/voom/downloader.py),
9
+ // so the two channels now behave the same way.
10
+
11
+ const fs = require("node:fs");
12
+ const os = require("node:os");
13
+ const path = require("node:path");
14
+ const https = require("node:https");
15
+ const http = require("node:http");
16
+ const tar = require("tar");
17
+ const AdmZip = require("adm-zip");
18
+
19
+ const { version } = require("./package.json");
20
+
21
+ const REPO = "Goldziher/voom";
22
+ const BINARY = "voom";
23
+
24
+ function getPlatformTriple() {
25
+ const type = os.type();
26
+ const arch = os.arch();
27
+
28
+ if (type === "Windows_NT") {
29
+ if (arch === "x64") return "x86_64-pc-windows-gnu";
30
+ throw new Error(`Unsupported Windows architecture: ${arch}`);
31
+ }
32
+
33
+ if (type === "Linux") {
34
+ if (arch === "x64") return "x86_64-unknown-linux-gnu";
35
+ if (arch === "arm64") return "aarch64-unknown-linux-gnu";
36
+ throw new Error(`Unsupported Linux architecture: ${arch}`);
37
+ }
38
+
39
+ if (type === "Darwin") {
40
+ if (arch === "x64") return "x86_64-apple-darwin";
41
+ if (arch === "arm64") return "aarch64-apple-darwin";
42
+ throw new Error(`Unsupported macOS architecture: ${arch}`);
43
+ }
44
+
45
+ throw new Error(`Unsupported platform: ${type} ${arch}`);
46
+ }
47
+
48
+ // npm keeps the `-rc.N` form that git tags use, so no normalization is needed here.
49
+ // The PyPI wrapper does need it — see pip-package/voom/downloader.py.
50
+ function getBinaryUrl() {
51
+ const platform = getPlatformTriple();
52
+ const ext = platform.includes("windows") ? "zip" : "tar.gz";
53
+ return `https://github.com/${REPO}/releases/download/v${version}/${BINARY}-${platform}.${ext}`;
54
+ }
55
+
56
+ // Per-user rather than inside the package directory: a global install often lands in a
57
+ // root-owned prefix that the running user cannot write to, which is exactly when the first run
58
+ // would need to write. Keyed by version so an upgrade fetches rather than reusing the old one.
59
+ function cacheDir() {
60
+ if (os.type() === "Windows_NT" && process.env.LOCALAPPDATA) {
61
+ return path.join(process.env.LOCALAPPDATA, BINARY, version);
62
+ }
63
+ return path.join(os.homedir(), ".cache", BINARY, version);
64
+ }
65
+
66
+ function binaryName() {
67
+ return os.type() === "Windows_NT" ? `${BINARY}.exe` : BINARY;
68
+ }
69
+
70
+ function downloadWithRedirects(url, dest, maxRedirects = 5) {
71
+ return new Promise((resolve, reject) => {
72
+ if (maxRedirects <= 0) {
73
+ return reject(new Error("Too many redirects"));
74
+ }
75
+
76
+ const urlObj = new URL(url);
77
+ const client = urlObj.protocol === "https:" ? https : http;
78
+
79
+ const req = client.get(url, { headers: { "User-Agent": "voom-npm-wrapper" } }, (res) => {
80
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
81
+ return downloadWithRedirects(res.headers.location, dest, maxRedirects - 1)
82
+ .then(resolve)
83
+ .catch(reject);
84
+ }
85
+
86
+ if (res.statusCode !== 200) {
87
+ return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
88
+ }
89
+
90
+ const file = fs.createWriteStream(dest);
91
+ res.pipe(file);
92
+
93
+ file.on("finish", () => {
94
+ file.close();
95
+ resolve();
96
+ });
97
+
98
+ file.on("error", (err) => {
99
+ fs.unlink(dest, () => {});
100
+ reject(err);
101
+ });
102
+ });
103
+
104
+ req.on("error", reject);
105
+ req.setTimeout(30000, () => {
106
+ req.destroy();
107
+ reject(new Error("Download timeout"));
108
+ });
109
+ });
110
+ }
111
+
112
+ /// Returns a path to the voom binary, downloading it on first use.
113
+ async function ensureBinary() {
114
+ const override = process.env.VOOM_BINARY;
115
+ if (override) {
116
+ return override;
117
+ }
118
+
119
+ const dir = cacheDir();
120
+ const name = binaryName();
121
+ const binaryPath = path.join(dir, name);
122
+ if (fs.existsSync(binaryPath)) {
123
+ return binaryPath;
124
+ }
125
+
126
+ const url = getBinaryUrl();
127
+ const isZip = url.endsWith(".zip");
128
+ fs.mkdirSync(dir, { recursive: true });
129
+
130
+ // Two concurrent invocations would otherwise race on the same target. Each unpacks into its
131
+ // own staging directory and renames, which is atomic within a filesystem — the loser's rename
132
+ // simply replaces an identical file.
133
+ const staging = fs.mkdtempSync(path.join(dir, ".staging-"));
134
+ const archivePath = path.join(staging, isZip ? `${BINARY}.zip` : `${BINARY}.tar.gz`);
135
+
136
+ process.stderr.write(`Downloading voom binary v${version}...\n`);
137
+ try {
138
+ await downloadWithRedirects(url, archivePath);
139
+
140
+ if (isZip) {
141
+ const zip = new AdmZip(archivePath);
142
+ const entry = zip.getEntries().find((e) => e.entryName.endsWith(name));
143
+ if (!entry) {
144
+ throw new Error("Binary not found in downloaded archive");
145
+ }
146
+ zip.extractEntryTo(entry, staging, false, true);
147
+ } else {
148
+ await tar.extract({
149
+ file: archivePath,
150
+ cwd: staging,
151
+ filter: (entryPath) => entryPath.endsWith(name),
152
+ });
153
+ }
154
+
155
+ const staged = path.join(staging, name);
156
+ if (!fs.existsSync(staged)) {
157
+ throw new Error("Binary not found in downloaded archive");
158
+ }
159
+ if (os.type() !== "Windows_NT") {
160
+ fs.chmodSync(staged, 0o755);
161
+ }
162
+ fs.renameSync(staged, binaryPath);
163
+ } finally {
164
+ fs.rmSync(staging, { recursive: true, force: true });
165
+ }
166
+
167
+ process.stderr.write("Binary downloaded successfully.\n");
168
+ return binaryPath;
169
+ }
170
+
171
+ module.exports = { ensureBinary, getBinaryUrl, getPlatformTriple };
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@goldziher/voom",
3
- "version": "0.0.0",
4
- "description": "Placeholder release reserving the package name. Install 0.1.0 or later for the actual CLI.",
5
- "keywords": ["cleanup", "build", "artifacts", "disk-space", "cli", "prune", "rust", "monorepo"],
3
+ "version": "0.2.0",
4
+ "description": "Fast, safe, parallel build-artifact pruning across every major language ecosystem",
5
+ "bin": {
6
+ "voom": "bin/voom"
7
+ },
8
+ "keywords": ["clean", "cleanup", "build-artifacts", "disk-space", "monorepo", "cli", "prune", "rust"],
6
9
  "author": "Na'aman Hirschfeld <nhirschfeld@gmail.com>",
7
10
  "license": "MIT",
8
11
  "repository": {
@@ -16,8 +19,12 @@
16
19
  "publishConfig": {
17
20
  "access": "public"
18
21
  },
19
- "files": ["README.md"],
22
+ "dependencies": {
23
+ "adm-zip": "^0.6.0",
24
+ "tar": "^7.0.0"
25
+ },
26
+ "files": ["bin/", "download.js", "LICENSE", "README.md"],
20
27
  "engines": {
21
- "node": ">=14"
28
+ "node": ">=18"
22
29
  }
23
30
  }