@lowdep/hashsum 1.0.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/bin/hashsum.js +194 -0
  4. package/package.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rushabh Shah
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,119 @@
1
+ # hashsum
2
+
3
+ ![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen) ![Node](https://img.shields.io/badge/node-%3E%3D14-blue) ![License: MIT](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
4
+
5
+
6
+ Compute and verify file checksums with one consistent interface on every OS. Supports MD5, SHA-1, SHA-256, and SHA-512. Zero dependencies.
7
+
8
+ No more remembering `sha256sum` (Linux) vs `shasum -a 256` (macOS) vs `certutil -hashfile` / `Get-FileHash` (Windows) — all of which print different formats.
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g hashsum
16
+ ```
17
+
18
+ Or without installing:
19
+
20
+ ```bash
21
+ npx hashsum file.zip
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ hashsum file.zip # SHA-256 (default)
30
+ hashsum *.iso -a sha512 # SHA-512 of several files
31
+ hashsum dist/* --save # Write sha256sums.txt
32
+ hashsum -c sha256sums.txt # Verify files against the list
33
+ hashsum --string "hello" -a md5 # Hash a literal string
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Output Format
39
+
40
+ Matches `sha256sum`, so the files are interoperable:
41
+
42
+ ```
43
+ $ hashsum archive.tar.gz
44
+ 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 archive.tar.gz
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Verify Downloads
50
+
51
+ Generate a checksum list:
52
+
53
+ ```bash
54
+ hashsum *.iso -a sha256 --save # → sha256sums.txt
55
+ ```
56
+
57
+ Verify later (the algorithm is auto-detected from each hash's length):
58
+
59
+ ```bash
60
+ $ hashsum -c sha256sums.txt
61
+
62
+ hashsum verifying 3 file(s) from sha256sums.txt
63
+
64
+ ✓ OK ubuntu.iso
65
+ ✗ FAILED corrupted.iso
66
+ ? MISSING deleted.iso
67
+
68
+ 1 OK · 1 failed · 1 missing
69
+ ```
70
+
71
+ Exit code is `1` if any file fails or is missing — so it works in CI and scripts.
72
+
73
+ ---
74
+
75
+ ## Supported Algorithms
76
+
77
+ | Algorithm | Hash length | Notes |
78
+ |---|---|---|
79
+ | `md5` | 32 hex | Fast, **not** for security |
80
+ | `sha1` | 40 hex | Legacy |
81
+ | `sha256` | 64 hex | **Default** — the modern standard |
82
+ | `sha512` | 128 hex | Stronger / faster on 64-bit |
83
+
84
+ When verifying with `-c`, the algorithm is inferred from each hash's length, so a mixed file works.
85
+
86
+ ---
87
+
88
+ ## Options
89
+
90
+ | Flag | Description |
91
+ |---|---|
92
+ | `-a, --algo <name>` | `md5` \| `sha1` \| `sha256` \| `sha512` |
93
+ | `-c, --check <file>` | Verify files listed in a checksums file |
94
+ | `--string <text>` | Hash a string instead of a file |
95
+ | `--save` | Write `<algo>sums.txt` |
96
+ | `--json` | JSON output |
97
+ | `--quiet` | Print only the hash / exit code |
98
+
99
+ ---
100
+
101
+ ## License
102
+
103
+ MIT
104
+
105
+ ---
106
+
107
+ ## Keywords
108
+
109
+ `checksum` · `sha256` · `md5` · `file hash` · `sha256sum windows` · `verify checksum` · `sha512` · `integrity check` · `cross-platform` · `zero dependencies`
110
+
111
+ ---
112
+
113
+ <div align="center">
114
+
115
+ **Built to solve, shared to help — Rushabh Shah 🛠️✨**
116
+
117
+ <sub>One of 40+ zero-dependency developer CLI tools — no <code>node_modules</code>, ever.</sub>
118
+
119
+ </div>
package/bin/hashsum.js ADDED
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const crypto = require('crypto');
7
+
8
+ const VERSION = '1.0.0';
9
+
10
+ // ─── ANSI ─────────────────────────────────────────────────────────────────────
11
+ const isTTY = process.stdout.isTTY;
12
+ const c = (code, t) => isTTY ? `\x1b[${code}m${t}\x1b[0m` : t;
13
+ const bold = t => c('1', t);
14
+ const dim = t => c('2', t);
15
+ const red = t => c('31', t);
16
+ const green = t => c('32', t);
17
+ const yellow = t => c('33', t);
18
+ const cyan = t => c('36', t);
19
+
20
+ const ALGOS = ['md5', 'sha1', 'sha256', 'sha512'];
21
+
22
+ // ─── Hash a file (streaming) ──────────────────────────────────────────────────
23
+ function hashFile(filePath, algo) {
24
+ return new Promise((resolve, reject) => {
25
+ const hash = crypto.createHash(algo);
26
+ const stream = fs.createReadStream(filePath);
27
+ stream.on('data', d => hash.update(d));
28
+ stream.on('end', () => resolve(hash.digest('hex')));
29
+ stream.on('error', reject);
30
+ });
31
+ }
32
+
33
+ function hashString(str, algo) {
34
+ return crypto.createHash(algo).update(str).digest('hex');
35
+ }
36
+
37
+ // ─── Parse a checksums file ───────────────────────────────────────────────────
38
+ // Lines look like: <hex> <filename> (two spaces, like sha256sum)
39
+ // or: <hex> *<filename> (binary mode marker)
40
+ function parseChecksumFile(content) {
41
+ const entries = [];
42
+ for (const line of content.split(/\r?\n/)) {
43
+ const trimmed = line.trim();
44
+ if (!trimmed || trimmed.startsWith('#')) continue;
45
+ const m = trimmed.match(/^([0-9a-fA-F]{32,128})\s+\*?(.+)$/);
46
+ if (m) entries.push({ hash: m[1].toLowerCase(), file: m[2].trim() });
47
+ }
48
+ return entries;
49
+ }
50
+
51
+ function algoForHashLength(hex) {
52
+ switch (hex.length) {
53
+ case 32: return 'md5';
54
+ case 40: return 'sha1';
55
+ case 64: return 'sha256';
56
+ case 128: return 'sha512';
57
+ default: return null;
58
+ }
59
+ }
60
+
61
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
62
+ const args = process.argv.slice(2);
63
+ const VALUE_FLAGS = new Set(['-a', '--algo', '-c', '--check', '--string']);
64
+
65
+ function getFlag(...names) { for (const f of names) { const i = args.indexOf(f); if (i !== -1) return args[i + 1]; } return null; }
66
+ function hasFlag(...names) { return names.some(f => args.includes(f)); }
67
+
68
+ const positional = [];
69
+ for (let i = 0; i < args.length; i++) {
70
+ if (args[i].startsWith('-') && args[i] !== '-') { if (VALUE_FLAGS.has(args[i])) i++; }
71
+ else positional.push(args[i]);
72
+ }
73
+
74
+ if (hasFlag('--version', '-v')) { console.log(`hashsum v${VERSION}`); process.exit(0); }
75
+
76
+ if (hasFlag('--help', '-h') || (!positional.length && !getFlag('-c', '--check') && getFlag('--string') === null)) {
77
+ console.log(`
78
+ ${bold('hashsum')} — Compute and verify file checksums, cross-platform, zero deps
79
+
80
+ ${bold('USAGE')}
81
+ hashsum [file...] [options]
82
+ hashsum -c <checksums-file> Verify files against a checksum list
83
+ hashsum --string "text" -a sha256 Hash a literal string
84
+
85
+ ${bold('OPTIONS')}
86
+ -a, --algo <name> md5 | sha1 | sha256 (default) | sha512
87
+ -c, --check <file> Verify files listed in a checksums file
88
+ --string <text> Hash a string instead of a file
89
+ --save Write "<hash> <file>" lines to <algo>sums.txt
90
+ --json JSON output
91
+ --quiet Minimal output (just results / exit code)
92
+ --version Show version
93
+
94
+ ${bold('EXAMPLES')}
95
+ hashsum file.zip # sha256 of one file
96
+ hashsum *.iso -a sha512 # sha512 of several files
97
+ hashsum dist/* --save # write sha256sums.txt
98
+ hashsum -c sha256sums.txt # verify against the list
99
+ hashsum --string "hello" -a md5 # hash a string
100
+
101
+ ${bold('OUTPUT FORMAT')} matches sha256sum: ${dim('<hex> <filename>')}
102
+ `);
103
+ process.exit(0);
104
+ }
105
+
106
+ const algo = (getFlag('-a', '--algo') || 'sha256').toLowerCase();
107
+ const checkFile = getFlag('-c', '--check');
108
+ const strInput = getFlag('--string');
109
+ const save = hasFlag('--save');
110
+ const asJson = hasFlag('--json');
111
+ const quiet = hasFlag('--quiet');
112
+
113
+ if (!checkFile && !ALGOS.includes(algo)) {
114
+ console.error(red(`\nUnknown algorithm: ${algo}. Use: ${ALGOS.join(', ')}\n`)); process.exit(2);
115
+ }
116
+
117
+ (async () => {
118
+ // ── Verify mode ──
119
+ if (checkFile) {
120
+ if (!fs.existsSync(checkFile)) { console.error(red(`\nChecksums file not found: ${checkFile}\n`)); process.exit(2); }
121
+ const baseDir = path.dirname(path.resolve(checkFile));
122
+ const entries = parseChecksumFile(fs.readFileSync(checkFile, 'utf8'));
123
+ if (!entries.length) { console.error(yellow('\nNo checksum entries found in file.\n')); process.exit(2); }
124
+
125
+ if (!quiet && !asJson) console.log(`\n${bold('hashsum')} ${dim('verifying ' + entries.length + ' file(s) from ' + path.basename(checkFile))}\n`);
126
+
127
+ let ok = 0, failed = 0, missing = 0;
128
+ const results = [];
129
+ for (const e of entries) {
130
+ const filePath = path.resolve(baseDir, e.file);
131
+ const useAlgo = algoForHashLength(e.hash) || algo;
132
+ let status;
133
+ if (!fs.existsSync(filePath)) { status = 'missing'; missing++; }
134
+ else {
135
+ const actual = await hashFile(filePath, useAlgo);
136
+ status = actual === e.hash ? 'ok' : 'failed';
137
+ if (status === 'ok') ok++; else failed++;
138
+ }
139
+ results.push({ file: e.file, status, algo: useAlgo });
140
+ if (!quiet && !asJson) {
141
+ const sym = status === 'ok' ? green('✓ OK') : status === 'missing' ? yellow('? MISSING') : red('✗ FAILED');
142
+ console.log(` ${sym} ${e.file}`);
143
+ }
144
+ }
145
+
146
+ if (asJson) {
147
+ console.log(JSON.stringify({ checked: entries.length, ok, failed, missing, results }, null, 2));
148
+ } else if (!quiet) {
149
+ console.log();
150
+ const parts = [green(`${ok} OK`)];
151
+ if (failed) parts.push(red(`${failed} failed`));
152
+ if (missing) parts.push(yellow(`${missing} missing`));
153
+ console.log(` ${parts.join(' · ')}\n`);
154
+ }
155
+ process.exit(failed > 0 || missing > 0 ? 1 : 0);
156
+ }
157
+
158
+ // ── String mode ──
159
+ if (strInput !== null) {
160
+ const digest = hashString(strInput, algo);
161
+ if (asJson) console.log(JSON.stringify({ algo, input: strInput, hash: digest }, null, 2));
162
+ else console.log(quiet ? digest : `${digest} ${dim('(' + algo + ' of string)')}`);
163
+ process.exit(0);
164
+ }
165
+
166
+ // ── Compute mode ──
167
+ if (!positional.length) { console.error(red('\nNo files given.\n')); process.exit(1); }
168
+
169
+ const lines = [];
170
+ const jsonOut = [];
171
+ let hadError = false;
172
+
173
+ for (const p of positional) {
174
+ const filePath = path.resolve(p);
175
+ if (!fs.existsSync(filePath)) { console.error(red(`hashsum: ${p}: no such file`)); hadError = true; continue; }
176
+ if (fs.statSync(filePath).isDirectory()) { console.error(yellow(`hashsum: ${p}: is a directory (skipped)`)); continue; }
177
+ const digest = await hashFile(filePath, algo);
178
+ lines.push(`${digest} ${p}`);
179
+ jsonOut.push({ file: p, algo, hash: digest });
180
+ if (!save && !asJson) {
181
+ console.log(quiet ? digest : `${cyan(digest)} ${p}`);
182
+ }
183
+ }
184
+
185
+ if (asJson) { console.log(JSON.stringify(jsonOut, null, 2)); process.exit(hadError ? 1 : 0); }
186
+
187
+ if (save) {
188
+ const outName = `${algo}sums.txt`;
189
+ fs.writeFileSync(outName, lines.join('\n') + '\n');
190
+ console.log(`${green('✓')} Wrote ${lines.length} checksum(s) to ${cyan(outName)}`);
191
+ }
192
+
193
+ process.exit(hadError ? 1 : 0);
194
+ })();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@lowdep/hashsum",
3
+ "version": "1.0.0",
4
+ "description": "Compute and verify file checksums (md5/sha1/sha256/sha512) with a consistent cross-platform interface — zero dependencies",
5
+ "bin": {
6
+ "hashsum": "bin/hashsum.js"
7
+ },
8
+ "keywords": [
9
+ "checksum",
10
+ "hash",
11
+ "sha256",
12
+ "md5",
13
+ "sha512",
14
+ "verify",
15
+ "integrity",
16
+ "cli",
17
+ "cross-platform",
18
+ "zero-dependencies",
19
+ "file hash",
20
+ "sha256sum windows",
21
+ "verify checksum",
22
+ "integrity check",
23
+ "zero dependencies"
24
+ ],
25
+ "author": "Rushabh Shah",
26
+ "license": "MIT",
27
+ "engines": {
28
+ "node": ">=14"
29
+ },
30
+ "files": [
31
+ "bin/"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Rushabh5000/hashsum.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/Rushabh5000/hashsum/issues"
39
+ },
40
+ "homepage": "https://github.com/Rushabh5000/hashsum#readme",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }