@backendsystems/nibble 0.3.0 → 0.3.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 +88 -1
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const https = require('https');
|
|
7
|
+
const crypto = require('crypto');
|
|
7
8
|
const { spawnSync } = require('child_process');
|
|
8
9
|
|
|
9
10
|
const PROJECT = 'nibble';
|
|
@@ -56,6 +57,90 @@ function download(url, outFile) {
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
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;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const match = trimmed.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
|
|
94
|
+
if (!match) {
|
|
95
|
+
continue;
|
|
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;
|
|
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
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
59
144
|
function run(cmd, args) {
|
|
60
145
|
const result = spawnSync(cmd, args, { stdio: 'inherit' });
|
|
61
146
|
if (result.status !== 0) {
|
|
@@ -99,7 +184,8 @@ async function main() {
|
|
|
99
184
|
const assetBase = `${PROJECT}_${osName}_${arch}`;
|
|
100
185
|
const ext = 'tar.gz';
|
|
101
186
|
const asset = `${assetBase}.${ext}`;
|
|
102
|
-
const
|
|
187
|
+
const urlBase = `https://github.com/${OWNER}/${REPO}/releases/download/${tag}`;
|
|
188
|
+
const url = `${urlBase}/${asset}`;
|
|
103
189
|
|
|
104
190
|
const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nibble-npm-'));
|
|
105
191
|
const archivePath = path.join(cacheDir, asset);
|
|
@@ -107,6 +193,7 @@ async function main() {
|
|
|
107
193
|
try {
|
|
108
194
|
console.log(`Downloading ${asset} from ${url}`);
|
|
109
195
|
await download(url, archivePath);
|
|
196
|
+
await verifyArchiveChecksum(urlBase, archivePath, asset, pkgVersion);
|
|
110
197
|
installFromArchive(archivePath, osName);
|
|
111
198
|
console.log('Installed nibble binary successfully');
|
|
112
199
|
} finally {
|