@lde/distribution-downloader 0.6.8 → 0.6.9

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/README.md CHANGED
@@ -1,3 +1,13 @@
1
- # Distribution Download
1
+ # @lde/distribution-downloader
2
2
 
3
3
  Downloads Distribution files from a given URL and saves them to the local filesystem.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @lde/distribution-downloader
9
+ ```
10
+
11
+ ## Documentation
12
+
13
+ See the [full documentation](https://ldelements.org/reference/distribution-downloader).
@@ -9,6 +9,17 @@ export interface Logger {
9
9
  }
10
10
  export interface DownloadOptions {
11
11
  logger?: Logger;
12
+ /**
13
+ * Idle timeout in milliseconds: the download is aborted when no bytes have
14
+ * arrived for this long, whether while waiting for the response headers or
15
+ * midway through the body. A large file that keeps flowing is never cut off,
16
+ * however long it takes. Defaults to 300 000 ms (5 minutes).
17
+ */
18
+ timeout?: number;
19
+ /**
20
+ * Cancels the download when aborted; the partial file is removed.
21
+ */
22
+ signal?: AbortSignal;
12
23
  }
13
24
  export interface DownloadResult {
14
25
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAM5C,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC9C;AAaD,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CACN,YAAY,EAAE,YAAY,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAAC;CAC5B;AAED,qBAAa,sBAAuB,YAAW,UAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,SAAY;IAEhC,QAAQ,CACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,SAAyD,EAC/D,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC;YA0CZ,mBAAmB;CAyBlC"}
1
+ {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAO5C,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC9C;AAaD,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CACN,YAAY,EAAE,YAAY,EAC1B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAAC;CAC5B;AAED,qBAAa,sBAAuB,YAAW,UAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,SAAY;IAEhC,QAAQ,CACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,SAAyD,EAC/D,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC;YA0EZ,mBAAmB;CAyBlC"}
package/dist/download.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import filenamifyUrl from 'filenamify-url';
2
2
  import { dirname, join, resolve, sep } from 'node:path';
3
+ import { Transform } from 'node:stream';
3
4
  import { pipeline } from 'node:stream/promises';
4
5
  import { createWriteStream } from 'node:fs';
5
6
  import { access, mkdir, rm, stat } from 'node:fs/promises';
@@ -30,26 +31,45 @@ export class LastModifiedDownloader {
30
31
  logger.debug(`File ${filePath} is up to date, skipping download.`);
31
32
  return { path: filePath, headers: new Headers() };
32
33
  }
33
- const downloadResponse = await fetch(downloadUrl, {
34
- signal: AbortSignal.timeout(300_000),
35
- });
36
- if (!downloadResponse.ok || !downloadResponse.body) {
37
- throw new Error(`Failed to download ${downloadUrl}: ${downloadResponse.statusText}`);
38
- }
34
+ const idleTimeout = options?.timeout ?? 300_000;
35
+ const idleAbortController = new AbortController();
36
+ let idleTimer;
37
+ const restartIdleTimer = () => {
38
+ clearTimeout(idleTimer);
39
+ idleTimer = setTimeout(() => idleAbortController.abort(new Error(`No data received for ${idleTimeout} ms`)), idleTimeout);
40
+ };
41
+ const signal = options?.signal === undefined
42
+ ? idleAbortController.signal
43
+ : AbortSignal.any([options.signal, idleAbortController.signal]);
44
+ restartIdleTimer();
39
45
  try {
40
- await mkdir(dirname(filePath), { recursive: true });
41
- await pipeline(downloadResponse.body, createWriteStream(filePath));
42
- }
43
- catch (error) {
44
- await rm(filePath, { force: true });
45
- throw new Error(`Failed to save ${downloadUrl} to ${filePath}: ${error}`);
46
+ const downloadResponse = await fetch(downloadUrl, { signal });
47
+ if (!downloadResponse.ok || !downloadResponse.body) {
48
+ throw new Error(`Failed to download ${downloadUrl}: ${downloadResponse.statusText}`);
49
+ }
50
+ try {
51
+ await mkdir(dirname(filePath), { recursive: true });
52
+ await pipeline(downloadResponse.body, new Transform({
53
+ transform(chunk, _encoding, callback) {
54
+ restartIdleTimer();
55
+ callback(null, chunk);
56
+ },
57
+ }), createWriteStream(filePath));
58
+ }
59
+ catch (error) {
60
+ await rm(filePath, { force: true });
61
+ throw new Error(`Failed to save ${downloadUrl} to ${filePath}: ${error}`);
62
+ }
63
+ const stats = await stat(filePath);
64
+ if (stats.size <= 1) {
65
+ logger.debug(`Distribution download ${downloadUrl} is empty`);
66
+ throw new Error('Distribution download is empty');
67
+ }
68
+ return { path: filePath, headers: downloadResponse.headers };
46
69
  }
47
- const stats = await stat(filePath);
48
- if (stats.size <= 1) {
49
- logger.debug(`Distribution download ${downloadUrl} is empty`);
50
- throw new Error('Distribution download is empty');
70
+ finally {
71
+ clearTimeout(idleTimer);
51
72
  }
52
- return { path: filePath, headers: downloadResponse.headers };
53
73
  }
54
74
  async localFileIsUpToDate(filePath, distribution) {
55
75
  if (undefined === distribution.lastModified) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/distribution-downloader",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/ldelements/lde.git",
6
6
  "directory": "packages/distribution-downloader"