@eik/cli 3.1.69 → 3.2.1
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/CHANGELOG.md +20 -0
- package/classes/integrity.js +11 -1
- package/classes/publish/package/publish.js +12 -1
- package/classes/publish/package/tasks/check-if-already-published.js +6 -1
- package/classes/publish/package/tasks/task.js +2 -0
- package/classes/publish/package/tasks/upload-files.js +2 -0
- package/classes/version.js +10 -1
- package/commands/integrity.js +7 -1
- package/commands/publish.js +7 -0
- package/commands/version.js +7 -0
- package/package.json +3 -3
- package/types/classes/integrity.d.ts +13 -1
- package/types/classes/publish/package/publish.d.ts +11 -1
- package/types/classes/publish/package/tasks/task.d.ts +2 -0
- package/types/classes/version.d.ts +13 -1
- package/types/utils/http/integrity.d.ts +11 -1
- package/types/utils/http/request.d.ts +4 -0
- package/types/utils/http/retry.d.ts +15 -0
- package/utils/http/integrity.js +19 -2
- package/utils/http/latest-version.js +15 -2
- package/utils/http/request.js +21 -2
- package/utils/http/retry.js +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [3.2.1](https://github.com/eik-lib/cli/compare/v3.2.0...v3.2.1) (2026-09-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update dependency yargs to v18.2.0 ([#766](https://github.com/eik-lib/cli/issues/766)) ([ff39b1c](https://github.com/eik-lib/cli/commit/ff39b1ca3269d9246ac08748c179bbf7afca684b))
|
|
7
|
+
|
|
8
|
+
# [3.2.0](https://github.com/eik-lib/cli/compare/v3.1.69...v3.2.0) (2026-08-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* add retry to latest-version HTTP utility ([45b3115](https://github.com/eik-lib/cli/commit/45b31152e6b620df0831e9ba6ef054085ba82f98))
|
|
14
|
+
* retry HTTP requests on transient 5xx responses ([f7fa656](https://github.com/eik-lib/cli/commit/f7fa656a7001bacd98b2baf21903b935bbb4b535))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* make retry count and delay configurable through API and CLI ([b909b81](https://github.com/eik-lib/cli/commit/b909b81f46f59597ad52a80df3bea815e53f8eb7))
|
|
20
|
+
|
|
1
21
|
## [3.1.69](https://github.com/eik-lib/cli/compare/v3.1.68...v3.1.69) (2026-08-24)
|
|
2
22
|
|
|
3
23
|
|
package/classes/integrity.js
CHANGED
|
@@ -3,6 +3,7 @@ import assert from "@eik/common/lib/schemas/assert.js";
|
|
|
3
3
|
import ValidationError from "@eik/common/lib/schemas/validation-error.js";
|
|
4
4
|
import typeSlug from "@eik/common/lib/helpers/type-slug.js";
|
|
5
5
|
import { joinUrlPathname } from "../utils/url.js";
|
|
6
|
+
import { fetchWithRetry } from "../utils/http/retry.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @typedef {object} IntegrityOptions
|
|
@@ -13,6 +14,8 @@ import { joinUrlPathname } from "../utils/url.js";
|
|
|
13
14
|
* @property {string} version
|
|
14
15
|
* @property {string} [cwd]
|
|
15
16
|
* @property {boolean} [debug]
|
|
17
|
+
* @property {number} [retries=2] - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
18
|
+
* @property {number} [retryDelay=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
export default class Integrity {
|
|
@@ -28,6 +31,8 @@ export default class Integrity {
|
|
|
28
31
|
type,
|
|
29
32
|
debug = false,
|
|
30
33
|
cwd = process.cwd(),
|
|
34
|
+
retries,
|
|
35
|
+
retryDelay,
|
|
31
36
|
}) {
|
|
32
37
|
this.log = abslog(logger);
|
|
33
38
|
this.server = server;
|
|
@@ -36,6 +41,8 @@ export default class Integrity {
|
|
|
36
41
|
this.debug = debug;
|
|
37
42
|
this.cwd = cwd;
|
|
38
43
|
this.type = type;
|
|
44
|
+
this.retries = retries;
|
|
45
|
+
this.retryDelay = retryDelay;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
async run() {
|
|
@@ -80,7 +87,10 @@ export default class Integrity {
|
|
|
80
87
|
);
|
|
81
88
|
this.log.debug(` ==> url: ${url}`);
|
|
82
89
|
|
|
83
|
-
const res = await fetch(url)
|
|
90
|
+
const res = await fetchWithRetry(() => fetch(url), {
|
|
91
|
+
maxRetries: this.retries !== undefined ? this.retries + 1 : undefined,
|
|
92
|
+
baseDelayMs: this.retryDelay,
|
|
93
|
+
});
|
|
84
94
|
|
|
85
95
|
if (res.ok) {
|
|
86
96
|
this.log.debug(` ==> ok: true`);
|
|
@@ -25,6 +25,8 @@ import Cleanup from "./tasks/cleanup.js";
|
|
|
25
25
|
* @property {string[]} [map]
|
|
26
26
|
* @property {string} [out="./.eik"]
|
|
27
27
|
* @property {Record<string, string>} files
|
|
28
|
+
* @property {number} [retries=2] - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
29
|
+
* @property {number} [retryDelay=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
28
30
|
*/
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -59,6 +61,8 @@ export default class Publish {
|
|
|
59
61
|
map = [],
|
|
60
62
|
out = "./.eik",
|
|
61
63
|
files = {},
|
|
64
|
+
retries,
|
|
65
|
+
retryDelay,
|
|
62
66
|
}) {
|
|
63
67
|
const config = new EikConfig(
|
|
64
68
|
{
|
|
@@ -106,8 +110,15 @@ export default class Publish {
|
|
|
106
110
|
logger: this.log,
|
|
107
111
|
path: this.path,
|
|
108
112
|
config,
|
|
113
|
+
retries,
|
|
114
|
+
retryDelay,
|
|
115
|
+
});
|
|
116
|
+
this.uploadFiles = new UploadFiles({
|
|
117
|
+
logger: this.log,
|
|
118
|
+
config,
|
|
119
|
+
retries,
|
|
120
|
+
retryDelay,
|
|
109
121
|
});
|
|
110
|
-
this.uploadFiles = new UploadFiles({ logger: this.log, config });
|
|
111
122
|
this.saveMetafile = new SaveMetafile({ logger: this.log, cwd, config });
|
|
112
123
|
this.cleanup = new Cleanup({
|
|
113
124
|
logger: this.log,
|
|
@@ -15,7 +15,12 @@ export default class CheckIfAlreadyPublished extends Task {
|
|
|
15
15
|
log.debug(" ==> Fetching package metadata from server");
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
if (
|
|
18
|
+
if (
|
|
19
|
+
await integrity(server, typeSlug(type), name, version, {
|
|
20
|
+
retries: this.retries,
|
|
21
|
+
retryDelay: this.retryDelay,
|
|
22
|
+
})
|
|
23
|
+
) {
|
|
19
24
|
throw new Error(
|
|
20
25
|
`${name} version ${version} already exists on the Eik server. Publishing is not necessary.`,
|
|
21
26
|
);
|
package/classes/version.js
CHANGED
|
@@ -23,6 +23,8 @@ import hashCompare from "../utils/hash/compare.js";
|
|
|
23
23
|
* @property {string} [out="./.eik"]
|
|
24
24
|
* @property {string | Record<string, string>} files
|
|
25
25
|
* @property {string} [configFile="eik.json"]
|
|
26
|
+
* @property {number} [retries=2] - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
27
|
+
* @property {number} [retryDelay=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
26
28
|
*/
|
|
27
29
|
|
|
28
30
|
export default class Version {
|
|
@@ -41,6 +43,8 @@ export default class Version {
|
|
|
41
43
|
out = "./.eik",
|
|
42
44
|
files,
|
|
43
45
|
configFile = "eik.json",
|
|
46
|
+
retries,
|
|
47
|
+
retryDelay,
|
|
44
48
|
}) {
|
|
45
49
|
const config = new EikConfig(
|
|
46
50
|
{
|
|
@@ -61,6 +65,8 @@ export default class Version {
|
|
|
61
65
|
this.configFile = configFile;
|
|
62
66
|
this.path = isAbsolute(config.out) ? config.out : join(cwd, config.out);
|
|
63
67
|
this.level = level;
|
|
68
|
+
this.retries = retries;
|
|
69
|
+
this.retryDelay = retryDelay;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
/**
|
|
@@ -99,7 +105,10 @@ export default class Version {
|
|
|
99
105
|
|
|
100
106
|
let integrityHash;
|
|
101
107
|
try {
|
|
102
|
-
integrityHash = await integrity(server, typeSlug(type), name, version
|
|
108
|
+
integrityHash = await integrity(server, typeSlug(type), name, version, {
|
|
109
|
+
retries: this.retries,
|
|
110
|
+
retryDelay: this.retryDelay,
|
|
111
|
+
});
|
|
103
112
|
} catch (err) {
|
|
104
113
|
throw new Error(
|
|
105
114
|
`Unable to fetch package metadata from server: ${/** @type {any} */ (err).message}`,
|
package/commands/integrity.js
CHANGED
|
@@ -17,6 +17,11 @@ export const builder = (yargs) => {
|
|
|
17
17
|
alias: "s",
|
|
18
18
|
describe: "Eik server address, if different from configuration file",
|
|
19
19
|
},
|
|
20
|
+
retries: {
|
|
21
|
+
describe:
|
|
22
|
+
"Number of retry attempts on transient server errors. Each retry waits longer than the last (500 ms, 1000 ms, …). Default: 2 retries (3 total attempts). Set to 0 to disable.",
|
|
23
|
+
type: "number",
|
|
24
|
+
},
|
|
20
25
|
})
|
|
21
26
|
.example("eik integrity")
|
|
22
27
|
.example("eik integrity --server https://assets.myserver.com");
|
|
@@ -25,7 +30,7 @@ export const builder = (yargs) => {
|
|
|
25
30
|
export const handler = commandHandler(
|
|
26
31
|
{ command, options: ["server"] },
|
|
27
32
|
async (argv, log, spinner) => {
|
|
28
|
-
const { name, version, server, out, type, cwd, debug } = argv;
|
|
33
|
+
const { name, version, server, out, type, cwd, debug, retries } = argv;
|
|
29
34
|
|
|
30
35
|
const integrity = await new Integrity({
|
|
31
36
|
logger: log,
|
|
@@ -35,6 +40,7 @@ export const handler = commandHandler(
|
|
|
35
40
|
debug,
|
|
36
41
|
cwd,
|
|
37
42
|
type,
|
|
43
|
+
retries,
|
|
38
44
|
}).run();
|
|
39
45
|
|
|
40
46
|
if (integrity) {
|
package/commands/publish.js
CHANGED
|
@@ -27,6 +27,11 @@ export const builder = (yargs) => {
|
|
|
27
27
|
type: "string",
|
|
28
28
|
alias: "t",
|
|
29
29
|
},
|
|
30
|
+
retries: {
|
|
31
|
+
describe:
|
|
32
|
+
"Number of retry attempts on transient server errors. Each retry waits longer than the last (500 ms, 1000 ms, …). Default: 2 retries (3 total attempts). Set to 0 to disable.",
|
|
33
|
+
type: "number",
|
|
34
|
+
},
|
|
30
35
|
})
|
|
31
36
|
.example("eik publish")
|
|
32
37
|
.example("eik publish --dry-run")
|
|
@@ -49,6 +54,7 @@ export const handler = commandHandler(
|
|
|
49
54
|
files,
|
|
50
55
|
type,
|
|
51
56
|
configFile,
|
|
57
|
+
retries,
|
|
52
58
|
} = argv;
|
|
53
59
|
|
|
54
60
|
if (type === "map") {
|
|
@@ -71,6 +77,7 @@ export const handler = commandHandler(
|
|
|
71
77
|
map,
|
|
72
78
|
out,
|
|
73
79
|
files,
|
|
80
|
+
retries,
|
|
74
81
|
};
|
|
75
82
|
|
|
76
83
|
const publish = await new PublishPackage(options).run();
|
package/commands/version.js
CHANGED
|
@@ -25,6 +25,11 @@ export const builder = (yargs) => {
|
|
|
25
25
|
describe: "Log details about the operation and skip upload",
|
|
26
26
|
type: "boolean",
|
|
27
27
|
},
|
|
28
|
+
retries: {
|
|
29
|
+
describe:
|
|
30
|
+
"Number of retry attempts on transient server errors. Each retry waits longer than the last (500 ms, 1000 ms, …). Default: 2 retries (3 total attempts). Set to 0 to disable.",
|
|
31
|
+
type: "number",
|
|
32
|
+
},
|
|
28
33
|
})
|
|
29
34
|
.example("eik version")
|
|
30
35
|
.example("eik version minor")
|
|
@@ -46,6 +51,7 @@ export const handler = commandHandler(
|
|
|
46
51
|
type,
|
|
47
52
|
files,
|
|
48
53
|
configFile,
|
|
54
|
+
retries,
|
|
49
55
|
} = argv;
|
|
50
56
|
|
|
51
57
|
const options = {
|
|
@@ -60,6 +66,7 @@ export const handler = commandHandler(
|
|
|
60
66
|
out,
|
|
61
67
|
files,
|
|
62
68
|
configFile,
|
|
69
|
+
retries,
|
|
63
70
|
};
|
|
64
71
|
|
|
65
72
|
const newVersion = await new VersionPackage(options).run();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eik/cli",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "CLI tool for publishing assets to an Eik server",
|
|
5
5
|
"main": "./classes/index.js",
|
|
6
6
|
"types": "./types/classes/index.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"semver": "7.8.5",
|
|
51
51
|
"tar": "7.5.22",
|
|
52
52
|
"tinyrainbow": "3.1.1",
|
|
53
|
-
"yargs": "18.
|
|
53
|
+
"yargs": "18.2.0",
|
|
54
54
|
"yargs-parser": "22.0.0"
|
|
55
55
|
},
|
|
56
56
|
"overrides": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@eik/prettier-config": "1.0.2",
|
|
64
64
|
"@eik/semantic-release-config": "1.0.19",
|
|
65
65
|
"@eik/service": "5.1.69",
|
|
66
|
-
"@eik/sink-memory": "2.0
|
|
66
|
+
"@eik/sink-memory": "2.4.0",
|
|
67
67
|
"@eik/typescript-config": "1.0.2",
|
|
68
68
|
"@types/node": "26.2.0",
|
|
69
69
|
"@types/semver": "7.8.0",
|
|
@@ -7,6 +7,14 @@ export type IntegrityOptions = {
|
|
|
7
7
|
version: string;
|
|
8
8
|
cwd?: string;
|
|
9
9
|
debug?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
12
|
+
*/
|
|
13
|
+
retries?: number;
|
|
14
|
+
/**
|
|
15
|
+
* - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
16
|
+
*/
|
|
17
|
+
retryDelay?: number;
|
|
10
18
|
};
|
|
11
19
|
/**
|
|
12
20
|
* @typedef {object} IntegrityOptions
|
|
@@ -17,6 +25,8 @@ export type IntegrityOptions = {
|
|
|
17
25
|
* @property {string} version
|
|
18
26
|
* @property {string} [cwd]
|
|
19
27
|
* @property {boolean} [debug]
|
|
28
|
+
* @property {number} [retries=2] - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
29
|
+
* @property {number} [retryDelay=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
20
30
|
*/
|
|
21
31
|
export default class Integrity {
|
|
22
32
|
log: abslog.ValidLogger;
|
|
@@ -26,10 +36,12 @@ export default class Integrity {
|
|
|
26
36
|
debug: boolean;
|
|
27
37
|
cwd: string;
|
|
28
38
|
type: "map" | "npm" | "package" | undefined;
|
|
39
|
+
retries: number | undefined;
|
|
40
|
+
retryDelay: number | undefined;
|
|
29
41
|
/**
|
|
30
42
|
*
|
|
31
43
|
* @param {IntegrityOptions} options
|
|
32
44
|
*/
|
|
33
|
-
constructor({ logger, name, version, server, type, debug, cwd, }: IntegrityOptions);
|
|
45
|
+
constructor({ logger, name, version, server, type, debug, cwd, retries, retryDelay, }: IntegrityOptions);
|
|
34
46
|
run(): Promise<any>;
|
|
35
47
|
}
|
|
@@ -21,6 +21,14 @@ export type PublishOptions = {
|
|
|
21
21
|
map?: string[];
|
|
22
22
|
out?: string;
|
|
23
23
|
files: Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
26
|
+
*/
|
|
27
|
+
retries?: number;
|
|
28
|
+
/**
|
|
29
|
+
* - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
30
|
+
*/
|
|
31
|
+
retryDelay?: number;
|
|
24
32
|
};
|
|
25
33
|
export type PublishResult = {
|
|
26
34
|
type: string;
|
|
@@ -52,6 +60,8 @@ export type PublishResult = {
|
|
|
52
60
|
* @property {string[]} [map]
|
|
53
61
|
* @property {string} [out="./.eik"]
|
|
54
62
|
* @property {Record<string, string>} files
|
|
63
|
+
* @property {number} [retries=2] - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
64
|
+
* @property {number} [retryDelay=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
55
65
|
*/
|
|
56
66
|
/**
|
|
57
67
|
* @typedef {object} PublishResult
|
|
@@ -86,7 +96,7 @@ export default class Publish {
|
|
|
86
96
|
/**
|
|
87
97
|
* @param {PublishOptions} options
|
|
88
98
|
*/
|
|
89
|
-
constructor({ logger, cwd, token, dryRun, server, type, name, version, map, out, files, }: PublishOptions);
|
|
99
|
+
constructor({ logger, cwd, token, dryRun, server, type, name, version, map, out, files, retries, retryDelay, }: PublishOptions);
|
|
90
100
|
/**
|
|
91
101
|
* @returns {Promise<PublishResult>}
|
|
92
102
|
*/
|
|
@@ -11,6 +11,14 @@ export type VersionOptions = {
|
|
|
11
11
|
out?: string;
|
|
12
12
|
files: string | Record<string, string>;
|
|
13
13
|
configFile?: string;
|
|
14
|
+
/**
|
|
15
|
+
* - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
16
|
+
*/
|
|
17
|
+
retries?: number;
|
|
18
|
+
/**
|
|
19
|
+
* - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
20
|
+
*/
|
|
21
|
+
retryDelay?: number;
|
|
14
22
|
};
|
|
15
23
|
/**
|
|
16
24
|
* @typedef {object} VersionOptions
|
|
@@ -25,6 +33,8 @@ export type VersionOptions = {
|
|
|
25
33
|
* @property {string} [out="./.eik"]
|
|
26
34
|
* @property {string | Record<string, string>} files
|
|
27
35
|
* @property {string} [configFile="eik.json"]
|
|
36
|
+
* @property {number} [retries=2] - Number of retry attempts on transient 5xx/network errors (0 to disable). Default 2 retries = 3 total attempts
|
|
37
|
+
* @property {number} [retryDelay=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
28
38
|
*/
|
|
29
39
|
export default class Version {
|
|
30
40
|
log: abslog.ValidLogger;
|
|
@@ -32,10 +42,12 @@ export default class Version {
|
|
|
32
42
|
configFile: string;
|
|
33
43
|
path: any;
|
|
34
44
|
level: import("semver").ReleaseType;
|
|
45
|
+
retries: number | undefined;
|
|
46
|
+
retryDelay: number | undefined;
|
|
35
47
|
/**
|
|
36
48
|
* @param {VersionOptions} options
|
|
37
49
|
*/
|
|
38
|
-
constructor({ logger, server, type, name, version, level, cwd, map, out, files, configFile, }: VersionOptions);
|
|
50
|
+
constructor({ logger, server, type, name, version, level, cwd, map, out, files, configFile, retries, retryDelay, }: VersionOptions);
|
|
39
51
|
/**
|
|
40
52
|
* Similar to `npm version`, but updates `eik.json`
|
|
41
53
|
* @returns {Promise<string | null>} The new version number, or null if the versioning failed
|
|
@@ -11,4 +11,14 @@ export default _default;
|
|
|
11
11
|
*
|
|
12
12
|
* @throws Error
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} server
|
|
16
|
+
* @param {string} type
|
|
17
|
+
* @param {string} name
|
|
18
|
+
* @param {string} version
|
|
19
|
+
* @param {{ retries?: number, retryDelay?: number }} [options]
|
|
20
|
+
*/
|
|
21
|
+
declare function _default(server: string, type: string, name: string, version: string, { retries, retryDelay }?: {
|
|
22
|
+
retries?: number;
|
|
23
|
+
retryDelay?: number;
|
|
24
|
+
}): Promise<any>;
|
|
@@ -6,6 +6,8 @@ export type RequestOptions = {
|
|
|
6
6
|
file?: string;
|
|
7
7
|
map?: string;
|
|
8
8
|
token?: string;
|
|
9
|
+
retries?: number;
|
|
10
|
+
retryDelay?: number;
|
|
9
11
|
};
|
|
10
12
|
/**
|
|
11
13
|
* @typedef {object} RequestOptions
|
|
@@ -16,6 +18,8 @@ export type RequestOptions = {
|
|
|
16
18
|
* @property {string} [file]
|
|
17
19
|
* @property {string} [map]
|
|
18
20
|
* @property {string} [token]
|
|
21
|
+
* @property {number} [retries]
|
|
22
|
+
* @property {number} [retryDelay]
|
|
19
23
|
*/
|
|
20
24
|
/**
|
|
21
25
|
* HTTP Utility for making requests against an Eik server
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a fetch call with retry logic for transient server errors (5xx).
|
|
3
|
+
* 404 responses are returned immediately without retry — they indicate the
|
|
4
|
+
* resource does not exist, not a transient failure.
|
|
5
|
+
*
|
|
6
|
+
* @param {() => Promise<Response>} fn - Function that performs the fetch call
|
|
7
|
+
* @param {object} [options]
|
|
8
|
+
* @param {number} [options.maxRetries=3] - Total number of attempts (default 3 = 1 initial + 2 retries). Pass 1 to make exactly one attempt with no retry.
|
|
9
|
+
* @param {number} [options.baseDelayMs=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
10
|
+
* @returns {Promise<Response>}
|
|
11
|
+
*/
|
|
12
|
+
export declare function fetchWithRetry(fn: () => Promise<Response>, { maxRetries, baseDelayMs }?: {
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
baseDelayMs?: number;
|
|
15
|
+
}): Promise<Response>;
|
package/utils/http/integrity.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { fetchWithRetry } from "./retry.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Fetches package integrity string by name and version from a given Eik asset server.
|
|
@@ -12,11 +13,27 @@ import { join } from "node:path";
|
|
|
12
13
|
*
|
|
13
14
|
* @throws Error
|
|
14
15
|
*/
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} server
|
|
18
|
+
* @param {string} type
|
|
19
|
+
* @param {string} name
|
|
20
|
+
* @param {string} version
|
|
21
|
+
* @param {{ retries?: number, retryDelay?: number }} [options]
|
|
22
|
+
*/
|
|
23
|
+
export default async (
|
|
24
|
+
server,
|
|
25
|
+
type,
|
|
26
|
+
name,
|
|
27
|
+
version,
|
|
28
|
+
{ retries, retryDelay } = {},
|
|
29
|
+
) => {
|
|
16
30
|
const url = new URL(join(type, name, version), server);
|
|
17
31
|
url.search = `?t=${Date.now()}`;
|
|
18
32
|
|
|
19
|
-
const res = await fetch(url)
|
|
33
|
+
const res = await fetchWithRetry(() => fetch(url), {
|
|
34
|
+
maxRetries: retries !== undefined ? retries + 1 : undefined,
|
|
35
|
+
baseDelayMs: retryDelay,
|
|
36
|
+
});
|
|
20
37
|
|
|
21
38
|
if (!res.ok) {
|
|
22
39
|
if (res.status === 404) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
|
+
import { fetchWithRetry } from "./retry.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Fetches the latest version from an Eik server of a package by name, optionally restricting the lookup to a specified semver major version
|
|
@@ -11,9 +12,21 @@ import { join } from "path";
|
|
|
11
12
|
*
|
|
12
13
|
* @throws Error
|
|
13
14
|
*/
|
|
14
|
-
export default async (
|
|
15
|
+
export default async (
|
|
16
|
+
server,
|
|
17
|
+
type,
|
|
18
|
+
name,
|
|
19
|
+
major,
|
|
20
|
+
/** @type {{ retries?: number, retryDelay?: number }} */ {
|
|
21
|
+
retries,
|
|
22
|
+
retryDelay,
|
|
23
|
+
} = {},
|
|
24
|
+
) => {
|
|
15
25
|
const url = new URL(`${join(type, name)}?t=${Date.now()}`, server);
|
|
16
|
-
const res = await fetch(url)
|
|
26
|
+
const res = await fetchWithRetry(() => fetch(url), {
|
|
27
|
+
maxRetries: retries !== undefined ? retries + 1 : undefined,
|
|
28
|
+
baseDelayMs: retryDelay,
|
|
29
|
+
});
|
|
17
30
|
if (!res.ok) {
|
|
18
31
|
if (res.status === 404) {
|
|
19
32
|
return null;
|
package/utils/http/request.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fetchWithRetry } from "./retry.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @typedef {object} RequestOptions
|
|
@@ -9,6 +10,8 @@ import { readFile } from "node:fs/promises";
|
|
|
9
10
|
* @property {string} [file]
|
|
10
11
|
* @property {string} [map]
|
|
11
12
|
* @property {string} [token]
|
|
13
|
+
* @property {number} [retries]
|
|
14
|
+
* @property {number} [retryDelay]
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -21,7 +24,17 @@ import { readFile } from "node:fs/promises";
|
|
|
21
24
|
* @throws Error
|
|
22
25
|
*/
|
|
23
26
|
async function request(options) {
|
|
24
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
method = "POST",
|
|
29
|
+
host,
|
|
30
|
+
pathname,
|
|
31
|
+
data,
|
|
32
|
+
file,
|
|
33
|
+
map,
|
|
34
|
+
token,
|
|
35
|
+
retries,
|
|
36
|
+
retryDelay,
|
|
37
|
+
} = options;
|
|
25
38
|
const body = new FormData();
|
|
26
39
|
const headers = new Headers();
|
|
27
40
|
|
|
@@ -49,7 +62,13 @@ async function request(options) {
|
|
|
49
62
|
const url = new URL(pathname, host);
|
|
50
63
|
url.search = `?t=${Date.now()}`;
|
|
51
64
|
|
|
52
|
-
const res = await
|
|
65
|
+
const res = await fetchWithRetry(
|
|
66
|
+
() => fetch(url, { method, body, headers }),
|
|
67
|
+
{
|
|
68
|
+
maxRetries: retries !== undefined ? retries + 1 : undefined,
|
|
69
|
+
baseDelayMs: retryDelay,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
53
72
|
|
|
54
73
|
if (!res.ok) {
|
|
55
74
|
const err = new Error(
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a fetch call with retry logic for transient server errors (5xx).
|
|
3
|
+
* 404 responses are returned immediately without retry — they indicate the
|
|
4
|
+
* resource does not exist, not a transient failure.
|
|
5
|
+
*
|
|
6
|
+
* @param {() => Promise<Response>} fn - Function that performs the fetch call
|
|
7
|
+
* @param {object} [options]
|
|
8
|
+
* @param {number} [options.maxRetries=3] - Total number of attempts (default 3 = 1 initial + 2 retries). Pass 1 to make exactly one attempt with no retry.
|
|
9
|
+
* @param {number} [options.baseDelayMs=500] - Base delay in ms between retries; doubles each attempt (500 ms, 1000 ms, …)
|
|
10
|
+
* @returns {Promise<Response>}
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchWithRetry(
|
|
13
|
+
fn,
|
|
14
|
+
{ maxRetries = 3, baseDelayMs = 500 } = {},
|
|
15
|
+
) {
|
|
16
|
+
let lastErr;
|
|
17
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
18
|
+
let res;
|
|
19
|
+
try {
|
|
20
|
+
res = await fn();
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// Network-level failure (ECONNRESET, ENOTFOUND, etc.)
|
|
23
|
+
lastErr = err;
|
|
24
|
+
if (attempt < maxRetries) {
|
|
25
|
+
await delay(baseDelayMs * attempt);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 5xx — transient server error, retry
|
|
32
|
+
if (res.status >= 500 && attempt < maxRetries) {
|
|
33
|
+
await delay(baseDelayMs * attempt);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return res;
|
|
38
|
+
}
|
|
39
|
+
throw lastErr;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @param {number} ms */
|
|
43
|
+
function delay(ms) {
|
|
44
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
45
|
+
}
|