@bifrostwallet/assets 1.3.0 → 1.3.2
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 +21 -9
- package/index.d.ts +9 -1
- package/index.js +1 -1
- package/package.json +1 -4
- package/tokens/eip155-56.json +10 -1
- package/tokens/eip155-97.json +1 -1
- package/verify-logo-hash.js +92 -23
- package/fetch-logos.js +0 -193
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ yarn add @bifrostwallet/assets --exact
|
|
|
22
22
|
|
|
23
23
|
`<version>` is the package version and the git tag. That tag is written into the package at build time. Returned `logoURL` values are absolute. A missing record is still `null`.
|
|
24
24
|
|
|
25
|
-
An exact version tag is immutable on jsDelivr. Cache headers last one year, and the file is stored permanently, so this pinned URL does not go stale ([Caching](https://www.jsdelivr.com/documentation#id-caching)). Self-host when you want a copy you control
|
|
25
|
+
An exact version tag is immutable on jsDelivr. Cache headers last one year, and the file is stored permanently, so this pinned URL does not go stale ([Caching](https://www.jsdelivr.com/documentation#id-caching)). Self-host when you want a copy you control. The steps are in [Self-hosting logos](#self-hosting-logos).
|
|
26
26
|
|
|
27
27
|
Don't load logos from `raw.githubusercontent.com`. GitHub's raw file host is not a CDN: it rate-limits requests per IP and returns HTTP 429 under load. jsDelivr is built for this traffic.
|
|
28
28
|
|
|
@@ -43,23 +43,35 @@ Pass `baseURL` when you host the files yourself. A trailing slash is optional. T
|
|
|
43
43
|
|
|
44
44
|
### Optional: check a downloaded logo
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
`verifyLogoHash(bytes, logoHash)` is true when the SHA-256 of the decoded file bytes equals `logoHash`: 64 lowercase hex characters, no `0x` prefix. `fetchLogo(entry)` downloads `logoURL` and runs that check in one step. It does not retry unless you pass `retries` (the delay before the first retry is `retryDelay`, 500 ms by default, and it doubles each time).
|
|
47
47
|
|
|
48
48
|
### Self-hosting logos
|
|
49
49
|
|
|
50
|
-
Logo files are not in the npm package.
|
|
50
|
+
Logo files are not in the npm package. Clone this repository and check out the same version as your installed `@bifrostwallet/assets`. Tags have no `v` prefix.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
```sh
|
|
53
|
+
git clone https://github.com/bifrostwallet/assets
|
|
54
|
+
cd assets
|
|
55
|
+
git checkout <version>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`<version>` is the installed package version, for example `1.2.2`.
|
|
59
|
+
|
|
60
|
+
Upload only the `logo.webp` files under `data/`. Keep those relative paths (`tokens/…`, `providers/…`, `contracts/…`, `dapps/…`) so `baseURL` can join them.
|
|
61
|
+
|
|
62
|
+
Google Cloud:
|
|
53
63
|
|
|
54
64
|
```sh
|
|
55
|
-
|
|
65
|
+
gcloud storage rsync data gs://<your-bucket> --recursive --exclude='^(?!.*logo\.webp$).*'
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
Amazon S3:
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
```sh
|
|
71
|
+
aws s3 sync data s3://<your-bucket> --exclude '*' --include '*logo.webp'
|
|
72
|
+
```
|
|
61
73
|
|
|
62
|
-
|
|
74
|
+
Point the package at the public URL of that directory:
|
|
63
75
|
|
|
64
76
|
```typescript
|
|
65
77
|
import { createAssets } from "@bifrostwallet/assets";
|
|
@@ -69,7 +81,7 @@ const assets = createAssets({
|
|
|
69
81
|
});
|
|
70
82
|
```
|
|
71
83
|
|
|
72
|
-
|
|
84
|
+
When you bump `@bifrostwallet/assets`, check out that version and upload again before you ship. The default jsDelivr URL changes with the version. A copy you host does not.
|
|
73
85
|
|
|
74
86
|
### Listed providers
|
|
75
87
|
|
package/index.d.ts
CHANGED
|
@@ -186,6 +186,14 @@ export interface FetchLogoOptions {
|
|
|
186
186
|
/** Defaults to global fetch. Tests pass a substitute. */
|
|
187
187
|
fetch?: typeof fetch;
|
|
188
188
|
signal?: AbortSignal;
|
|
189
|
+
/**
|
|
190
|
+
* Extra attempts after a network error or HTTP 403, 408, 429, or 5xx.
|
|
191
|
+
* Defaults to 0.
|
|
192
|
+
* A hash mismatch or a response over 256KB is never retried.
|
|
193
|
+
*/
|
|
194
|
+
retries?: number;
|
|
195
|
+
/** Milliseconds before the first retry. Doubles on each later retry. Defaults to 500. */
|
|
196
|
+
retryDelay?: number;
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
/**
|
|
@@ -201,7 +209,7 @@ export declare function fetchLogo(
|
|
|
201
209
|
/**
|
|
202
210
|
* True when SHA-256 of the decoded logo bytes equals expectedHash.
|
|
203
211
|
* expectedHash is 64 lowercase hex characters with no 0x prefix (catalog logoHash).
|
|
204
|
-
* For bytes you already have
|
|
212
|
+
* For bytes you already have.
|
|
205
213
|
* fetchLogo calls this after a download.
|
|
206
214
|
*/
|
|
207
215
|
export declare function verifyLogoHash(
|
package/index.js
CHANGED
|
@@ -50,7 +50,7 @@ const CONTRACT_CATEGORIES = ["dex","aggregator","bridge","lending","staking","yi
|
|
|
50
50
|
const CONTRACT_CATEGORY_LABELS = {"dex":"DEX","aggregator":"Aggregator","bridge":"Bridge","lending":"Lending","staking":"Staking","yield":"Yield","perpetuals":"Perpetuals","predictions":"Predictions","nft-marketplace":"NFT Marketplace","governance":"Governance","fassets":"FAssets","rewards":"Rewards","other":"Other"};
|
|
51
51
|
const DAPP_CATEGORIES = ["featured","dex","lending","yield","staking","bridges","marketplaces","social","games","other"];
|
|
52
52
|
const DAPP_CATEGORY_LABELS = {"featured":"Featured","dex":"DEX","lending":"Lending","yield":"Yield","staking":"Staking","bridges":"Bridges","marketplaces":"Marketplaces","social":"Social","games":"Games","other":"Other"};
|
|
53
|
-
const DEFAULT_LOGO_BASE_URL = "https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.3.
|
|
53
|
+
const DEFAULT_LOGO_BASE_URL = "https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.3.2/data/";
|
|
54
54
|
|
|
55
55
|
const EIP712_DOMAIN_SALT = "0xffc5336f20c2026b0f53d269def3cdffa3ca99b07e238af05f263a89517104f7";
|
|
56
56
|
const PROVIDER_LISTING_PRIMARY_TYPE = "ProviderListing";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bifrostwallet/assets",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Curated catalog of data providers, validators, tokens, contracts and dapps for Bifrost Wallet.",
|
|
5
5
|
"repository": "https://github.com/bifrostwallet/assets.git",
|
|
6
6
|
"author": "Bifrost Software Ltd",
|
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
"default": "./signatures.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
-
"bin": {
|
|
21
|
-
"fetch-logos": "./fetch-logos.js"
|
|
22
|
-
},
|
|
23
20
|
"sideEffects": false,
|
|
24
21
|
"dependencies": {}
|
|
25
22
|
}
|
package/tokens/eip155-56.json
CHANGED
|
@@ -1916,6 +1916,15 @@
|
|
|
1916
1916
|
"logoURL": "tokens/eip155-56/0xba9425ec55ee0e72216d18e0ad8bbba2553bfb60/logo.webp",
|
|
1917
1917
|
"logoHash": "935a9ac318bf75dfdf1f5a61a4162be707c624d6f43781ac91182f96180a64b1"
|
|
1918
1918
|
},
|
|
1919
|
+
{
|
|
1920
|
+
"name": "Wrapped BNB",
|
|
1921
|
+
"symbol": "WBNB",
|
|
1922
|
+
"decimals": 18,
|
|
1923
|
+
"address": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
|
|
1924
|
+
"coingeckoId": "wbnb",
|
|
1925
|
+
"logoURL": "tokens/eip155-56/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c/logo.webp",
|
|
1926
|
+
"logoHash": "8926a7bc9572fbc38319d019f3cd92f6eeaca99d2f7ca57358b5771856721a98"
|
|
1927
|
+
},
|
|
1919
1928
|
{
|
|
1920
1929
|
"name": "Alchemy Pay",
|
|
1921
1930
|
"symbol": "ACH",
|
|
@@ -2598,6 +2607,6 @@
|
|
|
2598
2607
|
"address": null,
|
|
2599
2608
|
"coingeckoId": "binancecoin",
|
|
2600
2609
|
"logoURL": "tokens/eip155-56/native/logo.webp",
|
|
2601
|
-
"logoHash": "
|
|
2610
|
+
"logoHash": "8926a7bc9572fbc38319d019f3cd92f6eeaca99d2f7ca57358b5771856721a98"
|
|
2602
2611
|
}
|
|
2603
2612
|
]
|
package/tokens/eip155-97.json
CHANGED
package/verify-logo-hash.js
CHANGED
|
@@ -13,6 +13,42 @@ function verifyLogoHash(bytes, expectedHash) {
|
|
|
13
13
|
return crypto.timingSafeEqual(digest, expected);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function delay(ms) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
setTimeout(resolve, ms);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readRetryCount(value) {
|
|
23
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
|
|
24
|
+
const count = Math.floor(value);
|
|
25
|
+
return count > 0 ? count : 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readRetryDelay(value) {
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return 500;
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isRetryableStatus(status) {
|
|
34
|
+
return status === 403 || status === 408 || status === 429 || (status >= 500 && status <= 599);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function callerAborted(options) {
|
|
38
|
+
return !!(options && options.signal && options.signal.aborted);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function backoff(options, attempt, status) {
|
|
42
|
+
const retries = readRetryCount(options && options.retries);
|
|
43
|
+
const retryDelay = readRetryDelay(options && options.retryDelay);
|
|
44
|
+
const wait = retryDelay * 2 ** (attempt - 1);
|
|
45
|
+
const onRetry = options && options.onRetry;
|
|
46
|
+
if (typeof onRetry === "function") {
|
|
47
|
+
await onRetry({ attempt, retries, status, delay: wait });
|
|
48
|
+
}
|
|
49
|
+
if (wait > 0) await delay(wait);
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
async function fetchLogo(entry, options) {
|
|
17
53
|
if (!entry || typeof entry.logoURL !== "string" || entry.logoURL.trim() === "") {
|
|
18
54
|
throw new Error("logoURL is required");
|
|
@@ -21,29 +57,62 @@ async function fetchLogo(entry, options) {
|
|
|
21
57
|
throw new Error("logoHash must be a lowercase SHA-256 hex digest");
|
|
22
58
|
}
|
|
23
59
|
const fetchImpl = (options && options.fetch) || fetch;
|
|
24
|
-
const
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
const retries = readRetryCount(options && options.retries);
|
|
61
|
+
let attempt = 0;
|
|
62
|
+
for (;;) {
|
|
63
|
+
const signal = options && options.signal ? options.signal : AbortSignal.timeout(30000);
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetchImpl(entry.logoURL, {
|
|
67
|
+
redirect: "follow",
|
|
68
|
+
headers: { accept: "application/octet-stream" },
|
|
69
|
+
signal,
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (!callerAborted(options) && attempt < retries) {
|
|
73
|
+
attempt += 1;
|
|
74
|
+
await backoff(options, attempt, "network");
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const message = error && error.message ? error.message : String(error);
|
|
78
|
+
throw new Error("Could not fetch logo: " + message);
|
|
79
|
+
}
|
|
80
|
+
if (!response || !response.ok) {
|
|
81
|
+
const status = response && typeof response.status === "number" ? response.status : 0;
|
|
82
|
+
if (response && typeof response.arrayBuffer === "function") {
|
|
83
|
+
try {
|
|
84
|
+
await response.arrayBuffer();
|
|
85
|
+
} catch (drainError) {
|
|
86
|
+
void drainError;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (isRetryableStatus(status) && attempt < retries && !callerAborted(options)) {
|
|
90
|
+
attempt += 1;
|
|
91
|
+
await backoff(options, attempt, status);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const label = status ? status : "request failed";
|
|
95
|
+
throw new Error("Could not fetch logo: HTTP " + label);
|
|
96
|
+
}
|
|
97
|
+
let bytes;
|
|
98
|
+
try {
|
|
99
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!callerAborted(options) && attempt < retries) {
|
|
102
|
+
attempt += 1;
|
|
103
|
+
await backoff(options, attempt, "network");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const message = error && error.message ? error.message : String(error);
|
|
107
|
+
throw new Error("Could not fetch logo: " + message);
|
|
108
|
+
}
|
|
109
|
+
if (bytes.byteLength > 256 * 1024) {
|
|
110
|
+
throw new Error("Could not fetch logo: response exceeds 256KB");
|
|
111
|
+
}
|
|
112
|
+
if (!verifyLogoHash(bytes, entry.logoHash)) {
|
|
113
|
+
throw new Error("logo hash mismatch");
|
|
114
|
+
}
|
|
115
|
+
return bytes;
|
|
46
116
|
}
|
|
47
|
-
return bytes;
|
|
48
117
|
}
|
|
49
118
|
module.exports = { verifyLogoHash, fetchLogo };
|
package/fetch-logos.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
// @bifrostwallet/assets fetch-logos — generated file, do not edit.
|
|
4
|
-
// Downloads logo files for this package version. Each catalog entry's logoHash
|
|
5
|
-
// is the trust anchor. Bytes are the decoded response body (content-encoding removed).
|
|
6
|
-
// Accept is application/octet-stream so the CDN is not asked to convert the image.
|
|
7
|
-
|
|
8
|
-
const fs = require("node:fs");
|
|
9
|
-
const path = require("node:path");
|
|
10
|
-
const { fetchLogo, verifyLogoHash } = require("./verify-logo-hash.js");
|
|
11
|
-
|
|
12
|
-
const DEFAULT_LOGO_BASE_URL = "https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.3.0/data/";
|
|
13
|
-
const USAGE = [
|
|
14
|
-
"Usage: fetch-logos <dir>",
|
|
15
|
-
"",
|
|
16
|
-
"Download this package version's logos into <dir>.",
|
|
17
|
-
"Paths stay relative to data/ (providers/, tokens/, contracts/, dapps/).",
|
|
18
|
-
"Each file is checked against the logoHash on its catalog entry.",
|
|
19
|
-
"A missing hash or a hash mismatch does not write that file, and the command exits non-zero.",
|
|
20
|
-
"Do not upload the directory when the command fails.",
|
|
21
|
-
"",
|
|
22
|
-
"Upload <dir> to any static host. createAssets baseURL is optional.",
|
|
23
|
-
"Omit it to use jsDelivr at this release tag:",
|
|
24
|
-
"https://cdn.jsdelivr.net/gh/bifrostwallet/assets@1.3.0/data/",
|
|
25
|
-
"Or set baseURL to the public URL where you uploaded that directory.",
|
|
26
|
-
"Example: https://assets.example.com/",
|
|
27
|
-
"Run fetch-logos again when you bump the package version.",
|
|
28
|
-
].join("\n");
|
|
29
|
-
|
|
30
|
-
function readJson(filePath) {
|
|
31
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function walkJsonArrays(dir, visit) {
|
|
35
|
-
if (!fs.existsSync(dir)) return;
|
|
36
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
-
const fullPath = path.join(dir, entry.name);
|
|
38
|
-
if (entry.isDirectory()) {
|
|
39
|
-
walkJsonArrays(fullPath, visit);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (!entry.name.endsWith(".json")) continue;
|
|
43
|
-
if (entry.name === "package.json" || entry.name === "signatures.json") {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const parsed = readJson(fullPath);
|
|
47
|
-
if (!Array.isArray(parsed)) continue;
|
|
48
|
-
for (const item of parsed) {
|
|
49
|
-
if (item && typeof item === "object" && typeof item.logoURL === "string") {
|
|
50
|
-
visit(item, path.relative(dir, fullPath));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isSafeLogoPath(value) {
|
|
57
|
-
if (!value || value.startsWith("/") || value.includes("..") || value.includes("\\")) return false;
|
|
58
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return false;
|
|
59
|
-
return /^(providers|tokens|contracts|dapps)\/[A-Za-z0-9._@/-]+$/.test(value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function loadAnchors(packageRoot) {
|
|
63
|
-
const entries = new Map();
|
|
64
|
-
const failures = [];
|
|
65
|
-
walkJsonArrays(packageRoot, (item) => {
|
|
66
|
-
const logoPath = item.logoURL;
|
|
67
|
-
const logoHash = item.logoHash;
|
|
68
|
-
if (!isSafeLogoPath(logoPath)) {
|
|
69
|
-
failures.push(String(logoPath) + ": invalid logo path");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (typeof logoHash !== "string" || !/^[0-9a-f]{64}$/.test(logoHash)) {
|
|
73
|
-
failures.push(logoPath + ": missing logoHash");
|
|
74
|
-
entries.set(logoPath, null);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const existing = entries.get(logoPath);
|
|
78
|
-
if (existing !== undefined && existing !== logoHash) {
|
|
79
|
-
failures.push(logoPath + ": logoHash conflict");
|
|
80
|
-
entries.set(logoPath, null);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (existing === undefined) entries.set(logoPath, logoHash);
|
|
84
|
-
});
|
|
85
|
-
return { entries, failures };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function destinationFor(destDir, logoPath) {
|
|
89
|
-
const root = path.resolve(destDir);
|
|
90
|
-
const target = path.resolve(root, logoPath);
|
|
91
|
-
const relative = path.relative(root, target);
|
|
92
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
93
|
-
throw new Error("logo path escapes destination");
|
|
94
|
-
}
|
|
95
|
-
return target;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function mapPool(items, limit, fn) {
|
|
99
|
-
const queue = items.slice();
|
|
100
|
-
const workers = [];
|
|
101
|
-
const count = Math.min(limit, queue.length);
|
|
102
|
-
for (let i = 0; i < count; i += 1) {
|
|
103
|
-
workers.push(
|
|
104
|
-
(async () => {
|
|
105
|
-
while (queue.length > 0) {
|
|
106
|
-
const item = queue.shift();
|
|
107
|
-
await fn(item);
|
|
108
|
-
}
|
|
109
|
-
})(),
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
await Promise.all(workers);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function fetchLogos(options) {
|
|
116
|
-
if (!options || typeof options.destDir !== "string" || options.destDir.trim() === "") {
|
|
117
|
-
throw new Error("destDir is required");
|
|
118
|
-
}
|
|
119
|
-
const packageRoot = options.packageRoot || __dirname;
|
|
120
|
-
const supplied = typeof options.baseURL === "string" ? options.baseURL.trim() : "";
|
|
121
|
-
const baseURL = (supplied || DEFAULT_LOGO_BASE_URL).replace(/\/?$/, "/");
|
|
122
|
-
const fetchImpl = options.fetch || fetch;
|
|
123
|
-
const loaded = loadAnchors(packageRoot);
|
|
124
|
-
const failures = loaded.failures.slice();
|
|
125
|
-
const written = [];
|
|
126
|
-
const jobs = [];
|
|
127
|
-
for (const [logoPath, logoHash] of loaded.entries) {
|
|
128
|
-
if (typeof logoHash !== "string") continue;
|
|
129
|
-
jobs.push({ logoPath, logoHash });
|
|
130
|
-
}
|
|
131
|
-
await mapPool(jobs, 8, async (job) => {
|
|
132
|
-
try {
|
|
133
|
-
const bytes = await fetchLogo(
|
|
134
|
-
{ logoURL: baseURL + job.logoPath, logoHash: job.logoHash },
|
|
135
|
-
{ fetch: fetchImpl },
|
|
136
|
-
);
|
|
137
|
-
const target = destinationFor(options.destDir, job.logoPath);
|
|
138
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
139
|
-
const partial = target + ".partial";
|
|
140
|
-
fs.writeFileSync(partial, bytes);
|
|
141
|
-
fs.renameSync(partial, target);
|
|
142
|
-
written.push(job.logoPath);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
const message = error && error.message ? error.message : String(error);
|
|
145
|
-
failures.push(job.logoPath + ": " + message);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
return { written, failures };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function runCli(argv, deps) {
|
|
152
|
-
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
153
|
-
const extra = deps || {};
|
|
154
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
155
|
-
console.info(USAGE);
|
|
156
|
-
return 0;
|
|
157
|
-
}
|
|
158
|
-
if (args[0] === "fetch-logos") args.shift();
|
|
159
|
-
if (args.length !== 1) {
|
|
160
|
-
console.error(USAGE);
|
|
161
|
-
return 1;
|
|
162
|
-
}
|
|
163
|
-
const result = await fetchLogos({
|
|
164
|
-
destDir: args[0],
|
|
165
|
-
packageRoot: extra.packageRoot,
|
|
166
|
-
baseURL: extra.baseURL,
|
|
167
|
-
fetch: extra.fetch,
|
|
168
|
-
});
|
|
169
|
-
for (const failure of result.failures) console.error(failure);
|
|
170
|
-
if (result.failures.length > 0) {
|
|
171
|
-
console.error(
|
|
172
|
-
"fetch-logos failed for " +
|
|
173
|
-
result.failures.length +
|
|
174
|
-
" logo(s). Those files were not written. Do not upload this directory.",
|
|
175
|
-
);
|
|
176
|
-
return 1;
|
|
177
|
-
}
|
|
178
|
-
console.info("Wrote " + result.written.length + " logo(s) to " + args[0]);
|
|
179
|
-
return 0;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (require.main === module) {
|
|
183
|
-
runCli(process.argv.slice(2))
|
|
184
|
-
.then((code) => {
|
|
185
|
-
process.exitCode = code;
|
|
186
|
-
})
|
|
187
|
-
.catch((error) => {
|
|
188
|
-
console.error(error && error.message ? error.message : error);
|
|
189
|
-
process.exitCode = 1;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
module.exports = { fetchLogos, fetchLogo, runCli, verifyLogoHash };
|