@dhruv2mars/offdex 0.0.3 → 0.0.5

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.
@@ -5,6 +5,7 @@ import http from "node:http";
5
5
  import https from "node:https";
6
6
  import { homedir } from "node:os";
7
7
  import { join } from "node:path";
8
+ import { gunzipSync } from "node:zlib";
8
9
 
9
10
  const REPO = "Dhruv2mars/offdex";
10
11
  const SUPPORTED_TARGETS = new Map([
@@ -58,6 +59,10 @@ export function checksumsAssetNameFor(platform = process.platform, arch = proces
58
59
  return `checksums-${platform}-${arch}.txt`;
59
60
  }
60
61
 
62
+ export function compressedAssetNameFor(platform = process.platform, arch = process.arch) {
63
+ return `${assetNameFor(platform, arch)}.gz`;
64
+ }
65
+
61
66
  export function resolveInstallRoot(env = process.env, home = homedir()) {
62
67
  return env.OFFDEX_INSTALL_ROOT || join(home, ".offdex");
63
68
  }
@@ -136,6 +141,18 @@ export function parseChecksumForAsset(text, asset) {
136
141
  return null;
137
142
  }
138
143
 
144
+ export function shouldReportProgress({ receivedBytes, totalBytes, lastPercent }) {
145
+ if (!totalBytes || totalBytes <= 0) {
146
+ return false;
147
+ }
148
+
149
+ const percent = Math.floor((receivedBytes / totalBytes) * 100);
150
+ if (lastPercent < 0) {
151
+ return true;
152
+ }
153
+ return percent === 100 || percent >= lastPercent + 10;
154
+ }
155
+
139
156
  function requestProtocolFor(url) {
140
157
  return new URL(url).protocol;
141
158
  }
@@ -183,7 +200,7 @@ export function requestText(url, redirects = 0) {
183
200
  });
184
201
  }
185
202
 
186
- export function download(url, outputPath, redirects = 0) {
203
+ export function download(url, outputPath, redirects = 0, options = {}) {
187
204
  if (redirects > 5) {
188
205
  throw new Error("too_many_redirects");
189
206
  }
@@ -207,7 +224,7 @@ export function download(url, outputPath, redirects = 0) {
207
224
  response.headers.location
208
225
  ) {
209
226
  response.resume();
210
- download(response.headers.location, outputPath, redirects + 1).then(resolve, reject);
227
+ download(response.headers.location, outputPath, redirects + 1, options).then(resolve, reject);
211
228
  return;
212
229
  }
213
230
  if (response.statusCode !== 200) {
@@ -215,6 +232,12 @@ export function download(url, outputPath, redirects = 0) {
215
232
  reject(new Error(`http ${response.statusCode}`));
216
233
  return;
217
234
  }
235
+ const totalBytes = Number(response.headers["content-length"] || 0);
236
+ let receivedBytes = 0;
237
+ response.on("data", (chunk) => {
238
+ receivedBytes += chunk.length;
239
+ options.onProgress?.({ receivedBytes, totalBytes });
240
+ });
218
241
  const file = createWriteStream(partPath);
219
242
  file.on("error", async (error) => {
220
243
  await rm(partPath, { force: true });
@@ -238,6 +261,9 @@ export function download(url, outputPath, redirects = 0) {
238
261
  });
239
262
  }
240
263
  );
264
+ request.setTimeout(options.timeoutMs ?? 30_000, () => {
265
+ request.destroy(new Error("download_timeout"));
266
+ });
241
267
  request.on("error", async (error) => {
242
268
  await rm(partPath, { force: true });
243
269
  reject(error);
@@ -252,13 +278,15 @@ export async function installRuntime({
252
278
  arch = process.arch,
253
279
  home = homedir(),
254
280
  downloadFn = download,
255
- requestTextFn = requestText
281
+ requestTextFn = requestText,
282
+ onProgress
256
283
  }) {
257
284
  assertSupportedPlatform(platform, arch);
258
285
  const installRoot = resolveInstallRoot(env, home);
259
286
  const installBin = resolveInstalledBin(env, platform, home);
260
287
  const installMeta = resolveInstallMetaPath(env, home);
261
288
  const asset = assetNameFor(platform, arch);
289
+ const compressedAsset = compressedAssetNameFor(platform, arch);
262
290
  const checksumsAsset = checksumsAssetNameFor(platform, arch);
263
291
  const baseUrl = env.OFFDEX_RELEASE_BASE_URL
264
292
  || `https://github.com/${REPO}/releases/download/v${version}`;
@@ -271,21 +299,42 @@ export async function installRuntime({
271
299
  } catch {
272
300
  throw new Error(`failed_download:${checksumsAsset}`);
273
301
  }
274
- const expectedChecksum = parseChecksumForAsset(checksumsText, asset);
302
+ const expectedCompressedChecksum = parseChecksumForAsset(checksumsText, compressedAsset);
303
+ const expectedChecksum = expectedCompressedChecksum || parseChecksumForAsset(checksumsText, asset);
275
304
  if (!expectedChecksum) {
276
305
  throw new Error(`missing_checksum:${asset}`);
277
306
  }
278
307
 
279
308
  const tempPath = `${installBin}.download`;
280
309
  try {
281
- try {
282
- await downloadFn(`${baseUrl}/${asset}`, tempPath);
283
- } catch {
284
- throw new Error(`failed_download:${asset}`);
285
- }
286
- const actualChecksum = createHash("sha256").update(readFileSync(tempPath)).digest("hex");
287
- if (actualChecksum !== expectedChecksum) {
288
- throw new Error(`checksum_mismatch:${asset}`);
310
+ if (expectedCompressedChecksum) {
311
+ const compressedPath = `${tempPath}.gz`;
312
+ try {
313
+ await downloadFn(`${baseUrl}/${compressedAsset}`, compressedPath, 0, { onProgress });
314
+ } catch {
315
+ throw new Error(`failed_download:${compressedAsset}`);
316
+ }
317
+ try {
318
+ const actualCompressedChecksum = createHash("sha256")
319
+ .update(readFileSync(compressedPath))
320
+ .digest("hex");
321
+ if (actualCompressedChecksum !== expectedCompressedChecksum) {
322
+ throw new Error(`checksum_mismatch:${compressedAsset}`);
323
+ }
324
+ await writeFile(tempPath, gunzipSync(readFileSync(compressedPath)));
325
+ } finally {
326
+ await rm(compressedPath, { force: true });
327
+ }
328
+ } else {
329
+ try {
330
+ await downloadFn(`${baseUrl}/${asset}`, tempPath, 0, { onProgress });
331
+ } catch {
332
+ throw new Error(`failed_download:${asset}`);
333
+ }
334
+ const actualChecksum = createHash("sha256").update(readFileSync(tempPath)).digest("hex");
335
+ if (actualChecksum !== expectedChecksum) {
336
+ throw new Error(`checksum_mismatch:${asset}`);
337
+ }
289
338
  }
290
339
  if (platform !== "win32") {
291
340
  await chmod(tempPath, 0o755);
package/bin/install.js CHANGED
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import {
5
5
  installRuntime,
6
6
  resolvePackageVersion,
7
+ shouldReportProgress,
7
8
  shouldSkipPackageInstall,
8
9
  supportedPlatformList
9
10
  } from "./install-lib.js";
@@ -17,7 +18,21 @@ if (shouldSkipPackageInstall({ env: process.env, packageRoot })) {
17
18
  process.exit(0);
18
19
  }
19
20
 
20
- installRuntime({ version: packageVersion })
21
+ let lastPercent = -10;
22
+
23
+ installRuntime({
24
+ version: packageVersion,
25
+ onProgress({ receivedBytes, totalBytes }) {
26
+ if (!shouldReportProgress({ receivedBytes, totalBytes, lastPercent })) {
27
+ return;
28
+ }
29
+
30
+ lastPercent = Math.floor((receivedBytes / totalBytes) * 100);
31
+ console.error(
32
+ `offdex: downloading native runtime ${lastPercent}% (${Math.round(receivedBytes / 1024 / 1024)}MB/${Math.round(totalBytes / 1024 / 1024)}MB)`
33
+ );
34
+ },
35
+ })
21
36
  .then(({ installBin }) => {
22
37
  console.log(`offdex: installed ${installBin}`);
23
38
  })
package/bin/offdex.js CHANGED
@@ -13,11 +13,38 @@ import {
13
13
  } from "./offdex-lib.js";
14
14
 
15
15
  const args = process.argv.slice(2);
16
+ const HELP_TEXT = `Offdex CLI
17
+
18
+ Usage:
19
+ offdex bridge [options]
20
+ offdex help
21
+
22
+ Options:
23
+ --host <host> Bridge host. Default: 0.0.0.0
24
+ --port <port> Bridge port. Default: 42420
25
+ --mode <codex|demo> Bridge runtime mode. Default: codex
26
+ --control-plane-url <url> Managed remote control plane URL
27
+ -h, --help Show help
28
+
29
+ Environment fallbacks:
30
+ OFFDEX_BRIDGE_HOST
31
+ OFFDEX_BRIDGE_PORT
32
+ OFFDEX_BRIDGE_MODE
33
+ OFFDEX_CONTROL_PLANE_URL
34
+ `;
16
35
  const installedBin = resolveInstalledBin(process.env, process.platform);
17
36
  const packageVersion = readPackageVersion();
18
37
  const currentInstalledVersion = installedVersion(process.env);
19
38
  const workspaceBridgeCli = resolveWorkspaceBridgeCli();
20
39
 
40
+ if (
41
+ !existsSync(installedBin) &&
42
+ (args.length === 0 || args[0] === "help" || args[0] === "--help" || args[0] === "-h")
43
+ ) {
44
+ console.log(HELP_TEXT);
45
+ process.exit(0);
46
+ }
47
+
21
48
  if (workspaceBridgeCli && !existsSync(installedBin)) {
22
49
  const result = spawnSync("bun", [workspaceBridgeCli, ...args], {
23
50
  stdio: "inherit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhruv2mars/offdex",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Codex mobile bridge CLI for Offdex",
5
5
  "type": "module",
6
6
  "bin": {