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 +22 -2
- package/bin/uncomment +0 -0
- package/install.js +126 -108
- package/package.json +5 -7
- package/index.js +0 -37
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
|
-
- 🔧 **
|
|
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
|
|
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 (
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
+
const archivePath = path.join(stagingDir, archiveName);
|
|
141
|
+
fs.writeFileSync(archivePath, archive);
|
|
128
142
|
|
|
129
|
-
|
|
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(
|
|
148
|
+
throw new Error(`${memberName} not found in ${archiveName}`);
|
|
136
149
|
}
|
|
137
|
-
zip.extractEntryTo(entry,
|
|
150
|
+
zip.extractEntryTo(entry, stagingDir, false, true);
|
|
138
151
|
} else {
|
|
139
152
|
await tar.extract({
|
|
140
153
|
file: archivePath,
|
|
141
|
-
cwd:
|
|
142
|
-
filter: (entryPath) =>
|
|
154
|
+
cwd: stagingDir,
|
|
155
|
+
filter: (entryPath) => path.basename(entryPath) === memberName,
|
|
143
156
|
});
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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.
|
|
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.
|
|
35
|
-
"tar": "^
|
|
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": ">=
|
|
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;
|