@uxf/icons-generator 11.128.0 → 12.1.0

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.
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._resolveRegistryTarget = _resolveRegistryTarget;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
8
+ function withTrailingSlash(url) {
9
+ return url.endsWith("/") ? url : `${url}/`;
10
+ }
11
+ /** npm identifies credentials by URL with the protocol stripped ("nerf dart"). */
12
+ function nerfDart(url) {
13
+ return withTrailingSlash(url).replace(/^https?:/, "");
14
+ }
15
+ function collectConfigFiles(fileName) {
16
+ const files = [];
17
+ const { root } = (0, path_1.parse)(process.cwd());
18
+ let dir = process.cwd();
19
+ for (;;) {
20
+ const candidate = (0, path_1.join)(dir, fileName);
21
+ if ((0, fs_1.existsSync)(candidate)) {
22
+ files.push(candidate);
23
+ }
24
+ if (dir === root) {
25
+ break;
26
+ }
27
+ dir = (0, path_1.dirname)(dir);
28
+ }
29
+ const home = (0, path_1.join)((0, os_1.homedir)(), fileName);
30
+ if ((0, fs_1.existsSync)(home) && !files.includes(home)) {
31
+ files.push(home);
32
+ }
33
+ // Nearest file first — the first hit for any key wins.
34
+ return files;
35
+ }
36
+ function interpolateEnv(value) {
37
+ return value.replace(/\$\{([^}]+)\}/g, (_, name) => { var _a; return (_a = process.env[name]) !== null && _a !== void 0 ? _a : ""; });
38
+ }
39
+ function unquote(value) {
40
+ const trimmed = value.trim();
41
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 1) ||
42
+ (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length > 1)) {
43
+ return trimmed.slice(1, -1);
44
+ }
45
+ return trimmed;
46
+ }
47
+ function parseNpmrc(content) {
48
+ const entries = {};
49
+ for (const rawLine of content.split("\n")) {
50
+ const line = rawLine.trim();
51
+ if (!line || line.startsWith("#") || line.startsWith(";") || line.startsWith("[")) {
52
+ continue;
53
+ }
54
+ const eq = line.indexOf("=");
55
+ if (eq === -1) {
56
+ continue;
57
+ }
58
+ const key = line.slice(0, eq).trim();
59
+ if (key in entries) {
60
+ continue;
61
+ }
62
+ entries[key] = interpolateEnv(unquote(line.slice(eq + 1)));
63
+ }
64
+ return entries;
65
+ }
66
+ let _npmrc;
67
+ function npmrc() {
68
+ if (_npmrc) {
69
+ return _npmrc;
70
+ }
71
+ const merged = {};
72
+ for (const file of collectConfigFiles(".npmrc")) {
73
+ for (const [key, value] of Object.entries(parseNpmrc((0, fs_1.readFileSync)(file, "utf8")))) {
74
+ if (!(key in merged)) {
75
+ merged[key] = value;
76
+ }
77
+ }
78
+ }
79
+ _npmrc = merged;
80
+ return merged;
81
+ }
82
+ /**
83
+ * Split `key: value` at the separating colon, skipping colons inside quotes.
84
+ *
85
+ * `npmRegistries` is keyed by URL and Yarn writes those keys quoted, so a naive
86
+ * `indexOf(":")` would cut `"https://npm.uxf.dev":` after `"https` and lose both
87
+ * the registry and the credentials nested under it.
88
+ */
89
+ function splitKeyValue(line) {
90
+ let quote = null;
91
+ for (let index = 0; index < line.length; index += 1) {
92
+ const character = line.charAt(index);
93
+ if (quote !== null) {
94
+ if (character === quote) {
95
+ quote = null;
96
+ }
97
+ continue;
98
+ }
99
+ if (character === '"' || character === "'") {
100
+ quote = character;
101
+ continue;
102
+ }
103
+ if (character === ":") {
104
+ return { key: line.slice(0, index), value: line.slice(index + 1).trim() };
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ /** Drop a trailing ` # comment`, which YAML allows after a scalar. */
110
+ function stripInlineComment(value) {
111
+ if (value.startsWith('"') || value.startsWith("'")) {
112
+ const quote = value.charAt(0);
113
+ const closing = value.indexOf(quote, 1);
114
+ return closing === -1 ? value : value.slice(0, closing + 1);
115
+ }
116
+ const comment = value.indexOf(" #");
117
+ return comment === -1 ? value : value.slice(0, comment).trimEnd();
118
+ }
119
+ /**
120
+ * Indentation-only YAML reader covering the shape `.yarnrc.yml` actually uses:
121
+ * scalars, nested maps and quoted keys. Enough for `npmRegistryServer`,
122
+ * `npmScopes.*` and `npmRegistries.*`, and it keeps this package dependency-free.
123
+ */
124
+ function parseSimpleYaml(content) {
125
+ var _a, _b, _c, _d;
126
+ const root = {};
127
+ const stack = [{ indent: -1, node: root }];
128
+ for (const rawLine of content.split("\n")) {
129
+ if (!rawLine.trim() || rawLine.trim().startsWith("#") || rawLine.trim().startsWith("-")) {
130
+ continue;
131
+ }
132
+ const indent = rawLine.length - rawLine.trimStart().length;
133
+ const pair = splitKeyValue(rawLine.trim());
134
+ if (!pair) {
135
+ continue;
136
+ }
137
+ const key = unquote(pair.key);
138
+ const value = stripInlineComment(pair.value);
139
+ while (stack.length > 1 && indent <= ((_b = (_a = stack.at(-1)) === null || _a === void 0 ? void 0 : _a.indent) !== null && _b !== void 0 ? _b : -1)) {
140
+ stack.pop();
141
+ }
142
+ const parent = (_d = (_c = stack.at(-1)) === null || _c === void 0 ? void 0 : _c.node) !== null && _d !== void 0 ? _d : root;
143
+ if (value === "" || value.startsWith("#")) {
144
+ const child = {};
145
+ parent[key] = child;
146
+ stack.push({ indent, node: child });
147
+ }
148
+ else {
149
+ parent[key] = interpolateEnv(unquote(value));
150
+ }
151
+ }
152
+ return root;
153
+ }
154
+ let _yarnrc;
155
+ function yarnrcs() {
156
+ if (_yarnrc) {
157
+ return _yarnrc;
158
+ }
159
+ _yarnrc = collectConfigFiles(".yarnrc.yml").map((file) => parseSimpleYaml((0, fs_1.readFileSync)(file, "utf8")));
160
+ return _yarnrc;
161
+ }
162
+ function asNode(value) {
163
+ return typeof value === "object" && value !== null ? value : undefined;
164
+ }
165
+ function asString(value) {
166
+ return typeof value === "string" && value !== "" ? value : undefined;
167
+ }
168
+ /* --------------------------------------------------------------- resolve */
169
+ function authorizationFromParts(token, basic) {
170
+ if (token) {
171
+ return `Bearer ${token}`;
172
+ }
173
+ if (basic) {
174
+ // Yarn's `npmAuthIdent` is `user:password`; npm's `_auth` is already base64.
175
+ const encoded = basic.includes(":") ? Buffer.from(basic, "utf8").toString("base64") : basic;
176
+ return `Basic ${encoded}`;
177
+ }
178
+ return undefined;
179
+ }
180
+ function npmrcAuthorization(registry) {
181
+ const entries = npmrc();
182
+ // npm walks up the path of the nerf-darted registry URL looking for creds.
183
+ let dart = nerfDart(registry);
184
+ for (;;) {
185
+ const token = entries[`${dart}:_authToken`];
186
+ const auth = entries[`${dart}:_auth`];
187
+ const username = entries[`${dart}:username`];
188
+ const password = entries[`${dart}:_password`];
189
+ if (token || auth) {
190
+ return authorizationFromParts(token, auth);
191
+ }
192
+ if (username && password) {
193
+ const decoded = Buffer.from(password, "base64").toString("utf8");
194
+ return authorizationFromParts(undefined, `${username}:${decoded}`);
195
+ }
196
+ const trimmed = dart.replace(/[^/]+\/$/, "");
197
+ if (trimmed === dart || trimmed === "//") {
198
+ return undefined;
199
+ }
200
+ dart = trimmed;
201
+ }
202
+ }
203
+ function yarnrcRegistryNode(config, registry) {
204
+ var _a;
205
+ const registries = asNode(config.npmRegistries);
206
+ if (!registries) {
207
+ return undefined;
208
+ }
209
+ // Yarn matches `npmRegistries` keys with or without the trailing slash.
210
+ const withSlash = withTrailingSlash(registry);
211
+ const withoutSlash = withSlash.slice(0, -1);
212
+ return (_a = asNode(registries[withSlash])) !== null && _a !== void 0 ? _a : asNode(registries[withoutSlash]);
213
+ }
214
+ function yarnAuthorization(scope, registry) {
215
+ var _a;
216
+ for (const config of yarnrcs()) {
217
+ const scopeNode = asNode((_a = asNode(config.npmScopes)) === null || _a === void 0 ? void 0 : _a[scope]);
218
+ const registryNode = yarnrcRegistryNode(config, registry);
219
+ // The top-level credential belongs to the top-level registry and to
220
+ // nothing else. Yarn scopes it the same way, and using it as a blanket
221
+ // fallback would hand our Verdaccio token to whatever host a scope
222
+ // happens to point at — Font Awesome's own registry, for instance.
223
+ const rootRegistry = asString(config.npmRegistryServer);
224
+ const rootNode = rootRegistry !== undefined && withTrailingSlash(rootRegistry) === withTrailingSlash(registry)
225
+ ? config
226
+ : undefined;
227
+ for (const node of [scopeNode, registryNode, rootNode]) {
228
+ if (!node) {
229
+ continue;
230
+ }
231
+ const authorization = authorizationFromParts(asString(node.npmAuthToken), asString(node.npmAuthIdent));
232
+ if (authorization) {
233
+ return authorization;
234
+ }
235
+ }
236
+ }
237
+ return undefined;
238
+ }
239
+ function resolveRegistry(scope) {
240
+ var _a, _b, _c;
241
+ const fromNpmrc = (_a = npmrc()[`@${scope}:registry`]) !== null && _a !== void 0 ? _a : undefined;
242
+ if (fromNpmrc) {
243
+ return withTrailingSlash(fromNpmrc);
244
+ }
245
+ for (const config of yarnrcs()) {
246
+ const scoped = asString((_c = asNode((_b = asNode(config.npmScopes)) === null || _b === void 0 ? void 0 : _b[scope])) === null || _c === void 0 ? void 0 : _c.npmRegistryServer);
247
+ if (scoped) {
248
+ return withTrailingSlash(scoped);
249
+ }
250
+ }
251
+ const npmDefault = npmrc().registry;
252
+ if (npmDefault) {
253
+ return withTrailingSlash(npmDefault);
254
+ }
255
+ for (const config of yarnrcs()) {
256
+ const fallback = asString(config.npmRegistryServer);
257
+ if (fallback) {
258
+ return withTrailingSlash(fallback);
259
+ }
260
+ }
261
+ return DEFAULT_REGISTRY;
262
+ }
263
+ /**
264
+ * Work out which registry serves a scoped package and how to authenticate
265
+ * against it, reading the same `.npmrc` / `.yarnrc.yml` files the package
266
+ * manager would. Consumer projects are on Yarn while this monorepo is on npm,
267
+ * so both have to be understood.
268
+ */
269
+ function _resolveRegistryTarget(packageName) {
270
+ var _a, _b;
271
+ const scope = packageName.startsWith("@") ? ((_a = packageName.slice(1).split("/").at(0)) !== null && _a !== void 0 ? _a : "") : "";
272
+ const registry = resolveRegistry(scope);
273
+ return {
274
+ authorization: (_b = npmrcAuthorization(registry)) !== null && _b !== void 0 ? _b : yarnAuthorization(scope, registry),
275
+ registry,
276
+ };
277
+ }
@@ -0,0 +1,6 @@
1
+ export interface ParsedFaSvg {
2
+ width: number;
3
+ height: number;
4
+ pathData: string | string[];
5
+ }
6
+ export declare function _parseFaSvg(svgContent: string): ParsedFaSvg;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._parseFaSvg = _parseFaSvg;
4
+ const VIEW_BOX_RE = /viewBox="0\s+0\s+(\d+)\s+(\d+)"/;
5
+ const PATH_D_RE = /<path\b[^>]*\sd="([^"]*)"/g;
6
+ function _parseFaSvg(svgContent) {
7
+ const viewBox = svgContent.match(VIEW_BOX_RE);
8
+ if (!viewBox) {
9
+ throw new Error("Could not find a viewBox starting at the origin in SVG content.");
10
+ }
11
+ const paths = [...svgContent.matchAll(PATH_D_RE)].map((match) => { var _a; return (_a = match.at(1)) !== null && _a !== void 0 ? _a : ""; });
12
+ if (paths.length === 0) {
13
+ throw new Error("No <path d=...> elements found in SVG content.");
14
+ }
15
+ const single = paths.at(0);
16
+ return {
17
+ width: Number(viewBox.at(1)),
18
+ height: Number(viewBox.at(2)),
19
+ pathData: paths.length === 1 && single !== undefined ? single : paths,
20
+ };
21
+ }
@@ -0,0 +1,18 @@
1
+ type Prefetch = () => Promise<void>;
2
+ /**
3
+ * Providers register here as they are loaded, which only happens when a
4
+ * project's `icons.config.js` actually requires one.
5
+ *
6
+ * The indirection keeps the CLI from importing any provider itself: a config
7
+ * built purely from inline SVGs must not pay for — or be broken by — a
8
+ * provider's module-level setup, such as the `faPro` adapter refusing to run
9
+ * while the retired `@fortawesome/fontawesome-pro` monolith is installed.
10
+ */
11
+ export declare function _registerPrefetch(prefetch: Prefetch): void;
12
+ /**
13
+ * Let every loaded provider resolve its icons up front. Providers expose a
14
+ * synchronous per-icon API, so anything needing I/O has to be fetched here,
15
+ * between loading the config and generating the sprite.
16
+ */
17
+ export declare function _runPrefetch(): Promise<void>;
18
+ export {};
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._registerPrefetch = _registerPrefetch;
4
+ exports._runPrefetch = _runPrefetch;
5
+ const prefetchers = new Set();
6
+ /**
7
+ * Providers register here as they are loaded, which only happens when a
8
+ * project's `icons.config.js` actually requires one.
9
+ *
10
+ * The indirection keeps the CLI from importing any provider itself: a config
11
+ * built purely from inline SVGs must not pay for — or be broken by — a
12
+ * provider's module-level setup, such as the `faPro` adapter refusing to run
13
+ * while the retired `@fortawesome/fontawesome-pro` monolith is installed.
14
+ */
15
+ function _registerPrefetch(prefetch) {
16
+ prefetchers.add(prefetch);
17
+ }
18
+ /**
19
+ * Let every loaded provider resolve its icons up front. Providers expose a
20
+ * synchronous per-icon API, so anything needing I/O has to be fetched here,
21
+ * between loading the config and generating the sprite.
22
+ */
23
+ async function _runPrefetch() {
24
+ await Promise.all([...prefetchers].map((prefetch) => prefetch()));
25
+ }
@@ -0,0 +1,16 @@
1
+ export interface TarEntry {
2
+ content: Uint8Array<ArrayBuffer>;
3
+ name: string;
4
+ }
5
+ /**
6
+ * Minimal streaming tar reader.
7
+ *
8
+ * Yields only the entries `wants` accepts, so the caller never has to hold the
9
+ * whole archive: everything else is consumed and dropped as it arrives. That is
10
+ * what lets us pull a ~110 MB Font Awesome tarball over the network and keep
11
+ * just the handful of SVGs a project actually references.
12
+ *
13
+ * Breaking out of the loop cancels the underlying stream, so a caller that has
14
+ * everything it needs stops the download instead of draining it.
15
+ */
16
+ export declare function _untar(stream: ReadableStream<Uint8Array>, wants: (name: string) => boolean): AsyncGenerator<TarEntry>;
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._untar = _untar;
4
+ const BLOCK_SIZE = 512;
5
+ const NAME_OFFSET = 0;
6
+ const NAME_LENGTH = 100;
7
+ const SIZE_OFFSET = 124;
8
+ const SIZE_LENGTH = 12;
9
+ const TYPEFLAG_OFFSET = 156;
10
+ const PREFIX_OFFSET = 345;
11
+ const PREFIX_LENGTH = 155;
12
+ const decoder = new TextDecoder();
13
+ function readString(header, offset, length) {
14
+ const raw = header.subarray(offset, offset + length);
15
+ const end = raw.indexOf(0);
16
+ return decoder.decode(end === -1 ? raw : raw.subarray(0, end)).trim();
17
+ }
18
+ // tar stores the size as a NUL/space terminated octal string. GNU also emits a
19
+ // base-256 form for sizes that do not fit in 11 octal digits; no entry in the
20
+ // Font Awesome tarballs is anywhere near 8 GB, so the octal form is enough.
21
+ function readSize(header) {
22
+ const raw = readString(header, SIZE_OFFSET, SIZE_LENGTH);
23
+ const parsed = Number.parseInt(raw, 8);
24
+ return Number.isFinite(parsed) ? parsed : 0;
25
+ }
26
+ function isZeroBlock(block) {
27
+ return block.every((byte) => byte === 0);
28
+ }
29
+ function concat(left, right) {
30
+ const out = new Uint8Array(left.length + right.length);
31
+ out.set(left, 0);
32
+ out.set(right, left.length);
33
+ return out;
34
+ }
35
+ /** A GNU "L" entry's payload is the replacement path, NUL terminated. */
36
+ function readGnuLongName(payload) {
37
+ const end = payload.indexOf(0);
38
+ const value = decoder.decode(end === -1 ? payload : payload.subarray(0, end)).trim();
39
+ return value === "" ? null : value;
40
+ }
41
+ const SPACE = 0x20;
42
+ const EQUALS = 0x3d;
43
+ /**
44
+ * A pax extended header's payload is a sequence of `"<len> <key>=<value>\n"`
45
+ * records, where `<len>` counts its own digits too. Only `path` interests us;
46
+ * everything else (mtime, sizes, ownership) is metadata we do not read.
47
+ *
48
+ * The walk is over bytes, not over a decoded string: `<len>` is a byte count,
49
+ * and a single non-ASCII character in an earlier record (an accented `uname`,
50
+ * say) would otherwise shift every subsequent offset and silently lose the
51
+ * path — which is the very truncation this exists to prevent.
52
+ */
53
+ function readPaxPath(payload) {
54
+ let offset = 0;
55
+ while (offset < payload.length) {
56
+ const space = payload.indexOf(SPACE, offset);
57
+ if (space === -1) {
58
+ return null;
59
+ }
60
+ const recordLength = Number.parseInt(decoder.decode(payload.subarray(offset, space)), 10);
61
+ if (!Number.isFinite(recordLength) || recordLength <= 0 || offset + recordLength > payload.length) {
62
+ return null;
63
+ }
64
+ const record = payload.subarray(space + 1, offset + recordLength);
65
+ const equals = record.indexOf(EQUALS);
66
+ if (equals !== -1 && decoder.decode(record.subarray(0, equals)) === "path") {
67
+ // The record ends with the newline its length accounts for.
68
+ return decoder.decode(record.subarray(equals + 1)).replace(/\n$/, "");
69
+ }
70
+ offset += recordLength;
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * Minimal streaming tar reader.
76
+ *
77
+ * Yields only the entries `wants` accepts, so the caller never has to hold the
78
+ * whole archive: everything else is consumed and dropped as it arrives. That is
79
+ * what lets us pull a ~110 MB Font Awesome tarball over the network and keep
80
+ * just the handful of SVGs a project actually references.
81
+ *
82
+ * Breaking out of the loop cancels the underlying stream, so a caller that has
83
+ * everything it needs stops the download instead of draining it.
84
+ */
85
+ async function* _untar(stream, wants) {
86
+ var _a;
87
+ const reader = stream.getReader();
88
+ let buffer = new Uint8Array(0);
89
+ let isStreamDone = false;
90
+ const pull = async () => {
91
+ const { done: isDone, value } = await reader.read();
92
+ if (isDone) {
93
+ isStreamDone = true;
94
+ return;
95
+ }
96
+ buffer = concat(buffer, value);
97
+ };
98
+ const ensure = async (length) => {
99
+ while (buffer.length < length && !isStreamDone) {
100
+ await pull();
101
+ }
102
+ return buffer.length >= length;
103
+ };
104
+ const take = (length) => {
105
+ const out = buffer.subarray(0, length);
106
+ buffer = buffer.subarray(length);
107
+ return out;
108
+ };
109
+ const skip = async (length) => {
110
+ let remaining = length;
111
+ while (remaining > 0) {
112
+ if (buffer.length === 0) {
113
+ if (isStreamDone) {
114
+ return false;
115
+ }
116
+ await pull();
117
+ continue;
118
+ }
119
+ const chunk = Math.min(remaining, buffer.length);
120
+ buffer = buffer.subarray(chunk);
121
+ remaining -= chunk;
122
+ }
123
+ return true;
124
+ };
125
+ // Set by a preceding GNU long-name ("L") or pax ("x") entry and consumed by
126
+ // the next real entry, whose own 100-byte name field is then a truncated
127
+ // duplicate. npm's own packer emits the pax form, so both have to work —
128
+ // ignoring them would index an icon under a silently truncated name.
129
+ let pendingLongName = null;
130
+ // Running out of data is never a normal ending: a tar archive finishes with
131
+ // a zero-filled block. Treating exhaustion as "done" would let a cut
132
+ // download look like a complete but much smaller archive, and the caller
133
+ // would cache that partial reading as authoritative.
134
+ const truncated = () => new Error("Unexpected end of tar archive — the download was cut short.");
135
+ try {
136
+ for (;;) {
137
+ if (!(await ensure(BLOCK_SIZE))) {
138
+ throw truncated();
139
+ }
140
+ const header = take(BLOCK_SIZE);
141
+ if (isZeroBlock(header)) {
142
+ return;
143
+ }
144
+ const size = readSize(header);
145
+ const padded = Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
146
+ const typeFlag = String.fromCharCode((_a = header.at(TYPEFLAG_OFFSET)) !== null && _a !== void 0 ? _a : 0);
147
+ if (typeFlag === "L" || typeFlag === "x" || typeFlag === "g") {
148
+ if (!(await ensure(padded))) {
149
+ throw truncated();
150
+ }
151
+ const payload = take(padded).subarray(0, size);
152
+ const overridden = typeFlag === "L" ? readGnuLongName(payload) : readPaxPath(payload);
153
+ // A global ("g") header carries defaults for the rest of the
154
+ // archive rather than for one entry; it never names a path, so
155
+ // in practice this leaves `pendingLongName` untouched.
156
+ if (overridden !== null) {
157
+ pendingLongName = overridden;
158
+ }
159
+ continue;
160
+ }
161
+ const prefix = readString(header, PREFIX_OFFSET, PREFIX_LENGTH);
162
+ const shortName = readString(header, NAME_OFFSET, NAME_LENGTH);
163
+ const name = pendingLongName !== null && pendingLongName !== void 0 ? pendingLongName : (prefix ? `${prefix}/${shortName}` : shortName);
164
+ pendingLongName = null;
165
+ const isFile = typeFlag === "0" || typeFlag === "\0";
166
+ if (!isFile || !wants(name)) {
167
+ if (!(await skip(padded))) {
168
+ throw truncated();
169
+ }
170
+ continue;
171
+ }
172
+ if (!(await ensure(padded))) {
173
+ throw truncated();
174
+ }
175
+ // Copy out of the shared buffer — `subarray` would keep the whole
176
+ // backing chunk alive for as long as the caller holds the entry.
177
+ yield { content: new Uint8Array(take(padded).subarray(0, size)), name };
178
+ }
179
+ }
180
+ finally {
181
+ await reader.cancel().catch(() => undefined);
182
+ }
183
+ }