@backendsystems/nibble 0.3.1 → 0.4.1
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/bin/install.js +79 -170
- package/package.json +4 -1
package/bin/install.js
CHANGED
|
@@ -1,211 +1,120 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const os = require('os');
|
|
5
6
|
const path = require('path');
|
|
6
|
-
const https = require('https');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
-
const
|
|
8
|
+
const tar = require('tar');
|
|
9
9
|
|
|
10
10
|
const PROJECT = 'nibble';
|
|
11
11
|
const OWNER = 'backendsystems';
|
|
12
|
-
const REPO = 'nibble';
|
|
13
12
|
const ROOT = path.resolve(__dirname, '..');
|
|
14
13
|
const VENDOR_DIR = path.join(ROOT, 'vendor');
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
function mapPlatform() {
|
|
18
|
-
const platformMap = {
|
|
19
|
-
linux: 'linux',
|
|
20
|
-
darwin: 'darwin',
|
|
21
|
-
win32: 'windows',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const archMap = {
|
|
25
|
-
x64: 'amd64',
|
|
26
|
-
arm64: 'arm64',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const osName = platformMap[process.platform];
|
|
30
|
-
const arch = archMap[process.arch];
|
|
31
|
-
if (!osName || !arch) {
|
|
32
|
-
throw new Error(`unsupported platform: ${process.platform}/${process.arch}`);
|
|
33
|
-
}
|
|
14
|
+
const { version } = require(path.join(ROOT, 'package.json'));
|
|
34
15
|
|
|
35
|
-
|
|
36
|
-
}
|
|
16
|
+
const platformMap = { linux: 'linux', darwin: 'darwin', win32: 'windows' };
|
|
17
|
+
const archMap = { x64: 'amd64', arm64: 'arm64' };
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (res.statusCode !== 200) {
|
|
47
|
-
res.resume();
|
|
48
|
-
return reject(new Error(`download failed (${res.statusCode}): ${url}`));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const file = fs.createWriteStream(outFile);
|
|
52
|
-
res.pipe(file);
|
|
53
|
-
file.on('finish', () => file.close(() => resolve()));
|
|
54
|
-
file.on('error', reject);
|
|
55
|
-
});
|
|
56
|
-
req.on('error', reject);
|
|
57
|
-
});
|
|
19
|
+
const osPlatform = platformMap[process.platform];
|
|
20
|
+
const osArch = archMap[process.arch];
|
|
21
|
+
|
|
22
|
+
if (!osPlatform || !osArch) {
|
|
23
|
+
console.error(`Unsupported platform: ${process.platform}/${process.arch}`);
|
|
24
|
+
process.exit(1);
|
|
58
25
|
}
|
|
59
26
|
|
|
60
|
-
|
|
27
|
+
const tag = `v${version}`;
|
|
28
|
+
const base = `https://github.com/${OWNER}/${PROJECT}/releases/download/${tag}`;
|
|
29
|
+
const archiveName = `${PROJECT}_${osPlatform}_${osArch}.tar.gz`;
|
|
30
|
+
const binName = process.platform === 'win32' ? `${PROJECT}.exe` : PROJECT;
|
|
31
|
+
const destPath = path.join(VENDOR_DIR, binName);
|
|
32
|
+
|
|
33
|
+
function download(url, dest) {
|
|
61
34
|
return new Promise((resolve, reject) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
res.on('end', () => resolve(data));
|
|
79
|
-
res.on('error', reject);
|
|
80
|
-
});
|
|
81
|
-
req.on('error', reject);
|
|
35
|
+
const file = fs.createWriteStream(dest);
|
|
36
|
+
function get(url) {
|
|
37
|
+
https.get(url, (res) => {
|
|
38
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
39
|
+
return get(res.headers.location);
|
|
40
|
+
}
|
|
41
|
+
if (res.statusCode !== 200) {
|
|
42
|
+
return reject(new Error(`HTTP ${res.statusCode} downloading ${url}`));
|
|
43
|
+
}
|
|
44
|
+
res.pipe(file);
|
|
45
|
+
file.on('finish', () => file.close(resolve));
|
|
46
|
+
file.on('error', reject);
|
|
47
|
+
}).on('error', reject);
|
|
48
|
+
}
|
|
49
|
+
get(url);
|
|
82
50
|
});
|
|
83
51
|
}
|
|
84
52
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
53
|
+
function fetch(url) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
function get(url) {
|
|
56
|
+
https.get(url, (res) => {
|
|
57
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
58
|
+
return get(res.headers.location);
|
|
59
|
+
}
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
62
|
+
}
|
|
63
|
+
const chunks = [];
|
|
64
|
+
res.on('data', c => chunks.push(c));
|
|
65
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
66
|
+
res.on('error', reject);
|
|
67
|
+
}).on('error', reject);
|
|
96
68
|
}
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
return checksums;
|
|
69
|
+
get(url);
|
|
70
|
+
});
|
|
100
71
|
}
|
|
101
72
|
|
|
102
73
|
function sha256File(filePath) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'checksums.txt',
|
|
111
|
-
`${PROJECT}_${version}_checksums.txt`,
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
let checksumFile = '';
|
|
115
|
-
let checksumName = '';
|
|
116
|
-
for (const candidate of checksumCandidates) {
|
|
117
|
-
try {
|
|
118
|
-
checksumFile = await downloadText(`${urlBase}/${candidate}`);
|
|
119
|
-
checksumName = candidate;
|
|
120
|
-
break;
|
|
121
|
-
} catch (err) {
|
|
122
|
-
if (!String(err.message).includes('download failed (404)')) {
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!checksumFile) {
|
|
129
|
-
throw new Error(`no checksum file found (tried: ${checksumCandidates.join(', ')})`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const checksums = parseChecksums(checksumFile);
|
|
133
|
-
const expected = checksums.get(archiveName);
|
|
134
|
-
if (!expected) {
|
|
135
|
-
throw new Error(`checksum for ${archiveName} not found in ${checksumName}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const actual = sha256File(archivePath);
|
|
139
|
-
if (actual !== expected) {
|
|
140
|
-
throw new Error(`checksum mismatch for ${archiveName}`);
|
|
141
|
-
}
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const hash = crypto.createHash('sha256');
|
|
76
|
+
fs.createReadStream(filePath)
|
|
77
|
+
.on('data', d => hash.update(d))
|
|
78
|
+
.on('end', () => resolve(hash.digest('hex')))
|
|
79
|
+
.on('error', reject);
|
|
80
|
+
});
|
|
142
81
|
}
|
|
143
82
|
|
|
144
|
-
function
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
83
|
+
function parseChecksum(checksumsTxt, filename) {
|
|
84
|
+
for (const line of checksumsTxt.split('\n')) {
|
|
85
|
+
const [hash, name] = line.trim().split(/\s+/);
|
|
86
|
+
if (name === filename) return hash;
|
|
148
87
|
}
|
|
88
|
+
return null;
|
|
149
89
|
}
|
|
150
90
|
|
|
151
|
-
function
|
|
91
|
+
async function main() {
|
|
92
|
+
console.log(`Installing ${PROJECT} ${version} for ${osPlatform}/${osArch}...`);
|
|
152
93
|
fs.mkdirSync(VENDOR_DIR, { recursive: true });
|
|
153
94
|
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (process.platform === 'win32') {
|
|
159
|
-
run('powershell', [
|
|
160
|
-
'-NoProfile',
|
|
161
|
-
'-Command',
|
|
162
|
-
`Expand-Archive -Path \"${archivePath}\" -DestinationPath \"${VENDOR_DIR}\" -Force`,
|
|
163
|
-
]);
|
|
164
|
-
} else {
|
|
165
|
-
run('unzip', ['-o', archivePath, '-d', VENDOR_DIR]);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
run('tar', ['-xzf', archivePath, '-C', VENDOR_DIR]);
|
|
95
|
+
const checksumsTxt = await fetch(`${base}/checksums.txt`);
|
|
96
|
+
const expectedHash = parseChecksum(checksumsTxt, archiveName);
|
|
97
|
+
if (!expectedHash) {
|
|
98
|
+
throw new Error(`No checksum found for ${archiveName} in checksums.txt`);
|
|
169
99
|
}
|
|
170
100
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (process.platform !== 'win32') {
|
|
176
|
-
fs.chmodSync(destBinary, 0o755);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
101
|
+
const tmpFile = path.join(os.tmpdir(), archiveName);
|
|
102
|
+
try {
|
|
103
|
+
await download(`${base}/${archiveName}`, tmpFile);
|
|
179
104
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const assetBase = `${PROJECT}_${osName}_${arch}`;
|
|
185
|
-
const ext = 'tar.gz';
|
|
186
|
-
const asset = `${assetBase}.${ext}`;
|
|
187
|
-
const urlBase = `https://github.com/${OWNER}/${REPO}/releases/download/${tag}`;
|
|
188
|
-
const url = `${urlBase}/${asset}`;
|
|
189
|
-
|
|
190
|
-
const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nibble-npm-'));
|
|
191
|
-
const archivePath = path.join(cacheDir, asset);
|
|
105
|
+
const actualHash = await sha256File(tmpFile);
|
|
106
|
+
if (actualHash !== expectedHash) {
|
|
107
|
+
throw new Error(`Checksum mismatch for ${archiveName}: expected ${expectedHash}, got ${actualHash}`);
|
|
108
|
+
}
|
|
192
109
|
|
|
193
|
-
|
|
194
|
-
console.log(`
|
|
195
|
-
await download(url, archivePath);
|
|
196
|
-
await verifyArchiveChecksum(urlBase, archivePath, asset, pkgVersion);
|
|
197
|
-
installFromArchive(archivePath, osName);
|
|
198
|
-
console.log('Installed nibble binary successfully');
|
|
110
|
+
await tar.extract({ file: tmpFile, cwd: VENDOR_DIR, filter: p => p === binName });
|
|
111
|
+
console.log(`Installed ${PROJECT} to ${destPath}`);
|
|
199
112
|
} finally {
|
|
200
|
-
|
|
201
|
-
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
202
|
-
} else {
|
|
203
|
-
fs.rmdirSync(cacheDir, { recursive: true });
|
|
204
|
-
}
|
|
113
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
205
114
|
}
|
|
206
115
|
}
|
|
207
116
|
|
|
208
117
|
main().catch((err) => {
|
|
209
|
-
console.error(
|
|
118
|
+
console.error(`${PROJECT} install failed: ${err.message}`);
|
|
210
119
|
process.exit(1);
|
|
211
120
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backendsystems/nibble",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Fast local network scanner with hardware identification and a terminal UI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nibble": "bin/nibble.js"
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "node bin/install.js"
|
|
10
10
|
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"tar": "^7.0.0"
|
|
13
|
+
},
|
|
11
14
|
"repository": {
|
|
12
15
|
"type": "git",
|
|
13
16
|
"url": "https://github.com/backendsystems/nibble"
|