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.
Files changed (2) hide show
  1. package/bin/tessl.js +122 -60
  2. 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/paths.ts
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 getXdgDataHome() {
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 getSymlinkPath() {
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(getXdgDataHome(), "tessl", "versions");
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 getBinaryPath(version, platform) {
90
- return join(getVersionsDirectory(), `tessl-${version}-${platform}`);
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 (existsSync("/etc/alpine-release"))
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: { ...process.env, TESSL_AUTO_UPDATE_INTERVAL_MINUTES: "0" }
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 symlinkPath = getSymlinkPath();
267
- const symlinkTarget = await getExistingBinaryPath(symlinkPath);
268
- const installerVersion = "0.75.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 symlinkMismatch = false;
271
- if (isTestOrDevBuild && symlinkTarget) {
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 (symlinkTarget !== getBinaryPath(installerVersion, platform2)) {
339
+ if (existingBinary !== getVersionedBinPath(installerVersion, platform2)) {
278
340
  console.log(`Test/dev build - switching to version: ${installerVersion}`);
279
- symlinkMismatch = true;
341
+ versionMismatch = true;
280
342
  }
281
343
  }
282
- if (symlinkTarget && !symlinkMismatch) {
283
- return symlinkPath;
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 binaryPath = getBinaryPath(version, platform);
292
- if (existsSync3(binaryPath)) {
293
- return binaryPath;
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(binaryPath);
368
+ const validation = validateBinary(versionedBinPath);
307
369
  if (!validation.success) {
308
370
  try {
309
- rmSync(binaryPath, { force: true });
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 shouldCreateSymlink = !isInteractive() || await promptConfirmation(`Install Tessl CLI to ${symlinkPath}?`);
314
- if (shouldCreateSymlink) {
315
- await createSymlinkAtomic(binaryPath, symlinkPath);
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 symlinkPath;
384
+ return tesslBinPath;
323
385
  }
324
- return binaryPath;
386
+ return versionedBinPath;
325
387
  }
326
388
  async function main() {
327
389
  const path = await prepareBinary();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tessl",
3
- "version": "0.75.0",
3
+ "version": "0.77.0",
4
4
  "description": "Tessl CLI",
5
5
  "author": "Tessl",
6
6
  "license": "SEE LICENSE.md",