tessl 0.75.0 → 0.77.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 +122 -60
- package/package.json +1 -1
package/bin/tessl.js
CHANGED
|
@@ -51,6 +51,7 @@ async function downloadTarball(url) {
|
|
|
51
51
|
// installer/src/extract.ts
|
|
52
52
|
import { spawn } from "node:child_process";
|
|
53
53
|
import { mkdir } from "node:fs/promises";
|
|
54
|
+
import { Readable } from "node:stream";
|
|
54
55
|
import { pipeline } from "node:stream/promises";
|
|
55
56
|
async function extractTarball(stream, destination) {
|
|
56
57
|
await mkdir(destination, { recursive: true });
|
|
@@ -70,34 +71,114 @@ async function extractTarball(stream, destination) {
|
|
|
70
71
|
});
|
|
71
72
|
tar.on("error", reject);
|
|
72
73
|
});
|
|
73
|
-
await pipeline(stream, tar.stdin);
|
|
74
|
+
await pipeline(Readable.fromWeb(stream), tar.stdin);
|
|
74
75
|
await tarExit;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
// installer/src/
|
|
78
|
+
// installer/src/path.ts
|
|
79
|
+
import { existsSync } from "node:fs";
|
|
80
|
+
import {
|
|
81
|
+
copyFile,
|
|
82
|
+
mkdir as mkdir2,
|
|
83
|
+
readdir,
|
|
84
|
+
readlink,
|
|
85
|
+
rename,
|
|
86
|
+
rm,
|
|
87
|
+
symlink,
|
|
88
|
+
unlink
|
|
89
|
+
} from "node:fs/promises";
|
|
78
90
|
import { homedir } from "node:os";
|
|
79
|
-
import { join } from "node:path";
|
|
80
|
-
function
|
|
91
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
92
|
+
function getAppDataDir() {
|
|
93
|
+
if (process.platform === "win32") {
|
|
94
|
+
return process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local");
|
|
95
|
+
}
|
|
81
96
|
return process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
82
97
|
}
|
|
83
|
-
function
|
|
98
|
+
function getTesslBinPath() {
|
|
99
|
+
if (process.platform === "win32") {
|
|
100
|
+
return join(getAppDataDir(), "tessl", "bin", "tessl.exe");
|
|
101
|
+
}
|
|
84
102
|
return join(homedir(), ".local", "bin", "tessl");
|
|
85
103
|
}
|
|
86
104
|
function getVersionsDirectory() {
|
|
87
|
-
return join(
|
|
105
|
+
return join(getAppDataDir(), "tessl", "versions");
|
|
106
|
+
}
|
|
107
|
+
function getVersionedBinPath(version, platform) {
|
|
108
|
+
const base = `tessl-${version}-${platform}`;
|
|
109
|
+
const name = platform.startsWith("win32") ? `${base}.exe` : base;
|
|
110
|
+
return join(getVersionsDirectory(), name);
|
|
111
|
+
}
|
|
112
|
+
async function getExistingBinaryPath(tesslBinPath) {
|
|
113
|
+
if (!existsSync(tesslBinPath)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (process.platform === "win32") {
|
|
117
|
+
return tesslBinPath;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const target = await readlink(tesslBinPath);
|
|
121
|
+
const resolvedPath = resolve(dirname(tesslBinPath), target);
|
|
122
|
+
if (!existsSync(resolvedPath)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return resolvedPath;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
88
129
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
130
|
+
async function updateBinaryAtomic(versionedBinPath, tesslBinPath) {
|
|
131
|
+
await mkdir2(dirname(tesslBinPath), { recursive: true });
|
|
132
|
+
const tempPath = `${tesslBinPath}.tmp`;
|
|
133
|
+
if (process.platform === "win32") {
|
|
134
|
+
const oldPath = `${tesslBinPath}.old.${Date.now()}`;
|
|
135
|
+
await copyFile(versionedBinPath, tempPath);
|
|
136
|
+
try {
|
|
137
|
+
await rename(tesslBinPath, oldPath);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
if (e.code !== "ENOENT") {
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await rename(tempPath, tesslBinPath);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
try {
|
|
147
|
+
await rename(oldPath, tesslBinPath);
|
|
148
|
+
} catch {}
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
await pruneOldBackups(dirname(tesslBinPath), tesslBinPath);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
await unlink(tempPath);
|
|
156
|
+
} catch {}
|
|
157
|
+
await symlink(versionedBinPath, tempPath);
|
|
158
|
+
await rename(tempPath, tesslBinPath);
|
|
159
|
+
}
|
|
160
|
+
async function pruneOldBackups(binDir, tesslBinPath) {
|
|
161
|
+
try {
|
|
162
|
+
const prefix = `${basename(tesslBinPath)}.old.`;
|
|
163
|
+
const entries = await readdir(binDir);
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
if (entry.startsWith(prefix)) {
|
|
166
|
+
try {
|
|
167
|
+
await rm(join(binDir, entry), { force: true });
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {}
|
|
91
172
|
}
|
|
92
173
|
|
|
93
174
|
// installer/src/platform.ts
|
|
94
175
|
import { execSync } from "node:child_process";
|
|
95
|
-
import { existsSync } from "node:fs";
|
|
176
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
96
177
|
function getLibcVariant() {
|
|
97
178
|
if (process.platform !== "linux")
|
|
98
179
|
return null;
|
|
99
180
|
try {
|
|
100
|
-
if (
|
|
181
|
+
if (existsSync2("/etc/alpine-release"))
|
|
101
182
|
return "musl";
|
|
102
183
|
} catch {}
|
|
103
184
|
try {
|
|
@@ -127,6 +208,12 @@ function getPlatformString() {
|
|
|
127
208
|
return variant === "musl" ? "linux-arm64-musl" : "linux-arm64";
|
|
128
209
|
}
|
|
129
210
|
}
|
|
211
|
+
if (platform === "win32") {
|
|
212
|
+
if (arch === "x64")
|
|
213
|
+
return "win32-x64";
|
|
214
|
+
if (arch === "arm64")
|
|
215
|
+
return "win32-arm64";
|
|
216
|
+
}
|
|
130
217
|
return null;
|
|
131
218
|
}
|
|
132
219
|
function getPlatformDescription() {
|
|
@@ -157,7 +244,7 @@ function spawnBinary(binaryPath, args) {
|
|
|
157
244
|
TESSL_MANAGED_BY_NPM: "1"
|
|
158
245
|
}
|
|
159
246
|
});
|
|
160
|
-
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
247
|
+
const signals = process.platform === "win32" ? ["SIGINT", "SIGTERM", "SIGBREAK"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
161
248
|
const handlers = new Map;
|
|
162
249
|
for (const signal of signals) {
|
|
163
250
|
const handler = () => {
|
|
@@ -182,35 +269,6 @@ function spawnBinary(binaryPath, args) {
|
|
|
182
269
|
});
|
|
183
270
|
}
|
|
184
271
|
|
|
185
|
-
// installer/src/symlink.ts
|
|
186
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
187
|
-
import { symlink, rename, unlink, mkdir as mkdir2, readlink } from "node:fs/promises";
|
|
188
|
-
import { dirname, resolve } from "node:path";
|
|
189
|
-
async function getExistingBinaryPath(symlinkPath) {
|
|
190
|
-
if (!existsSync2(symlinkPath)) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
const binaryPath = await readlink(symlinkPath);
|
|
195
|
-
const resolvedPath = resolve(dirname(symlinkPath), binaryPath);
|
|
196
|
-
if (!existsSync2(resolvedPath)) {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
return resolvedPath;
|
|
200
|
-
} catch {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
async function createSymlinkAtomic(target, linkPath) {
|
|
205
|
-
const tempPath = `${linkPath}.tmp`;
|
|
206
|
-
await mkdir2(dirname(linkPath), { recursive: true });
|
|
207
|
-
try {
|
|
208
|
-
await unlink(tempPath);
|
|
209
|
-
} catch {}
|
|
210
|
-
await symlink(target, tempPath);
|
|
211
|
-
await rename(tempPath, linkPath);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
272
|
// installer/src/validate.ts
|
|
215
273
|
import { spawnSync } from "node:child_process";
|
|
216
274
|
function validateBinary(binaryPath) {
|
|
@@ -218,7 +276,10 @@ function validateBinary(binaryPath) {
|
|
|
218
276
|
const result = spawnSync(binaryPath, ["--version"], {
|
|
219
277
|
stdio: "pipe",
|
|
220
278
|
timeout: 1e4,
|
|
221
|
-
env: {
|
|
279
|
+
env: {
|
|
280
|
+
...process.env,
|
|
281
|
+
TESSL_AUTO_UPDATE_INTERVAL_MINUTES: "0"
|
|
282
|
+
}
|
|
222
283
|
});
|
|
223
284
|
if (result.error) {
|
|
224
285
|
return { success: false, error: result.error.message };
|
|
@@ -260,27 +321,28 @@ function printUnsupportedPlatformMessage() {
|
|
|
260
321
|
console.error(`Tessl CLI binaries are available for:
|
|
261
322
|
- macOS (ARM64, x64)
|
|
262
323
|
- Linux (x64, ARM64)
|
|
324
|
+
- Windows (x64, ARM64)
|
|
263
325
|
`);
|
|
264
326
|
}
|
|
265
327
|
async function prepareBinary() {
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const installerVersion = "0.
|
|
328
|
+
const tesslBinPath = getTesslBinPath();
|
|
329
|
+
const existingBinary = await getExistingBinaryPath(tesslBinPath);
|
|
330
|
+
const installerVersion = "0.77.0";
|
|
269
331
|
const isTestOrDevBuild = installerVersion.includes("test") || installerVersion.includes("dev");
|
|
270
|
-
let
|
|
271
|
-
if (isTestOrDevBuild &&
|
|
332
|
+
let versionMismatch = false;
|
|
333
|
+
if (isTestOrDevBuild && existingBinary) {
|
|
272
334
|
const platform2 = getPlatformString();
|
|
273
335
|
if (!platform2) {
|
|
274
336
|
printUnsupportedPlatformMessage();
|
|
275
337
|
return process.exit(1);
|
|
276
338
|
}
|
|
277
|
-
if (
|
|
339
|
+
if (existingBinary !== getVersionedBinPath(installerVersion, platform2)) {
|
|
278
340
|
console.log(`Test/dev build - switching to version: ${installerVersion}`);
|
|
279
|
-
|
|
341
|
+
versionMismatch = true;
|
|
280
342
|
}
|
|
281
343
|
}
|
|
282
|
-
if (
|
|
283
|
-
return
|
|
344
|
+
if (existingBinary && !versionMismatch) {
|
|
345
|
+
return tesslBinPath;
|
|
284
346
|
}
|
|
285
347
|
const platform = getPlatformString();
|
|
286
348
|
if (!platform) {
|
|
@@ -288,9 +350,9 @@ async function prepareBinary() {
|
|
|
288
350
|
return process.exit(1);
|
|
289
351
|
}
|
|
290
352
|
const version = isTestOrDevBuild ? installerVersion : await fetchLatestVersion(getChannelFromVersion(installerVersion));
|
|
291
|
-
const
|
|
292
|
-
if (existsSync3(
|
|
293
|
-
return
|
|
353
|
+
const versionedBinPath = getVersionedBinPath(version, platform);
|
|
354
|
+
if (existsSync3(versionedBinPath)) {
|
|
355
|
+
return versionedBinPath;
|
|
294
356
|
}
|
|
295
357
|
console.log(`Downloading ${version} for ${platform}`);
|
|
296
358
|
const url = getBinaryUrl(version, platform);
|
|
@@ -303,25 +365,25 @@ async function prepareBinary() {
|
|
|
303
365
|
clearInterval(progressInterval);
|
|
304
366
|
console.log("");
|
|
305
367
|
}
|
|
306
|
-
const validation = validateBinary(
|
|
368
|
+
const validation = validateBinary(versionedBinPath);
|
|
307
369
|
if (!validation.success) {
|
|
308
370
|
try {
|
|
309
|
-
rmSync(
|
|
371
|
+
rmSync(versionedBinPath, { force: true });
|
|
310
372
|
} catch {}
|
|
311
373
|
throw new Error(`${validation.error}. Removed the corrupt file — please try again.`);
|
|
312
374
|
}
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
await
|
|
375
|
+
const shouldInstall = !isInteractive() || await promptConfirmation(`Install Tessl CLI to ${tesslBinPath}?`);
|
|
376
|
+
if (shouldInstall) {
|
|
377
|
+
await updateBinaryAtomic(versionedBinPath, tesslBinPath);
|
|
316
378
|
if (platform.includes("musl")) {
|
|
317
379
|
console.log(`
|
|
318
380
|
This system requires libgcc and libstdc++. Install these using your distribution's package manager.
|
|
319
381
|
On Alpine: apk add libgcc libstdc++
|
|
320
382
|
`);
|
|
321
383
|
}
|
|
322
|
-
return
|
|
384
|
+
return tesslBinPath;
|
|
323
385
|
}
|
|
324
|
-
return
|
|
386
|
+
return versionedBinPath;
|
|
325
387
|
}
|
|
326
388
|
async function main() {
|
|
327
389
|
const path = await prepareBinary();
|