@homeflare/alchemy 0.22.0 → 0.24.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.
- package/README.md +15 -1
- package/dist/litellm/client.d.ts +76 -0
- package/dist/litellm/client.d.ts.map +1 -0
- package/dist/litellm/credentials.d.ts +48 -0
- package/dist/litellm/credentials.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +36 -0
- package/dist/litellm/fake-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +30 -0
- package/dist/litellm/fake-stack.d.ts.map +1 -0
- package/dist/litellm/generated/pass-through.d.ts +71 -0
- package/dist/litellm/generated/pass-through.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +13 -0
- package/dist/litellm/index.d.ts.map +1 -0
- package/dist/litellm/index.js +270 -0
- package/dist/litellm/index.js.map +14 -0
- package/dist/litellm/pass-through-endpoint.d.ts +94 -0
- package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
- package/dist/litellm/pass-through-form.d.ts +40 -0
- package/dist/litellm/pass-through-form.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +16 -0
- package/dist/litellm/providers.d.ts.map +1 -0
- package/dist/postgres/connection.d.ts +50 -0
- package/dist/postgres/connection.d.ts.map +1 -0
- package/dist/postgres/database-attrs.d.ts +79 -0
- package/dist/postgres/database-attrs.d.ts.map +1 -0
- package/dist/postgres/database-sql.d.ts +73 -0
- package/dist/postgres/database-sql.d.ts.map +1 -0
- package/dist/postgres/database.d.ts +63 -0
- package/dist/postgres/database.d.ts.map +1 -0
- package/dist/postgres/errors.d.ts +86 -0
- package/dist/postgres/errors.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +22 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +16 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +238 -0
- package/dist/postgres/index.js.map +15 -0
- package/dist/postgres/providers.d.ts +14 -0
- package/dist/postgres/providers.d.ts.map +1 -0
- package/dist/proxmox/api-token-form.d.ts +12 -1
- package/dist/proxmox/api-token-form.d.ts.map +1 -1
- package/dist/proxmox/api-token.d.ts +8 -3
- package/dist/proxmox/api-token.d.ts.map +1 -1
- package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts +13 -13
- package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts.map +1 -1
- package/dist/proxmox/index.d.ts +9 -2
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +17 -9
- package/dist/proxmox/index.js.map +8 -7
- package/dist/proxmox/zfs-pool-spec.d.ts +27 -0
- package/dist/proxmox/zfs-pool-spec.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool-write.d.ts +48 -7
- package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool.d.ts +52 -22
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/dist/release/archive.d.ts +6 -2
- package/dist/release/archive.d.ts.map +1 -1
- package/dist/release/binary-form.d.ts +14 -9
- package/dist/release/binary-form.d.ts.map +1 -1
- package/dist/release/binary-pins.d.ts +17 -0
- package/dist/release/binary-pins.d.ts.map +1 -0
- package/dist/release/catalog.d.ts.map +1 -1
- package/dist/release/index.js +90 -40
- package/dist/release/index.js.map +8 -7
- package/dist/release/tar.d.ts +9 -2
- package/dist/release/tar.d.ts.map +1 -1
- package/docs/adopted-deploys.md +14 -0
- package/docs/litellm.md +110 -0
- package/docs/peers.md +8 -1
- package/docs/postgres.md +125 -0
- package/docs/release-binary-catalogs.md +40 -4
- package/docs/release-binary-refusals.md +22 -8
- package/docs/release-binary-upstream.md +33 -15
- package/package.json +10 -1
- package/src/litellm/client.ts +195 -0
- package/src/litellm/credentials.ts +73 -0
- package/src/litellm/fake-litellm.ts +114 -0
- package/src/litellm/fake-stack.ts +100 -0
- package/src/litellm/generated/pass-through.ts +73 -0
- package/src/litellm/index.ts +42 -0
- package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
- package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
- package/src/litellm/pass-through-endpoint.test.ts +208 -0
- package/src/litellm/pass-through-endpoint.ts +258 -0
- package/src/litellm/pass-through-form.test.ts +128 -0
- package/src/litellm/pass-through-form.ts +136 -0
- package/src/litellm/providers.ts +19 -0
- package/src/postgres/connection.ts +62 -0
- package/src/postgres/database-attrs.ts +87 -0
- package/src/postgres/database-bigint-serialization.test.ts +140 -0
- package/src/postgres/database-lifecycle.test.ts +113 -0
- package/src/postgres/database-provenance.test.ts +118 -0
- package/src/postgres/database-refusals.test.ts +135 -0
- package/src/postgres/database-sql.ts +174 -0
- package/src/postgres/database.ts +193 -0
- package/src/postgres/errors.ts +131 -0
- package/src/postgres/fake-sql.ts +123 -0
- package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
- package/src/postgres/index.ts +30 -0
- package/src/postgres/providers.ts +16 -0
- package/src/proxmox/api-token-adopt.test.ts +138 -0
- package/src/proxmox/api-token-form.ts +19 -2
- package/src/proxmox/api-token.ts +8 -3
- package/src/proxmox/generated/pve/cluster-sdn-fabrics.ts +23 -13
- package/src/proxmox/index.test.ts +5 -2
- package/src/proxmox/index.ts +9 -2
- package/src/proxmox/zfs-pool-adopt.test.ts +141 -0
- package/src/proxmox/zfs-pool-spec.ts +76 -0
- package/src/proxmox/zfs-pool-write.ts +63 -9
- package/src/proxmox/zfs-pool.ts +55 -97
- package/src/release/archive.ts +10 -4
- package/src/release/binary-form.ts +24 -76
- package/src/release/binary-pins.ts +95 -0
- package/src/release/binary-root.test.ts +122 -0
- package/src/release/catalog.ts +10 -2
- package/src/release/tar-root.test.ts +114 -0
- package/src/release/tar.ts +78 -12
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tarReader(wanted, root)` — the declared-root mode measured against the Prometheus-family shape
|
|
3
|
+
* (tar.ts's header comment, 2026-09-23): one directory entry wraps every other entry, stripped
|
|
4
|
+
* before a name is matched, listed or checked for a duplicate. Everything tar.test.ts already
|
|
5
|
+
* refuses keeps refusing; this file is only what `root` adds or changes.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, test } from 'bun:test';
|
|
8
|
+
import { bytesOf, tarOf } from './fake-release.ts';
|
|
9
|
+
import { ArchiveRefused } from './refused.ts';
|
|
10
|
+
import { tarReader } from './tar.ts';
|
|
11
|
+
|
|
12
|
+
const ROOT = 'alertmanager-0.33.1.darwin-arm64';
|
|
13
|
+
const wrapper = { name: `${ROOT}/`, type: '5' } as const;
|
|
14
|
+
const binary = { bytes: bytesOf('#!binary\n'), name: `${ROOT}/alertmanager` } as const;
|
|
15
|
+
|
|
16
|
+
const read = (tar: Uint8Array, wanted: readonly string[], root?: string) => {
|
|
17
|
+
const reader = tarReader(new Set(wanted), root);
|
|
18
|
+
reader.push(tar);
|
|
19
|
+
return reader.finish();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const refusal = (run: () => unknown): string => {
|
|
23
|
+
try {
|
|
24
|
+
run();
|
|
25
|
+
} catch (cause) {
|
|
26
|
+
expect(cause).toBeInstanceOf(ArchiveRefused);
|
|
27
|
+
return (cause as ArchiveRefused).message;
|
|
28
|
+
}
|
|
29
|
+
throw new Error('expected a refusal');
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('a declared root strips exactly one leading directory', () => {
|
|
33
|
+
test('the member comes back under its stripped name; the root entry is not one of the names', () => {
|
|
34
|
+
const tar = tarOf([wrapper, binary]);
|
|
35
|
+
const { members, names } = read(tar, ['alertmanager'], ROOT);
|
|
36
|
+
expect(new TextDecoder().decode(members.get('alertmanager'))).toBe('#!binary\n');
|
|
37
|
+
expect(names).toEqual(['alertmanager']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('a sibling regular file under root is listed, stripped, but not fetched unless asked for', () => {
|
|
41
|
+
const notice = { bytes: bytesOf('legal text'), name: `${ROOT}/NOTICE` };
|
|
42
|
+
const { members, names } = read(tarOf([wrapper, binary, notice]), ['alertmanager'], ROOT);
|
|
43
|
+
expect(names).toEqual(['alertmanager', 'NOTICE']);
|
|
44
|
+
expect(members.has('NOTICE')).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('THE SAME ARCHIVE, root omitted: still refused "is a directory" — origin/main\'s behaviour', () => {
|
|
48
|
+
const tar = tarOf([wrapper, binary]);
|
|
49
|
+
expect(refusal(() => read(tar, ['alertmanager']))).toContain('is a directory');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('a root declared but never present in the archive is refused', () => {
|
|
53
|
+
expect(refusal(() => read(tarOf([binary]), ['alertmanager'], ROOT))).toContain('never appears');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 🔴 ADVERSARIAL REVIEW, 2026-09-23 (before merge): a stripped name was never re-checked by
|
|
57
|
+
// `nameProblem`, so `<root>//x` — not absolute as a whole raw name, but stripping to `/x` —
|
|
58
|
+
// parsed to completion as a second, distinct, un-refused entry `/x` beside `x`. Fixed in
|
|
59
|
+
// tar.ts by running `nameProblem` on the STRIPPED name too, not only the raw one.
|
|
60
|
+
test('stripping cannot uncover an absolute name the reader would otherwise refuse', () => {
|
|
61
|
+
// `<root>//alertmanager` is not absolute AS A WHOLE NAME, so it passes nameProblem before
|
|
62
|
+
// stripping; only after `<root>/` is removed does it become the absolute name `/alertmanager`.
|
|
63
|
+
const evil = { bytes: bytesOf('evil'), name: `${ROOT}//alertmanager` };
|
|
64
|
+
expect(refusal(() => read(tarOf([wrapper, binary, evil]), ['alertmanager'], ROOT))).toContain(
|
|
65
|
+
'strips to "/alertmanager"',
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('everything a declared root still refuses, whole archive', () => {
|
|
71
|
+
test.each([
|
|
72
|
+
['a second directory entry (the root, twice)', [wrapper, wrapper, binary], 'appearing twice'],
|
|
73
|
+
[
|
|
74
|
+
'an entry named <root>-evil/x: a segment match, not a string prefix',
|
|
75
|
+
[wrapper, binary, { name: `${ROOT}-evil/x` }],
|
|
76
|
+
'outside the declared root',
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
'a regular file at archive top level beside root',
|
|
80
|
+
[wrapper, binary, { name: 'README' }],
|
|
81
|
+
'outside the declared root',
|
|
82
|
+
],
|
|
83
|
+
['<root>/../x climbs out through the root', [wrapper, { name: `${ROOT}/../x` }], 'climbs out'],
|
|
84
|
+
[
|
|
85
|
+
'a PAX x header inside root',
|
|
86
|
+
[wrapper, binary, { name: `${ROOT}/PaxHeader`, type: 'x' }],
|
|
87
|
+
'PAX extended',
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
'a GNU L header inside root',
|
|
91
|
+
[wrapper, binary, { name: `${ROOT}/@LongLink`, type: 'L' }],
|
|
92
|
+
'GNU long-name',
|
|
93
|
+
],
|
|
94
|
+
['a symlink inside root', [wrapper, binary, { name: `${ROOT}/link`, type: '2' }], 'a symlink'],
|
|
95
|
+
[
|
|
96
|
+
'the root entry with size > 0',
|
|
97
|
+
[{ ...wrapper, bytes: bytesOf('x') }, binary],
|
|
98
|
+
'not an empty directory',
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
'the root entry as typeflag 1 (a hard link), not a directory',
|
|
102
|
+
[{ name: `${ROOT}/`, type: '1' }],
|
|
103
|
+
'not an empty directory',
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
'the root entry as typeflag 2 (a symlink), not a directory',
|
|
107
|
+
[{ name: `${ROOT}/`, type: '2' }],
|
|
108
|
+
'not an empty directory',
|
|
109
|
+
],
|
|
110
|
+
['a duplicate name after stripping', [wrapper, binary, binary], 'appears twice'],
|
|
111
|
+
] as const)('%s', (_, entries, message) => {
|
|
112
|
+
expect(refusal(() => read(tarOf(entries), ['alertmanager'], ROOT))).toContain(message);
|
|
113
|
+
});
|
|
114
|
+
});
|
package/src/release/tar.ts
CHANGED
|
@@ -15,10 +15,25 @@
|
|
|
15
15
|
* non-zero bytes after the end marker; an archive that stops before it.
|
|
16
16
|
* ★ GNU LONG NAMES ('L', 'K') AND PAX HEADERS ('x', 'g') ARE REFUSED, NOT INTERPRETED. Each renames
|
|
17
17
|
* the entry after it: a reader that honours them must get them exactly right to match exactly,
|
|
18
|
-
* and one that skips them matches under the wrong name.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* and one that skips them matches under the wrong name. Refusing costs nothing for an archive
|
|
19
|
+
* that carries neither, and a vendor format change becomes a refusal that says what changed
|
|
20
|
+
* instead of a silent mis-read.
|
|
21
|
+
* ★ AN OPTIONAL `root`: ONE DECLARED LEADING DIRECTORY, STRIPPED, NEVER GUESSED. Measured
|
|
22
|
+
* 2026-09-23: `gh api repos/<o>/<r>/releases/tags/<tag>` for each asset URL and digest, the
|
|
23
|
+
* archive downloaded into a scratch directory (never executed), re-hashed against GitHub's
|
|
24
|
+
* `digest`, then read by THIS reader (a driver script importing `tarReader`) both before and
|
|
25
|
+
* after this change — refused whole either way without `root`, and, with it, parsed to
|
|
26
|
+
* completion: `alertmanager-0.33.1.darwin-arm64.tar.gz` (37,247,168 B),
|
|
27
|
+
* `blackbox_exporter-0.28.0.darwin-arm64.tar.gz` (15,705,022 B),
|
|
28
|
+
* `node_exporter-1.12.1.darwin-arm64.tar.gz` (5,368,643 B) and
|
|
29
|
+
* `postgres_exporter-0.20.1.darwin-arm64.tar.gz` (10,072,235 B) each recompute to GitHub's own
|
|
30
|
+
* `digest`, and each wraps every entry in exactly one directory (typeflag '5', size 0, first)
|
|
31
|
+
* named `<binary>-<version>.darwin-arm64/`, holding only root-level regular files (the binary,
|
|
32
|
+
* `LICENSE`, `NOTICE`, and for alertmanager also `amtool` and `alertmanager.yml`) — no PAX or
|
|
33
|
+
* GNU long-name entries, so this reader's refusal of both stays exactly as costly as before.
|
|
34
|
+
* Without `root`, EVERY refusal above still holds byte-for-byte: a directory entry — the wrapper
|
|
35
|
+
* included — is refused the same way it always was (docs/release-binary-catalogs.md,
|
|
36
|
+
* docs/release-binary-upstream.md gap 11).
|
|
22
37
|
*/
|
|
23
38
|
import { ArchiveRefused } from './refused.ts';
|
|
24
39
|
|
|
@@ -101,8 +116,15 @@ export type TarContents = {
|
|
|
101
116
|
readonly names: readonly string[];
|
|
102
117
|
};
|
|
103
118
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
119
|
+
/**
|
|
120
|
+
* A push parser: `push` each chunk of the unzipped stream, then `finish`. Either may throw.
|
|
121
|
+
*
|
|
122
|
+
* @param root When given, exactly one typeflag-'5' entry named `<root>/`, size 0, is accepted and
|
|
123
|
+
* dropped rather than refused as a directory; every OTHER entry name must begin with the SEGMENT
|
|
124
|
+
* `<root>/` (a `<root>-evil/x` sibling does not) and is reported, matched and kept under that
|
|
125
|
+
* prefix stripped off. Omitted, behaviour is byte-identical to a reader with no root at all.
|
|
126
|
+
*/
|
|
127
|
+
export const tarReader = (wanted: ReadonlySet<string>, root?: string) => {
|
|
106
128
|
const header = new Uint8Array(BLOCK);
|
|
107
129
|
const members = new Map<string, Uint8Array>();
|
|
108
130
|
const names: string[] = [];
|
|
@@ -114,6 +136,8 @@ export const tarReader = (wanted: ReadonlySet<string>) => {
|
|
|
114
136
|
let zeros = 0;
|
|
115
137
|
let name = '';
|
|
116
138
|
let parts: Uint8Array[] | undefined;
|
|
139
|
+
let rootSeen = false;
|
|
140
|
+
const rootPrefix = root === undefined ? undefined : `${root}/`;
|
|
117
141
|
|
|
118
142
|
const endEntry = () => {
|
|
119
143
|
if (parts !== undefined) members.set(name, concatBytes(parts));
|
|
@@ -134,18 +158,57 @@ export const tarReader = (wanted: ReadonlySet<string>) => {
|
|
|
134
158
|
if (!posix && magic !== 'ustar \u0000')
|
|
135
159
|
throw refuse('an entry header is neither ustar nor GNU');
|
|
136
160
|
const prefix = posix ? text(header, 345, 155) : '';
|
|
137
|
-
|
|
138
|
-
const problem = nameProblem(
|
|
139
|
-
if (problem !== undefined) throw refuse(`entry ${JSON.stringify(
|
|
161
|
+
const rawName = prefix === '' ? text(header, 0, 100) : `${prefix}/${text(header, 0, 100)}`;
|
|
162
|
+
const problem = nameProblem(rawName);
|
|
163
|
+
if (problem !== undefined) throw refuse(`entry ${JSON.stringify(rawName)} ${problem}`);
|
|
164
|
+
// ★ THE DECLARED ROOT IS RECOGNISED BEFORE THE GENERAL TYPE CHECK, and only by exact name: a
|
|
165
|
+
// directory entry under any other name — nested, or a second copy of the root itself — still
|
|
166
|
+
// falls through to that check below and is refused as a directory, same as with no root.
|
|
167
|
+
if (rootPrefix !== undefined && rawName === rootPrefix) {
|
|
168
|
+
if (rootSeen) {
|
|
169
|
+
// ⚠️ Whatever type THIS entry is, its name is already taken: refused for reusing the
|
|
170
|
+
// root's name, not necessarily for being a second directory (it need not be one).
|
|
171
|
+
throw refuse(`entry "${rawName}" reuses the declared root's own name, appearing twice`);
|
|
172
|
+
}
|
|
173
|
+
const type = String.fromCharCode(header[156] ?? 0);
|
|
174
|
+
const size = octal(header, 124, 12, `entry "${rawName}"'s size`);
|
|
175
|
+
if (type !== '5' || size !== 0) {
|
|
176
|
+
throw refuse(`entry "${rawName}" is the declared root but is not an empty directory`);
|
|
177
|
+
}
|
|
178
|
+
rootSeen = true;
|
|
179
|
+
phase = 'header';
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let stripped = rawName;
|
|
183
|
+
if (rootPrefix !== undefined) {
|
|
184
|
+
if (!rawName.startsWith(rootPrefix)) {
|
|
185
|
+
throw refuse(`entry "${rawName}" is outside the declared root "${rootPrefix}"`);
|
|
186
|
+
}
|
|
187
|
+
// ⛔ THE STRIPPED NAME GETS THE SAME SAFETY CHECK AS A RAW ONE. `rawName` already passed
|
|
188
|
+
// `nameProblem` above, but stripping a prefix can UNCOVER a `/` or `..` that was hiding
|
|
189
|
+
// past it: `<root>//x` is not absolute as a whole name, but strips to `/x`, which is —
|
|
190
|
+
// the exact thing this reader refuses a plain (rootless) archive for. MEASURED: without
|
|
191
|
+
// this check, `<root>//x` beside `<root>/x` parsed to completion as two distinct entries,
|
|
192
|
+
// `x` and `/x`, neither refused. `stripped` can never be empty here: the one raw name that
|
|
193
|
+
// strips to '' is `rootPrefix` itself, handled above.
|
|
194
|
+
stripped = rawName.slice(rootPrefix.length);
|
|
195
|
+
const strippedProblem = nameProblem(stripped);
|
|
196
|
+
if (strippedProblem !== undefined) {
|
|
197
|
+
throw refuse(
|
|
198
|
+
`entry "${rawName}" strips to ${JSON.stringify(stripped)}, which ${strippedProblem}`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
name = stripped;
|
|
140
203
|
const type = String.fromCharCode(header[156] ?? 0);
|
|
141
204
|
if (type !== '0' && type !== '\0') {
|
|
142
205
|
const kind = KINDS[type] ?? `of unknown type ${JSON.stringify(type)}`;
|
|
143
|
-
throw refuse(`entry "${
|
|
206
|
+
throw refuse(`entry "${rawName}" is ${kind}; only regular files are accepted`);
|
|
144
207
|
}
|
|
145
|
-
if (seen.has(name)) throw refuse(`entry "${
|
|
208
|
+
if (seen.has(name)) throw refuse(`entry "${rawName}" appears twice`);
|
|
146
209
|
seen.add(name);
|
|
147
210
|
names.push(name);
|
|
148
|
-
remaining = octal(header, 124, 12, `entry "${
|
|
211
|
+
remaining = octal(header, 124, 12, `entry "${rawName}"'s size`);
|
|
149
212
|
padding = (BLOCK - (remaining % BLOCK)) % BLOCK;
|
|
150
213
|
parts = wanted.has(name) ? [] : undefined;
|
|
151
214
|
phase = 'data';
|
|
@@ -188,6 +251,9 @@ export const tarReader = (wanted: ReadonlySet<string>) => {
|
|
|
188
251
|
|
|
189
252
|
const finish = (): TarContents => {
|
|
190
253
|
if (phase !== 'end') throw refuse('the archive stops before its end-of-archive marker');
|
|
254
|
+
if (rootPrefix !== undefined && !rootSeen) {
|
|
255
|
+
throw refuse(`the declared root "${rootPrefix}" never appears in the archive`);
|
|
256
|
+
}
|
|
191
257
|
return { members, names };
|
|
192
258
|
};
|
|
193
259
|
|