uncomment-cli 3.6.0 → 3.8.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/README.md CHANGED
@@ -11,7 +11,7 @@ A blazing fast Rust-based command-line tool that removes comments from your sour
11
11
  - 🎯 **100% Accurate** - Never accidentally removes code that looks like comments
12
12
  - 🛡️ **Safe by Default** - Preview changes before applying them
13
13
  - 🌍 **Multi-language** - 49 built-in languages, 306 available via [tree-sitter-language-pack](https://github.com/kreuzberg-dev/tree-sitter-language-pack)
14
- - 🔧 **Zero Dependencies** - Downloads a self-contained binary
14
+ - 🔧 **Self-contained** - Installs a single static Rust binary, checksum-verified against the GitHub release
15
15
 
16
16
  ## Installation
17
17
 
@@ -19,7 +19,27 @@ A blazing fast Rust-based command-line tool that removes comments from your sour
19
19
  npm install -g uncomment-cli
20
20
  ```
21
21
 
22
- The installer will automatically download the appropriate pre-compiled Rust binary for your platform (Windows, macOS, or Linux).
22
+ The postinstall step downloads the pre-compiled binary for your platform and verifies
23
+ it against the release checksum manifest. Prebuilt archives exist for:
24
+
25
+ | Platform | Architectures |
26
+ | --- | --- |
27
+ | Linux (glibc) | `x86_64`, `aarch64` |
28
+ | macOS | `x86_64`, `aarch64` |
29
+ | Windows | `x86_64` |
30
+
31
+ Anything else — musl/Alpine Linux, 32-bit, other architectures — has no prebuilt
32
+ archive; install with `cargo install uncomment` instead.
33
+
34
+ ## Commands
35
+
36
+ | Command | What it does |
37
+ | --- | --- |
38
+ | `uncomment <paths>` | Remove comments (the default, with no subcommand) |
39
+ | `uncomment init` | Write a template `.uncomment.toml` |
40
+ | `uncomment scan` | Report every comment and the verdict a run would reach; writes no files |
41
+ | `uncomment keep` | Write `~keep` markers into the comments a decision selected |
42
+ | `uncomment lint` | Check tag comments (TODO, FIXME, HACK, XXX) against the `[lint]` convention |
23
43
 
24
44
  ## Quick Start
25
45
 
package/bin/uncomment CHANGED
Binary file
package/install.js CHANGED
@@ -1,159 +1,177 @@
1
+ const crypto = require("node:crypto");
1
2
  const fs = require("node:fs");
3
+ const https = require("node:https");
2
4
  const os = require("node:os");
3
5
  const path = require("node:path");
4
- const https = require("node:https");
5
- const http = require("node:http");
6
6
  const tar = require("tar");
7
7
  const AdmZip = require("adm-zip");
8
8
 
9
9
  const { version } = require("./package.json");
10
10
 
11
+ const BASE_URL = `https://github.com/Goldziher/uncomment/releases/download/v${version}`;
12
+
13
+ // Must stay in step with the archives .github/workflows/publish.yaml uploads.
14
+ const TRIPLES = {
15
+ "Windows_NT:x64": "x86_64-pc-windows-gnu",
16
+ "Linux:x64": "x86_64-unknown-linux-gnu",
17
+ "Linux:arm64": "aarch64-unknown-linux-gnu",
18
+ "Darwin:x64": "x86_64-apple-darwin",
19
+ "Darwin:arm64": "aarch64-apple-darwin",
20
+ };
21
+
22
+ function assertGlibc() {
23
+ // The Linux archives are gnu-linked. On a musl host (Alpine) they resolve at
24
+ // download time and then fail at exec with a bare "not found", so say so here.
25
+ const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
26
+ if (report && report.header && report.header.glibcVersionRuntime) {
27
+ return;
28
+ }
29
+ if (fs.existsSync("/etc/alpine-release")) {
30
+ throw new Error(
31
+ "musl-based Linux (Alpine) is not supported: only gnu-linked Linux archives are published. Build from source with `cargo install uncomment`.",
32
+ );
33
+ }
34
+ }
35
+
11
36
  function getPlatformTriple() {
12
37
  const type = os.type();
13
38
  const arch = os.arch();
39
+ const triple = TRIPLES[`${type}:${arch}`];
14
40
 
15
- if (type === "Windows_NT") {
16
- if (arch === "x64") return "x86_64-pc-windows-gnu";
17
- if (arch === "ia32") throw new Error("32-bit Windows is not supported");
41
+ if (!triple) {
42
+ throw new Error(
43
+ `No prebuilt uncomment binary for ${type} ${arch}. Supported: ${Object.keys(TRIPLES).join(", ")}. Build from source with \`cargo install uncomment\`.`,
44
+ );
18
45
  }
19
46
 
20
47
  if (type === "Linux") {
21
- if (arch === "x64") return "x86_64-unknown-linux-gnu";
22
- if (arch === "arm64") return "aarch64-unknown-linux-gnu";
23
- return "x86_64-unknown-linux-gnu";
24
- }
25
-
26
- if (type === "Darwin") {
27
- if (arch === "x64") return "x86_64-apple-darwin";
28
- if (arch === "arm64") return "aarch64-apple-darwin";
29
- return "x86_64-apple-darwin";
48
+ assertGlibc();
30
49
  }
31
50
 
32
- throw new Error(`Unsupported platform: ${type} ${arch}`);
33
- }
34
-
35
- function getBinaryUrl() {
36
- const platform = getPlatformTriple();
37
- const baseUrl = `https://github.com/Goldziher/uncomment/releases/download/v${version}`;
38
- const ext = platform.includes("windows") ? "zip" : "tar.gz";
39
- return `${baseUrl}/uncomment-${platform}.${ext}`;
51
+ return triple;
40
52
  }
41
53
 
42
- function downloadWithRedirects(url, dest, maxRedirects = 5) {
54
+ function httpsGet(url, maxRedirects = 5) {
43
55
  return new Promise((resolve, reject) => {
44
56
  if (maxRedirects <= 0) {
45
57
  return reject(new Error("Too many redirects"));
46
58
  }
47
59
 
48
- const urlObj = new URL(url);
49
- const client = urlObj.protocol === "https:" ? https : http;
50
-
51
- const req = client.get(
52
- url,
53
- {
54
- headers: {
55
- "User-Agent": "uncomment-npm-wrapper",
56
- },
57
- },
58
- (res) => {
59
- if (
60
- res.statusCode >= 300 &&
61
- res.statusCode < 400 &&
62
- res.headers.location
63
- ) {
64
- return downloadWithRedirects(
65
- res.headers.location,
66
- dest,
67
- maxRedirects - 1,
68
- )
69
- .then(resolve)
70
- .catch(reject);
71
- }
72
-
73
- if (res.statusCode !== 200) {
74
- return reject(
75
- new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`),
76
- );
77
- }
78
-
79
- const file = fs.createWriteStream(dest);
80
- res.pipe(file);
81
-
82
- file.on("finish", () => {
83
- file.close();
84
- resolve();
85
- });
86
-
87
- file.on("error", (err) => {
88
- fs.unlink(dest, () => {});
89
- reject(err);
90
- });
91
- },
92
- );
60
+ // HTTPS only. Following a redirect down to plain HTTP would let a network
61
+ // attacker substitute the archive that gets unpacked below.
62
+ if (new URL(url).protocol !== "https:") {
63
+ return reject(new Error(`Refusing non-HTTPS download URL: ${url}`));
64
+ }
65
+
66
+ const req = https.get(url, { headers: { "User-Agent": "uncomment-npm-wrapper" } }, (res) => {
67
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
68
+ res.resume();
69
+ return httpsGet(new URL(res.headers.location, url).toString(), maxRedirects - 1)
70
+ .then(resolve)
71
+ .catch(reject);
72
+ }
73
+
74
+ if (res.statusCode !== 200) {
75
+ res.resume();
76
+ return reject(new Error(`HTTP ${res.statusCode} ${res.statusMessage} for ${url}`));
77
+ }
78
+
79
+ const chunks = [];
80
+ res.on("data", (chunk) => chunks.push(chunk));
81
+ res.on("end", () => resolve(Buffer.concat(chunks)));
82
+ res.on("error", reject);
83
+ });
93
84
 
94
85
  req.on("error", reject);
95
- req.setTimeout(30000, () => {
96
- req.destroy();
97
- reject(new Error("Download timeout"));
86
+ req.setTimeout(60000, () => {
87
+ req.destroy(new Error(`Download timed out: ${url}`));
98
88
  });
99
89
  });
100
90
  }
101
91
 
92
+ function expectedDigest(checksums, archiveName) {
93
+ for (const line of checksums.toString("utf8").split("\n")) {
94
+ const match = line.trim().match(/^([0-9a-f]{64})\s+\*?(\S+)$/);
95
+ if (match && path.basename(match[2]) === archiveName) {
96
+ return match[1];
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+
102
102
  async function installBinary() {
103
- try {
104
- const url = getBinaryUrl();
105
- const isZip = url.endsWith(".zip");
106
- const binDir = path.join(__dirname, "bin");
107
- const archivePath = path.join(
108
- binDir,
109
- isZip ? "uncomment.zip" : "uncomment.tar.gz",
110
- );
111
- const binaryName =
112
- os.type() === "Windows_NT" ? "uncomment.exe" : "uncomment";
113
- const binaryPath = path.join(binDir, binaryName);
103
+ const triple = getPlatformTriple();
104
+ const isWindows = os.type() === "Windows_NT";
105
+ const archiveName = `uncomment-${triple}.${isWindows ? "zip" : "tar.gz"}`;
114
106
 
115
- if (!fs.existsSync(binDir)) {
116
- fs.mkdirSync(binDir, { recursive: true });
117
- }
107
+ const binDir = path.join(__dirname, "bin");
108
+ // bin/uncomment is the committed launcher stub; the native executable lives
109
+ // beside it under a distinct name so extraction never overwrites the stub.
110
+ const binaryName = isWindows ? "uncomment.exe" : "uncomment-bin";
111
+ const binaryPath = path.join(binDir, binaryName);
112
+ const memberName = isWindows ? "uncomment.exe" : "uncomment";
118
113
 
119
- if (fs.existsSync(binaryPath)) {
120
- return;
121
- }
114
+ fs.mkdirSync(binDir, { recursive: true });
115
+
116
+ if (fs.existsSync(binaryPath)) {
117
+ return;
118
+ }
119
+
120
+ const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "uncomment-install-"));
122
121
 
123
- console.log(`Downloading uncomment binary from ${url}...`);
122
+ try {
123
+ console.log(`Downloading ${archiveName}...`);
124
+ const archive = await httpsGet(`${BASE_URL}/${archiveName}`);
125
+
126
+ // Verify against the release's own checksum manifest before unpacking. The
127
+ // archive readers below (node-tar, adm-zip) have a history of path-traversal
128
+ // issues, so nothing hostile should reach them in the first place.
129
+ const checksums = await httpsGet(`${BASE_URL}/uncomment_${version}_checksums.txt`);
130
+ const expected = expectedDigest(checksums, archiveName);
131
+ if (!expected) {
132
+ throw new Error(`No checksum published for ${archiveName}; refusing to install.`);
133
+ }
124
134
 
125
- await downloadWithRedirects(url, archivePath);
135
+ const actual = crypto.createHash("sha256").update(archive).digest("hex");
136
+ if (actual !== expected) {
137
+ throw new Error(`Checksum mismatch for ${archiveName}: expected ${expected}, got ${actual}`);
138
+ }
126
139
 
127
- console.log("Extracting binary...");
140
+ const archivePath = path.join(stagingDir, archiveName);
141
+ fs.writeFileSync(archivePath, archive);
128
142
 
129
- if (isZip) {
143
+ console.log("Extracting...");
144
+ if (isWindows) {
130
145
  const zip = new AdmZip(archivePath);
131
- const entry = zip
132
- .getEntries()
133
- .find((e) => e.entryName.endsWith(binaryName));
146
+ const entry = zip.getEntries().find((e) => path.basename(e.entryName) === memberName);
134
147
  if (!entry) {
135
- throw new Error("Binary not found in downloaded archive");
148
+ throw new Error(`${memberName} not found in ${archiveName}`);
136
149
  }
137
- zip.extractEntryTo(entry, binDir, false, true);
150
+ zip.extractEntryTo(entry, stagingDir, false, true);
138
151
  } else {
139
152
  await tar.extract({
140
153
  file: archivePath,
141
- cwd: binDir,
142
- filter: (entryPath) => entryPath.endsWith(binaryName),
154
+ cwd: stagingDir,
155
+ filter: (entryPath) => path.basename(entryPath) === memberName,
143
156
  });
144
157
  }
145
158
 
146
- fs.unlinkSync(archivePath);
147
-
148
- if (os.type() !== "Windows_NT") {
149
- fs.chmodSync(binaryPath, 0o755);
159
+ const extracted = path.join(stagingDir, memberName);
160
+ if (!fs.existsSync(extracted)) {
161
+ throw new Error(`${archiveName} did not contain ${memberName}`);
150
162
  }
151
163
 
152
- console.log("uncomment binary installed successfully!");
153
- } catch (error) {
154
- console.error("Error installing uncomment binary:", error.message);
155
- process.exit(1);
164
+ // copy + unlink rather than rename: the staging dir is in os.tmpdir(), which is
165
+ // often a different filesystem from node_modules, and rename would fail EXDEV.
166
+ fs.copyFileSync(extracted, binaryPath);
167
+ fs.chmodSync(binaryPath, 0o755);
168
+ console.log(`uncomment ${version} installed (${triple}).`);
169
+ } finally {
170
+ fs.rmSync(stagingDir, { recursive: true, force: true });
156
171
  }
157
172
  }
158
173
 
159
- installBinary();
174
+ installBinary().catch((error) => {
175
+ console.error(`Failed to install the uncomment binary: ${error.message}`);
176
+ process.exit(1);
177
+ });
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "uncomment-cli",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "description": "A fast Rust-based CLI tool for removing comments from source code",
5
- "main": "index.js",
6
5
  "bin": {
7
6
  "uncomment": "bin/uncomment"
8
7
  },
@@ -31,15 +30,14 @@
31
30
  "url": "https://github.com/Goldziher/uncomment/issues"
32
31
  },
33
32
  "dependencies": {
34
- "adm-zip": "^0.6.0",
35
- "tar": "^6.0.0"
33
+ "adm-zip": "^0.6.1",
34
+ "tar": "^7.5.22"
36
35
  },
37
36
  "files": [
38
- "index.js",
39
37
  "install.js",
40
- "bin/"
38
+ "bin/uncomment"
41
39
  ],
42
40
  "engines": {
43
- "node": ">=14"
41
+ "node": ">=22"
44
42
  }
45
43
  }
package/index.js DELETED
@@ -1,37 +0,0 @@
1
- const { Binary } = require("binary-install");
2
- const os = require("os");
3
- const { version } = require("./package.json");
4
-
5
- function getPlatform() {
6
- const type = os.type();
7
- const arch = os.arch();
8
-
9
- if (type === "Windows_NT") {
10
- return arch === "x64" ? "x86_64-pc-windows-msvc" : "i686-pc-windows-msvc";
11
- }
12
-
13
- if (type === "Linux") {
14
- if (arch === "x64") return "x86_64-unknown-linux-gnu";
15
- if (arch === "arm64") return "aarch64-unknown-linux-gnu";
16
- return "x86_64-unknown-linux-gnu";
17
- }
18
-
19
- if (type === "Darwin") {
20
- if (arch === "x64") return "x86_64-apple-darwin";
21
- if (arch === "arm64") return "aarch64-apple-darwin";
22
- return "x86_64-apple-darwin";
23
- }
24
-
25
- throw new Error(`Unsupported platform: ${type} ${arch}`);
26
- }
27
-
28
- function getBinaryUrl() {
29
- const platform = getPlatform();
30
- const ext = os.type() === "Windows_NT" ? ".exe" : "";
31
- const baseUrl = `https://github.com/Goldziher/uncomment/releases/download/v${version}`;
32
- return `${baseUrl}/uncomment-${platform}${ext}`;
33
- }
34
-
35
- const binary = new Binary("uncomment", getBinaryUrl());
36
-
37
- module.exports = binary;