tessl 0.92.0 → 0.94.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/tessl.js +91 -48
- package/package.json +1 -1
package/bin/tessl.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// installer/src/main.ts
|
|
4
|
-
import { existsSync as existsSync3
|
|
4
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
5
|
+
import { mkdir as mkdir3, rm as rm2 } from "node:fs/promises";
|
|
6
|
+
import { basename as basename2, join as join2 } from "node:path";
|
|
5
7
|
import { createInterface } from "node:readline";
|
|
6
8
|
import { Readable as Readable2 } from "node:stream";
|
|
7
9
|
|
|
@@ -148,6 +150,40 @@ import {
|
|
|
148
150
|
} from "node:fs/promises";
|
|
149
151
|
import { homedir } from "node:os";
|
|
150
152
|
import { basename, dirname, join, resolve } from "node:path";
|
|
153
|
+
|
|
154
|
+
// installer/src/validate.ts
|
|
155
|
+
import { spawnSync } from "node:child_process";
|
|
156
|
+
function validateBinary(binaryPath) {
|
|
157
|
+
try {
|
|
158
|
+
const result = spawnSync(binaryPath, ["--version"], {
|
|
159
|
+
stdio: "pipe",
|
|
160
|
+
timeout: 1e4,
|
|
161
|
+
env: {
|
|
162
|
+
...process.env,
|
|
163
|
+
TESSL_AUTO_UPDATE_INTERVAL_MINUTES: "0"
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
if (result.error) {
|
|
167
|
+
return { success: false, error: result.error.message };
|
|
168
|
+
}
|
|
169
|
+
if (result.status !== 0) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: `--version exited with code ${result.status}`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { success: true };
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: err instanceof Error ? err.message : String(err)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// installer/src/path.ts
|
|
185
|
+
var pathSeq = 0;
|
|
186
|
+
var nextPathSeq = () => pathSeq += 1;
|
|
151
187
|
function getAppDataDir() {
|
|
152
188
|
if (process.platform === "win32") {
|
|
153
189
|
return process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local");
|
|
@@ -163,11 +199,29 @@ function getTesslBinPath() {
|
|
|
163
199
|
function getVersionsDirectory() {
|
|
164
200
|
return join(getAppDataDir(), "tessl", "versions");
|
|
165
201
|
}
|
|
202
|
+
function getStagedVersionsDirectory() {
|
|
203
|
+
return join(getAppDataDir(), "tessl", "staged-versions");
|
|
204
|
+
}
|
|
205
|
+
function getStagingDirForRun() {
|
|
206
|
+
return join(getStagedVersionsDirectory(), `${Date.now()}-${process.pid}-${nextPathSeq()}`);
|
|
207
|
+
}
|
|
166
208
|
function getVersionedBinPath(version, platform) {
|
|
167
209
|
const base = `tessl-${version}-${platform}`;
|
|
168
210
|
const name = platform.startsWith("win32") ? `${base}.exe` : base;
|
|
169
211
|
return join(getVersionsDirectory(), name);
|
|
170
212
|
}
|
|
213
|
+
async function promoteStagedBinary({
|
|
214
|
+
stagedBinPath,
|
|
215
|
+
versionedBinPath,
|
|
216
|
+
versionsDir
|
|
217
|
+
}) {
|
|
218
|
+
const validation = validateBinary(stagedBinPath);
|
|
219
|
+
if (!validation.success) {
|
|
220
|
+
throw new Error(`${validation.error}. Please try again.`);
|
|
221
|
+
}
|
|
222
|
+
await mkdir2(versionsDir, { recursive: true });
|
|
223
|
+
await rename(stagedBinPath, versionedBinPath);
|
|
224
|
+
}
|
|
171
225
|
async function getExistingBinaryPath(tesslBinPath) {
|
|
172
226
|
if (!existsSync(tesslBinPath)) {
|
|
173
227
|
return null;
|
|
@@ -186,22 +240,28 @@ async function getExistingBinaryPath(tesslBinPath) {
|
|
|
186
240
|
return null;
|
|
187
241
|
}
|
|
188
242
|
}
|
|
189
|
-
async function updateBinaryAtomic(
|
|
243
|
+
async function updateBinaryAtomic({
|
|
244
|
+
versionedBinPath,
|
|
245
|
+
tesslBinPath
|
|
246
|
+
}) {
|
|
190
247
|
await mkdir2(dirname(tesslBinPath), { recursive: true });
|
|
191
|
-
const
|
|
248
|
+
const tag = `${Date.now()}-${process.pid}-${nextPathSeq()}`;
|
|
249
|
+
const tempPath = `${tesslBinPath}.tmp.${tag}`;
|
|
192
250
|
if (process.platform === "win32") {
|
|
193
|
-
const oldPath = `${tesslBinPath}.old.${
|
|
251
|
+
const oldPath = `${tesslBinPath}.old.${tag}`;
|
|
194
252
|
await copyFile(versionedBinPath, tempPath);
|
|
195
253
|
try {
|
|
196
254
|
await rename(tesslBinPath, oldPath);
|
|
197
255
|
} catch (e) {
|
|
198
256
|
if (e.code !== "ENOENT") {
|
|
257
|
+
await unlink(tempPath).catch(() => {});
|
|
199
258
|
throw e;
|
|
200
259
|
}
|
|
201
260
|
}
|
|
202
261
|
try {
|
|
203
262
|
await rename(tempPath, tesslBinPath);
|
|
204
263
|
} catch (e) {
|
|
264
|
+
await unlink(tempPath).catch(() => {});
|
|
205
265
|
try {
|
|
206
266
|
await rename(oldPath, tesslBinPath);
|
|
207
267
|
} catch {}
|
|
@@ -214,7 +274,12 @@ async function updateBinaryAtomic(versionedBinPath, tesslBinPath) {
|
|
|
214
274
|
await unlink(tempPath);
|
|
215
275
|
} catch {}
|
|
216
276
|
await symlink(versionedBinPath, tempPath);
|
|
217
|
-
|
|
277
|
+
try {
|
|
278
|
+
await rename(tempPath, tesslBinPath);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
await unlink(tempPath).catch(() => {});
|
|
281
|
+
throw e;
|
|
282
|
+
}
|
|
218
283
|
}
|
|
219
284
|
async function pruneOldBackups(binDir, tesslBinPath) {
|
|
220
285
|
try {
|
|
@@ -328,36 +393,6 @@ function spawnBinary(binaryPath, args) {
|
|
|
328
393
|
});
|
|
329
394
|
}
|
|
330
395
|
|
|
331
|
-
// installer/src/validate.ts
|
|
332
|
-
import { spawnSync } from "node:child_process";
|
|
333
|
-
function validateBinary(binaryPath) {
|
|
334
|
-
try {
|
|
335
|
-
const result = spawnSync(binaryPath, ["--version"], {
|
|
336
|
-
stdio: "pipe",
|
|
337
|
-
timeout: 1e4,
|
|
338
|
-
env: {
|
|
339
|
-
...process.env,
|
|
340
|
-
TESSL_AUTO_UPDATE_INTERVAL_MINUTES: "0"
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
if (result.error) {
|
|
344
|
-
return { success: false, error: result.error.message };
|
|
345
|
-
}
|
|
346
|
-
if (result.status !== 0) {
|
|
347
|
-
return {
|
|
348
|
-
success: false,
|
|
349
|
-
error: `--version exited with code ${result.status}`
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
return { success: true };
|
|
353
|
-
} catch (err) {
|
|
354
|
-
return {
|
|
355
|
-
success: false,
|
|
356
|
-
error: err instanceof Error ? err.message : String(err)
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
396
|
// installer/src/verify.ts
|
|
362
397
|
import { createHash, verify } from "node:crypto";
|
|
363
398
|
function parseManifest(manifest) {
|
|
@@ -399,7 +434,12 @@ function sha256Hex(data) {
|
|
|
399
434
|
function isInteractive() {
|
|
400
435
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
401
436
|
}
|
|
402
|
-
async function downloadVerifyAndExtract(
|
|
437
|
+
async function downloadVerifyAndExtract({
|
|
438
|
+
version,
|
|
439
|
+
platform,
|
|
440
|
+
tarballUrl,
|
|
441
|
+
destination
|
|
442
|
+
}) {
|
|
403
443
|
const fileName = getBinaryFileName(version, platform);
|
|
404
444
|
const [tarballBytes, manifestBytes, manifestSig] = await Promise.all([
|
|
405
445
|
downloadBytes(tarballUrl),
|
|
@@ -415,7 +455,7 @@ async function downloadVerifyAndExtract(version, platform, tarballUrl, versionsD
|
|
|
415
455
|
throw new Error(`Checksum mismatch for ${fileName} (expected ${expected}, got ${actual}) — refusing to install.`);
|
|
416
456
|
}
|
|
417
457
|
const verifiedStream = Readable2.toWeb(Readable2.from(tarballBytes));
|
|
418
|
-
await extractTarball(verifiedStream,
|
|
458
|
+
await extractTarball(verifiedStream, destination);
|
|
419
459
|
}
|
|
420
460
|
async function promptConfirmation(message) {
|
|
421
461
|
return new Promise((resolve2) => {
|
|
@@ -441,7 +481,7 @@ function printUnsupportedPlatformMessage() {
|
|
|
441
481
|
async function prepareBinary() {
|
|
442
482
|
const tesslBinPath = getTesslBinPath();
|
|
443
483
|
const existingBinary = await getExistingBinaryPath(tesslBinPath);
|
|
444
|
-
const installerVersion = "0.
|
|
484
|
+
const installerVersion = "0.94.0";
|
|
445
485
|
const isTestOrDevBuild = installerVersion.includes("test") || installerVersion.includes("dev");
|
|
446
486
|
let versionMismatch = false;
|
|
447
487
|
if (isTestOrDevBuild && existingBinary) {
|
|
@@ -471,28 +511,31 @@ async function prepareBinary() {
|
|
|
471
511
|
console.log(`Downloading ${version} for ${platform}`);
|
|
472
512
|
const url = getBinaryUrl(version, platform);
|
|
473
513
|
const versionsDir = getVersionsDirectory();
|
|
514
|
+
const stagingDir = getStagingDirForRun();
|
|
515
|
+
const stagedBinPath = join2(stagingDir, basename2(versionedBinPath));
|
|
474
516
|
const progressInterval = setInterval(() => process.stdout.write("."), 500).unref();
|
|
475
517
|
try {
|
|
518
|
+
await mkdir3(stagingDir, { recursive: true });
|
|
476
519
|
if (RELEASE_VERIFICATION_ENABLED) {
|
|
477
|
-
await downloadVerifyAndExtract(
|
|
520
|
+
await downloadVerifyAndExtract({
|
|
521
|
+
version,
|
|
522
|
+
platform,
|
|
523
|
+
tarballUrl: url,
|
|
524
|
+
destination: stagingDir
|
|
525
|
+
});
|
|
478
526
|
} else {
|
|
479
527
|
const tarballStream = await downloadTarball(url);
|
|
480
|
-
await extractTarball(tarballStream,
|
|
528
|
+
await extractTarball(tarballStream, stagingDir);
|
|
481
529
|
}
|
|
530
|
+
await promoteStagedBinary({ stagedBinPath, versionedBinPath, versionsDir });
|
|
482
531
|
} finally {
|
|
483
532
|
clearInterval(progressInterval);
|
|
484
533
|
console.log("");
|
|
485
|
-
|
|
486
|
-
const validation = validateBinary(versionedBinPath);
|
|
487
|
-
if (!validation.success) {
|
|
488
|
-
try {
|
|
489
|
-
rmSync(versionedBinPath, { force: true });
|
|
490
|
-
} catch {}
|
|
491
|
-
throw new Error(`${validation.error}. Removed the corrupt file — please try again.`);
|
|
534
|
+
await rm2(stagingDir, { recursive: true, force: true }).catch(() => {});
|
|
492
535
|
}
|
|
493
536
|
const shouldInstall = !isInteractive() || await promptConfirmation(`Install Tessl CLI to ${tesslBinPath}?`);
|
|
494
537
|
if (shouldInstall) {
|
|
495
|
-
await updateBinaryAtomic(versionedBinPath, tesslBinPath);
|
|
538
|
+
await updateBinaryAtomic({ versionedBinPath, tesslBinPath });
|
|
496
539
|
if (platform.includes("musl")) {
|
|
497
540
|
console.log(`
|
|
498
541
|
This system requires libgcc and libstdc++. Install these using your distribution's package manager.
|