howcode 0.1.64-dev.0 → 0.1.65

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/lib/howcode.js +76 -7
  2. package/package.json +2 -2
package/lib/howcode.js CHANGED
@@ -5,14 +5,15 @@ const os = require('node:os')
5
5
  const path = require('node:path')
6
6
  const { spawn } = require('node:child_process')
7
7
  const { pipeline } = require('node:stream/promises')
8
- const { Readable } = require('node:stream')
8
+ const { Readable, Transform } = require('node:stream')
9
9
  const tar = require('tar')
10
10
 
11
11
  const packageJson = require('../package.json')
12
12
 
13
13
  const APP_NAME = packageJson.howcode.appName
14
14
  const RELEASE_BASE_URL = process.env.HOWCODE_BASE_URL || packageJson.howcode.releaseBaseUrl
15
- const DOWNLOAD_TIMEOUT_MS = 5 * 60_000
15
+ const FETCH_METADATA_TIMEOUT_MS = 30_000
16
+ const DOWNLOAD_IDLE_TIMEOUT_MS = 60_000
16
17
 
17
18
  const TARGETS = {
18
19
  'darwin:arm64': {
@@ -236,9 +237,53 @@ async function ensureWindowsLaunchIntegration(target, paths) {
236
237
  }
237
238
  }
238
239
 
240
+ function formatBytes(bytes) {
241
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
242
+
243
+ const units = ['B', 'KB', 'MB', 'GB']
244
+ let value = bytes
245
+ let unitIndex = 0
246
+ while (value >= 1024 && unitIndex < units.length - 1) {
247
+ value /= 1024
248
+ unitIndex += 1
249
+ }
250
+
251
+ const precision = unitIndex === 0 || value >= 10 ? 0 : 1
252
+ return `${value.toFixed(precision)} ${units[unitIndex]}`
253
+ }
254
+
255
+ function createDownloadProgressStream(input) {
256
+ let downloadedBytes = 0
257
+ let lastLoggedAt = 0
258
+
259
+ return new Transform({
260
+ transform(chunk, _encoding, callback) {
261
+ downloadedBytes += chunk.length
262
+ input.onProgress(downloadedBytes)
263
+
264
+ const now = Date.now()
265
+ if (now - lastLoggedAt >= 1000) {
266
+ lastLoggedAt = now
267
+ const downloadedLabel = formatBytes(downloadedBytes)
268
+ if (input.totalBytes > 0) {
269
+ const percent = Math.min(100, (downloadedBytes / input.totalBytes) * 100)
270
+ const totalLabel = formatBytes(input.totalBytes)
271
+ process.stdout.write(
272
+ `\rDownloading ${APP_NAME}: ${downloadedLabel} / ${totalLabel} (${percent.toFixed(0)}%)`,
273
+ )
274
+ } else {
275
+ process.stdout.write(`\rDownloading ${APP_NAME}: ${downloadedLabel}`)
276
+ }
277
+ }
278
+
279
+ callback(null, chunk)
280
+ },
281
+ })
282
+ }
283
+
239
284
  async function fetchJson(url) {
240
285
  const controller = new AbortController()
241
- const timeout = setTimeout(() => controller.abort(), 5000)
286
+ const timeout = setTimeout(() => controller.abort(), FETCH_METADATA_TIMEOUT_MS)
242
287
 
243
288
  try {
244
289
  const response = await fetch(url, { signal: controller.signal })
@@ -251,9 +296,21 @@ async function fetchJson(url) {
251
296
  }
252
297
  }
253
298
 
254
- async function downloadFile(url, filePath, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
299
+ async function downloadFile(url, filePath, idleTimeoutMs = DOWNLOAD_IDLE_TIMEOUT_MS) {
255
300
  const controller = new AbortController()
256
- const timeout = setTimeout(() => controller.abort(), timeoutMs)
301
+ let timedOut = false
302
+ let idleTimeout = setTimeout(() => {
303
+ timedOut = true
304
+ controller.abort()
305
+ }, idleTimeoutMs)
306
+
307
+ const resetIdleTimeout = () => {
308
+ clearTimeout(idleTimeout)
309
+ idleTimeout = setTimeout(() => {
310
+ timedOut = true
311
+ controller.abort()
312
+ }, idleTimeoutMs)
313
+ }
257
314
 
258
315
  try {
259
316
  const response = await fetch(url, { signal: controller.signal })
@@ -261,10 +318,22 @@ async function downloadFile(url, filePath, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
261
318
  throw new Error(`HTTP ${response.status} while downloading ${url}`)
262
319
  }
263
320
 
321
+ const totalBytes = Number(response.headers.get('content-length')) || 0
322
+ resetIdleTimeout()
264
323
  await fsp.mkdir(path.dirname(filePath), { recursive: true })
265
- await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(filePath))
324
+ await pipeline(
325
+ Readable.fromWeb(response.body),
326
+ createDownloadProgressStream({ totalBytes, onProgress: resetIdleTimeout }),
327
+ fs.createWriteStream(filePath),
328
+ )
329
+ process.stdout.write('\n')
330
+ } catch (error) {
331
+ if (timedOut) {
332
+ throw new Error(`Download stalled for ${Math.round(idleTimeoutMs / 1000)} seconds: ${url}`)
333
+ }
334
+ throw error
266
335
  } finally {
267
- clearTimeout(timeout)
336
+ clearTimeout(idleTimeout)
268
337
  }
269
338
  }
270
339
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howcode",
3
- "version": "0.1.64-dev.0",
3
+ "version": "0.1.65",
4
4
  "description": "Desktop coding app for Pi with projects, terminal, git, and diff workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Igor Warzocha",
@@ -38,6 +38,6 @@
38
38
  ],
39
39
  "howcode": {
40
40
  "appName": "howcode",
41
- "releaseBaseUrl": "https://github.com/IgorWarzocha/howcode/releases/download/dev"
41
+ "releaseBaseUrl": "https://github.com/IgorWarzocha/howcode/releases/download/main"
42
42
  }
43
43
  }