@backendsystems/nibble 0.3.1 → 0.4.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/bin/install.js +23 -185
- package/package.json +4 -1
package/bin/install.js
CHANGED
|
@@ -1,208 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const os = require('os');
|
|
5
3
|
const path = require('path');
|
|
6
|
-
const
|
|
7
|
-
const crypto = require('crypto');
|
|
8
|
-
const { spawnSync } = require('child_process');
|
|
4
|
+
const BinWrapper = require('bin-wrapper');
|
|
9
5
|
|
|
10
6
|
const PROJECT = 'nibble';
|
|
11
7
|
const OWNER = 'backendsystems';
|
|
12
|
-
const REPO = 'nibble';
|
|
13
8
|
const ROOT = path.resolve(__dirname, '..');
|
|
14
9
|
const VENDOR_DIR = path.join(ROOT, 'vendor');
|
|
15
10
|
const PACKAGE_JSON = require(path.join(ROOT, 'package.json'));
|
|
16
11
|
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
}
|
|
34
|
-
|
|
35
|
-
return { osName, arch };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function download(url, outFile) {
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const req = https.get(url, (res) => {
|
|
41
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
42
|
-
res.resume();
|
|
43
|
-
return resolve(download(res.headers.location, outFile));
|
|
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
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function downloadText(url) {
|
|
61
|
-
return new Promise((resolve, reject) => {
|
|
62
|
-
const req = https.get(url, (res) => {
|
|
63
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
64
|
-
res.resume();
|
|
65
|
-
return resolve(downloadText(res.headers.location));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (res.statusCode !== 200) {
|
|
69
|
-
res.resume();
|
|
70
|
-
return reject(new Error(`download failed (${res.statusCode}): ${url}`));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let data = '';
|
|
74
|
-
res.setEncoding('utf8');
|
|
75
|
-
res.on('data', (chunk) => {
|
|
76
|
-
data += chunk;
|
|
77
|
-
});
|
|
78
|
-
res.on('end', () => resolve(data));
|
|
79
|
-
res.on('error', reject);
|
|
80
|
-
});
|
|
81
|
-
req.on('error', reject);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function parseChecksums(text) {
|
|
86
|
-
const checksums = new Map();
|
|
87
|
-
for (const line of text.split(/\r?\n/)) {
|
|
88
|
-
const trimmed = line.trim();
|
|
89
|
-
if (!trimmed || trimmed.startsWith('#')) {
|
|
90
|
-
continue;
|
|
12
|
+
function buildWrapper() {
|
|
13
|
+
const tag = `v${PACKAGE_JSON.version}`;
|
|
14
|
+
const base = `https://github.com/${OWNER}/${PROJECT}/releases/download/${tag}`;
|
|
15
|
+
const binName = process.platform === 'win32' ? `${PROJECT}.exe` : PROJECT;
|
|
16
|
+
const supportedOs = ['linux', 'darwin', 'win32'];
|
|
17
|
+
const supportedArch = ['x64', 'arm64'];
|
|
18
|
+
|
|
19
|
+
const wrapper = new BinWrapper({ skipCheck: true });
|
|
20
|
+
for (const os of supportedOs) {
|
|
21
|
+
let osTarget = os;
|
|
22
|
+
if (os === 'win32') {
|
|
23
|
+
osTarget = 'windows';
|
|
91
24
|
}
|
|
92
25
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
checksums.set(match[2].trim(), match[1].toLowerCase());
|
|
98
|
-
}
|
|
99
|
-
return checksums;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function sha256File(filePath) {
|
|
103
|
-
const hash = crypto.createHash('sha256');
|
|
104
|
-
hash.update(fs.readFileSync(filePath));
|
|
105
|
-
return hash.digest('hex');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function verifyArchiveChecksum(urlBase, archivePath, archiveName, version) {
|
|
109
|
-
const checksumCandidates = [
|
|
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;
|
|
26
|
+
for (const arch of supportedArch) {
|
|
27
|
+
let archTarget = arch;
|
|
28
|
+
if (arch === 'x64') {
|
|
29
|
+
archTarget = 'amd64';
|
|
124
30
|
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!checksumFile) {
|
|
129
|
-
throw new Error(`no checksum file found (tried: ${checksumCandidates.join(', ')})`);
|
|
130
|
-
}
|
|
131
31
|
|
|
132
|
-
|
|
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
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function run(cmd, args) {
|
|
145
|
-
const result = spawnSync(cmd, args, { stdio: 'inherit' });
|
|
146
|
-
if (result.status !== 0) {
|
|
147
|
-
throw new Error(`command failed: ${cmd} ${args.join(' ')}`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function installFromArchive(archivePath, osName) {
|
|
152
|
-
fs.mkdirSync(VENDOR_DIR, { recursive: true });
|
|
153
|
-
|
|
154
|
-
const binName = osName === 'windows' ? `${PROJECT}.exe` : PROJECT;
|
|
155
|
-
const destBinary = path.join(VENDOR_DIR, binName);
|
|
156
|
-
|
|
157
|
-
if (archivePath.endsWith('.zip')) {
|
|
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]);
|
|
32
|
+
wrapper.src(`${base}/${PROJECT}_${osTarget}_${archTarget}.tar.gz`, os, arch);
|
|
166
33
|
}
|
|
167
|
-
} else {
|
|
168
|
-
run('tar', ['-xzf', archivePath, '-C', VENDOR_DIR]);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!fs.existsSync(destBinary)) {
|
|
172
|
-
throw new Error(`binary not found after extraction: ${destBinary}`);
|
|
173
34
|
}
|
|
174
35
|
|
|
175
|
-
|
|
176
|
-
fs.chmodSync(destBinary, 0o755);
|
|
177
|
-
}
|
|
36
|
+
return wrapper.dest(VENDOR_DIR).use(binName);
|
|
178
37
|
}
|
|
179
38
|
|
|
180
39
|
async function main() {
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
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);
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
console.log(`Downloading ${asset} from ${url}`);
|
|
195
|
-
await download(url, archivePath);
|
|
196
|
-
await verifyArchiveChecksum(urlBase, archivePath, asset, pkgVersion);
|
|
197
|
-
installFromArchive(archivePath, osName);
|
|
198
|
-
console.log('Installed nibble binary successfully');
|
|
199
|
-
} finally {
|
|
200
|
-
if (fs.rmSync) {
|
|
201
|
-
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
202
|
-
} else {
|
|
203
|
-
fs.rmdirSync(cacheDir, { recursive: true });
|
|
204
|
-
}
|
|
205
|
-
}
|
|
40
|
+
console.log(`Installing ${PROJECT} ${PACKAGE_JSON.version}...`);
|
|
41
|
+
const bin = buildWrapper();
|
|
42
|
+
await bin.run();
|
|
43
|
+
console.log('Installed nibble binary successfully');
|
|
206
44
|
}
|
|
207
45
|
|
|
208
46
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backendsystems/nibble",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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
|
+
"bin-wrapper": "^4.1.0"
|
|
13
|
+
},
|
|
11
14
|
"repository": {
|
|
12
15
|
"type": "git",
|
|
13
16
|
"url": "https://github.com/backendsystems/nibble"
|