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.
- package/lib/howcode.js +76 -7
- 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
|
|
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(),
|
|
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,
|
|
299
|
+
async function downloadFile(url, filePath, idleTimeoutMs = DOWNLOAD_IDLE_TIMEOUT_MS) {
|
|
255
300
|
const controller = new AbortController()
|
|
256
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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/
|
|
41
|
+
"releaseBaseUrl": "https://github.com/IgorWarzocha/howcode/releases/download/main"
|
|
42
42
|
}
|
|
43
43
|
}
|