@moewolf/moe-icons-cli 0.0.10 → 0.0.14
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 +27 -19
- package/dist/cli.js +124 -146
- package/dist/config-package/generated/SOURCE.json +3 -3
- package/dist/config-package/generated/moeicons-config.schema.json +1 -1
- package/dist/core/bitmap-shard-budget.d.ts +2 -2
- package/dist/core/bitmap-shard-budget.js +1 -1
- package/dist/core/bootstrap.js +5 -3
- package/dist/core/candidate-version-pointer.d.ts +13 -0
- package/dist/core/candidate-version-pointer.js +37 -0
- package/dist/core/config-package-plan.d.ts +25 -0
- package/dist/core/config-package-plan.js +156 -0
- package/dist/core/doctor.js +4 -0
- package/dist/core/free-download.js +34 -4
- package/dist/core/free-small-package-download.d.ts +67 -0
- package/dist/core/free-small-package-download.js +344 -0
- package/dist/core/generate.js +49 -33
- package/dist/core/install.d.ts +8 -2
- package/dist/core/install.js +70 -0
- package/dist/core/library-update.d.ts +6 -1
- package/dist/core/library-update.js +54 -0
- package/dist/core/manage.d.ts +4 -0
- package/dist/core/manage.js +4 -0
- package/dist/core/metadata-sync.js +119 -1
- package/dist/core/pro-download.js +19 -2
- package/dist/core/pro-install.d.ts +10 -4
- package/dist/core/pro-install.js +47 -0
- package/dist/core/pro-resources.d.ts +23 -0
- package/dist/core/pro-resources.js +51 -0
- package/dist/core/pro-selected-download.js +12 -2
- package/dist/core/pro-small-package-download.d.ts +61 -0
- package/dist/core/pro-small-package-download.js +301 -0
- package/dist/core/release-descriptor.js +6 -2
- package/dist/core/selected-resources.js +2 -2
- package/dist/core/small-pack/PIN.json +11 -0
- package/dist/core/small-pack/candidate-set-v1.schema.json +187 -0
- package/dist/core/small-pack/candidate-set.cjs +286 -0
- package/dist/core/small-pack/candidate-set.d.cts +12 -0
- package/dist/core/small-pack/tar-read.cjs +220 -0
- package/dist/core/small-pack/tar-read.d.cts +2 -0
- package/dist/core/small-package-install.d.ts +40 -0
- package/dist/core/small-package-install.js +252 -0
- package/dist/core/small-package-selection.d.ts +27 -0
- package/dist/core/small-package-selection.js +114 -0
- package/dist/core/update-policy.d.ts +1 -0
- package/dist/core/update-policy.js +10 -3
- package/dist/mcp/services.js +1 -1
- package/dist/project/config.js +1 -1
- package/dist/project/install-metadata.d.ts +2 -1
- package/dist/project/install-metadata.js +6 -3
- package/dist/project/install.d.ts +11 -2
- package/dist/project/install.js +104 -12
- package/dist/project/package-install-metadata.d.ts +31 -0
- package/dist/project/package-install-metadata.js +99 -0
- package/dist/project/tar-gz.d.ts +4 -8
- package/dist/project/tar-gz.js +6 -10
- package/package.json +11 -11
|
@@ -8,9 +8,11 @@ import { readMoeiconsConfig, loadConfigDocument, validateConfigDocument, } from
|
|
|
8
8
|
import { detectProject } from "../project/detect.js";
|
|
9
9
|
import { executeManagedReconcile } from "../project/install.js";
|
|
10
10
|
import { parseInstallMetadata, serializeInstallMetadata, sha256Bytes, } from "../project/install-metadata.js";
|
|
11
|
+
import { parsePackageInstallMetadata } from "../project/package-install-metadata.js";
|
|
11
12
|
import { withProjectLock } from "../project/project-lock.js";
|
|
12
13
|
import { ensureClassMergeDependencies, planTailwindIntegration } from "../project/tailwind.js";
|
|
13
14
|
import { extractTarGzAsync, ICON_ARCHIVE_MAX_ENTRIES, ICON_ARCHIVE_MAX_EXPANDED_BYTES, } from "../project/tar-gz.js";
|
|
15
|
+
import { runSmallPackageInstall } from "./small-package-install.js";
|
|
14
16
|
import { artifactCachePath, downloadFreeRelease, metadataCachePath, } from "./free-download.js";
|
|
15
17
|
import { downloadProArtifact } from "./pro-download.js";
|
|
16
18
|
import { PRO_DOWNLOAD_HOSTS, resolveProDescriptorEndpoint } from "./pro-download.js";
|
|
@@ -22,6 +24,18 @@ export async function runLibraryUpdateUseCase(context, deps, expected) {
|
|
|
22
24
|
const project = detectProject(context.cwd);
|
|
23
25
|
if (!project)
|
|
24
26
|
throw new CliError("VALIDATION_ERROR", "no package.json found in the current directory or parents");
|
|
27
|
+
const installedMetadataPath = join(project.root, ".moeicons", "install-metadata.json");
|
|
28
|
+
let packageSetInstalled = false;
|
|
29
|
+
if (deps.fs.existsSync(installedMetadataPath)) {
|
|
30
|
+
let schemaVersion;
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(deps.fs.readFileSync(installedMetadataPath, "utf8"));
|
|
33
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
34
|
+
schemaVersion = parsed.schemaVersion;
|
|
35
|
+
}
|
|
36
|
+
catch { /* validated by the normal install path below */ }
|
|
37
|
+
packageSetInstalled = schemaVersion === 2;
|
|
38
|
+
}
|
|
25
39
|
const document = loadConfigDocument(project.root);
|
|
26
40
|
const bootstrap = validateConfigDocument(document, undefined, { lenientCatalog: true });
|
|
27
41
|
if (bootstrap.kind !== "ok" || bootstrap.config.tier !== expected.tier)
|
|
@@ -29,6 +43,46 @@ export async function runLibraryUpdateUseCase(context, deps, expected) {
|
|
|
29
43
|
? bootstrap.message
|
|
30
44
|
: `config.tier must match ${expected.tier}; edit config before updating`);
|
|
31
45
|
assertDownloadSelection(bootstrap.config);
|
|
46
|
+
if (packageSetInstalled) {
|
|
47
|
+
const installed = parsePackageInstallMetadata(deps.fs.readFileSync(installedMetadataPath, "utf8"));
|
|
48
|
+
if (!installed || installed.candidate.tier !== expected.tier) {
|
|
49
|
+
const tierLabel = expected.tier === "free" ? "Free" : "Pro";
|
|
50
|
+
throw new CliError("VALIDATION_ERROR", `package-set update requires a valid installed ${tierLabel} candidate`);
|
|
51
|
+
}
|
|
52
|
+
if ((expected.tier === "free" && !deps.free) || !expected.packageSetSha256 || !/^[a-f0-9]{64}$/.test(expected.packageSetSha256))
|
|
53
|
+
throw new CliError("VALIDATION_ERROR", "package-set update requires an exact Worker package-set identity");
|
|
54
|
+
if (installed.candidate.fullVersion === expected.version && installed.candidate.packageSetSha256 !== expected.packageSetSha256)
|
|
55
|
+
throw new CliError("VALIDATION_ERROR", "published package-set identity changed for the installed version; refusing replacement");
|
|
56
|
+
const proAccessToken = expected.tier === "pro" ? await runAccessTokenUseCase(context, deps.auth) : undefined;
|
|
57
|
+
const result = await runSmallPackageInstall({
|
|
58
|
+
projectRoot: project.root,
|
|
59
|
+
fullVersion: expected.version,
|
|
60
|
+
expectedPackageSetSha256: expected.packageSetSha256,
|
|
61
|
+
...(context.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: context.env.MOEICONS_API_BASE_URL } : {}),
|
|
62
|
+
config: bootstrap.config,
|
|
63
|
+
plannedConfigDocument: JSON.stringify(document),
|
|
64
|
+
fs: deps.fs,
|
|
65
|
+
cacheDir: deps.free?.cacheDir ?? join(homedir(), ".moeicons", "cache"),
|
|
66
|
+
now: context.now,
|
|
67
|
+
...(proAccessToken ? { proAccessToken } : {}),
|
|
68
|
+
...(expected.tier === "free" && deps.free?.fixtureBaseUrl ? { fixtureOrigin: deps.free.fixtureBaseUrl } : {}),
|
|
69
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
70
|
+
...(deps.fetch ? { fetchFn: deps.fetch } : {}),
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
projectRoot: project.root,
|
|
74
|
+
artifactVersion: result.artifactVersion,
|
|
75
|
+
files: result.files,
|
|
76
|
+
downloadMode: "packages",
|
|
77
|
+
downloadNotes: result.fallbacks.map((fallback) => `Theme fallback: ${fallback}`),
|
|
78
|
+
networkBytes: result.networkBytes,
|
|
79
|
+
selectedFiles: result.selectedFiles,
|
|
80
|
+
packageCount: result.packageCount,
|
|
81
|
+
cacheHits: result.cacheHits,
|
|
82
|
+
metadataCacheHits: result.metadataCacheHits,
|
|
83
|
+
transferRetryCount: result.transferRetryCount,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
32
86
|
let selected;
|
|
33
87
|
let catalogJson;
|
|
34
88
|
let fullArchiveFiles;
|
package/dist/core/manage.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readInstalledResourceState, type InstallMetadata } from "../project/install-metadata.js";
|
|
2
|
+
import type { PackageInstallMetadata } from "../project/package-install-metadata.js";
|
|
2
3
|
import { type PublicLibraryVersions } from "./version-service.js";
|
|
3
4
|
export type LibraryVersionStatus = {
|
|
4
5
|
readonly kind: "installation-invalid";
|
|
@@ -11,6 +12,9 @@ export type LibraryVersionStatus = {
|
|
|
11
12
|
readonly kind: "unavailable";
|
|
12
13
|
readonly metadata: InstallMetadata;
|
|
13
14
|
readonly message: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "package-set-update-check-required";
|
|
17
|
+
readonly metadata: PackageInstallMetadata;
|
|
14
18
|
} | {
|
|
15
19
|
readonly kind: "current" | "update" | "channel-mismatch" | "remote-older";
|
|
16
20
|
readonly metadata: InstallMetadata;
|
package/dist/core/manage.js
CHANGED
|
@@ -5,6 +5,8 @@ export async function getLibraryVersionStatus(projectRoot, configTier, deps = {}
|
|
|
5
5
|
const installed = (deps.readState ?? readInstalledResourceState)(projectRoot, configTier);
|
|
6
6
|
if (installed.kind !== "ok")
|
|
7
7
|
return { kind: "installation-invalid", message: installed.message };
|
|
8
|
+
if (installed.metadata.schemaVersion === 2)
|
|
9
|
+
return { kind: "package-set-update-check-required", metadata: installed.metadata };
|
|
8
10
|
let versions;
|
|
9
11
|
try {
|
|
10
12
|
versions = await (deps.fetchVersions ?? (() => fetchLibraryVersions()))();
|
|
@@ -31,6 +33,8 @@ export async function getLibraryVersionStatus(projectRoot, configTier, deps = {}
|
|
|
31
33
|
export function formatLibraryVersionStatus(status) {
|
|
32
34
|
if (status.kind === "installation-invalid")
|
|
33
35
|
return `Current: invalid / Latest: unavailable / Status: installation invalid (${status.message})`;
|
|
36
|
+
if (status.kind === "package-set-update-check-required")
|
|
37
|
+
return `Current: ${status.metadata.candidate.fullVersion} / Latest: unavailable / Status: package-set update check required`;
|
|
34
38
|
if (status.kind === "check-failed" || status.kind === "unavailable")
|
|
35
39
|
return `Current: ${status.metadata.artifactVersion} / Latest: unavailable / Status: ${status.message}`;
|
|
36
40
|
const label = status.kind === "current" ? "up to date" : status.kind === "update" ? "update available" : status.kind === "channel-mismatch" ? "prerelease channel differs" : "remote version is older";
|
|
@@ -2,13 +2,20 @@ import { join } from "node:path";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { CliError } from "../errors/index.js";
|
|
4
4
|
import { detectProject } from "../project/detect.js";
|
|
5
|
+
import { loadConfigDocument, validateConfigDocument } from "../project/config.js";
|
|
5
6
|
import { parseInstallMetadata, serializeInstallMetadata, sha256Bytes, } from "../project/install-metadata.js";
|
|
7
|
+
import { parsePackageInstallMetadata, serializePackageInstallMetadata } from "../project/package-install-metadata.js";
|
|
6
8
|
import { executeManagedReconcile } from "../project/install.js";
|
|
7
9
|
import { withProjectLock } from "../project/project-lock.js";
|
|
8
10
|
import { cacheArtifact } from "./cache.js";
|
|
9
11
|
import { downloadMetadataArchive, fetchFreeDescriptor, metadataCachePath, } from "./free-download.js";
|
|
10
12
|
import { downloadSignedArtifact } from "./signed-artifact.js";
|
|
11
13
|
import { extractProMetadata, fetchProDescriptor, proSignedDownloadOptions } from "./pro-download.js";
|
|
14
|
+
import { downloadFreeVersionPackages } from "./free-small-package-download.js";
|
|
15
|
+
import { downloadProVersionPackages } from "./pro-small-package-download.js";
|
|
16
|
+
import { runAccessTokenUseCase } from "./auth.js";
|
|
17
|
+
import { packageTargetFiles, planConfigPackagesFromMetadata } from "./config-package-plan.js";
|
|
18
|
+
import { canonicalBytes, sha256 } from "./small-pack/candidate-set.cjs";
|
|
12
19
|
function toCliError(result) {
|
|
13
20
|
return new CliError(result.reason === "cancelled" ? "CANCELLED" : "VALIDATION_ERROR", result.message);
|
|
14
21
|
}
|
|
@@ -25,7 +32,18 @@ export async function runMetadataSyncUseCase(context, deps, options = {}) {
|
|
|
25
32
|
const metadataPath = join(project.root, ".moeicons", "install-metadata.json");
|
|
26
33
|
if (!deps.fs.existsSync(metadataPath))
|
|
27
34
|
throw new CliError("VALIDATION_ERROR", "managed install metadata is missing; run install first");
|
|
28
|
-
const
|
|
35
|
+
const rawMetadata = deps.fs.readFileSync(metadataPath, "utf8");
|
|
36
|
+
let schemaVersion;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(rawMetadata);
|
|
39
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
40
|
+
schemaVersion = parsed.schemaVersion;
|
|
41
|
+
}
|
|
42
|
+
catch { /* reported as invalid metadata below */ }
|
|
43
|
+
if (schemaVersion === 2) {
|
|
44
|
+
return runPackageSetMetadataSync(context, deps, project.root, rawMetadata, options);
|
|
45
|
+
}
|
|
46
|
+
const old = parseInstallMetadata(rawMetadata);
|
|
29
47
|
if (!old)
|
|
30
48
|
throw new CliError("VALIDATION_ERROR", "managed install metadata is invalid; run install first");
|
|
31
49
|
const tier = options.tier ?? old.tier;
|
|
@@ -95,3 +113,103 @@ export async function runMetadataSyncUseCase(context, deps, options = {}) {
|
|
|
95
113
|
files: [".moeicons/MANUAL.md", ".moeicons/catalog.json", ".moeicons/manifest.json"],
|
|
96
114
|
};
|
|
97
115
|
}
|
|
116
|
+
async function runPackageSetMetadataSync(context, deps, projectRoot, rawMetadata, options) {
|
|
117
|
+
const installed = parsePackageInstallMetadata(rawMetadata);
|
|
118
|
+
if (!installed)
|
|
119
|
+
throw new CliError("VALIDATION_ERROR", "package-set install metadata is invalid; run install first");
|
|
120
|
+
const tier = installed.candidate.tier;
|
|
121
|
+
if (options.tier && options.tier !== tier)
|
|
122
|
+
throw new CliError("VALIDATION_ERROR", "metadata refresh tier differs from the installed package-set");
|
|
123
|
+
const document = loadConfigDocument(projectRoot);
|
|
124
|
+
const bootstrap = validateConfigDocument(document, undefined, { lenientCatalog: true });
|
|
125
|
+
if (bootstrap.kind !== "ok")
|
|
126
|
+
throw new CliError("VALIDATION_ERROR", bootstrap.kind === "invalid" ? bootstrap.message : "project config is missing");
|
|
127
|
+
if (bootstrap.config.tier !== tier || bootstrap.config.target !== installed.target)
|
|
128
|
+
throw new CliError("VALIDATION_ERROR", "metadata refresh config differs from the installed package-set; run update");
|
|
129
|
+
const plannedConfigDocument = JSON.stringify(document);
|
|
130
|
+
const expectedPins = installed.packages
|
|
131
|
+
.map(({ resourceKey, packageSha256, dependencies }) => ({ resourceKey, packageSha256, dependencies: [...dependencies] }))
|
|
132
|
+
.sort((a, b) => a.resourceKey.localeCompare(b.resourceKey, "en"));
|
|
133
|
+
let packagePlan;
|
|
134
|
+
const writes = Object.create(null);
|
|
135
|
+
const resolveResourceKeys = async (candidate, loadVerifiedPackage) => {
|
|
136
|
+
const identity = candidate.root;
|
|
137
|
+
if (identity.fullVersion !== installed.candidate.fullVersion ||
|
|
138
|
+
identity.tier !== tier ||
|
|
139
|
+
identity.packageSetSha256 !== installed.candidate.packageSetSha256 ||
|
|
140
|
+
identity.sourceCommit !== installed.candidate.sourceCommit ||
|
|
141
|
+
identity.generatorCommit !== installed.candidate.generatorCommit ||
|
|
142
|
+
sha256(canonicalBytes(identity)) !== installed.candidate.candidateRootSha256)
|
|
143
|
+
throw new CliError("VALIDATION_ERROR", "published package-set identity differs from the installed pin; refusing metadata refresh");
|
|
144
|
+
packagePlan = await planConfigPackagesFromMetadata(bootstrap.config, candidate, loadVerifiedPackage);
|
|
145
|
+
const actualPins = packagePlan.packages
|
|
146
|
+
.map(({ resourceKey, packageSha256, dependencies }) => ({ resourceKey, packageSha256, dependencies: [...dependencies] }))
|
|
147
|
+
.sort((a, b) => a.resourceKey.localeCompare(b.resourceKey, "en"));
|
|
148
|
+
if (JSON.stringify(actualPins) !== JSON.stringify(expectedPins))
|
|
149
|
+
throw new CliError("VALIDATION_ERROR", "configured package selection differs from the installed candidate; run update before metadata refresh");
|
|
150
|
+
return packagePlan.resourceKeys.filter((key) => key.startsWith(`metadata/${tier}/`));
|
|
151
|
+
};
|
|
152
|
+
const consume = (row, files) => {
|
|
153
|
+
if (row.kind !== "metadata")
|
|
154
|
+
throw new CliError("VALIDATION_ERROR", "metadata refresh selected a non-metadata package");
|
|
155
|
+
const mapped = packageTargetFiles(row, files, bootstrap.config.target);
|
|
156
|
+
for (const [path, bytes] of Object.entries(mapped.files)) {
|
|
157
|
+
const installPath = `.moeicons/package-metadata/${path}`;
|
|
158
|
+
if (Object.hasOwn(writes, installPath))
|
|
159
|
+
throw new CliError("VALIDATION_ERROR", "metadata package paths overlap");
|
|
160
|
+
writes[installPath] = bytes;
|
|
161
|
+
}
|
|
162
|
+
return Promise.resolve();
|
|
163
|
+
};
|
|
164
|
+
const cacheDir = deps.free.cacheDir ?? join(homedir(), ".moeicons", "cache");
|
|
165
|
+
const downloadOptions = {
|
|
166
|
+
cacheDir,
|
|
167
|
+
fullVersion: installed.candidate.fullVersion,
|
|
168
|
+
expectedPackageSetSha256: installed.candidate.packageSetSha256,
|
|
169
|
+
resolveResourceKeys,
|
|
170
|
+
consume,
|
|
171
|
+
...(context.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: context.env.MOEICONS_API_BASE_URL } : {}),
|
|
172
|
+
...(deps.fetch ? { fetchFn: deps.fetch } : {}),
|
|
173
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
174
|
+
};
|
|
175
|
+
const report = tier === "free"
|
|
176
|
+
? await downloadFreeVersionPackages({
|
|
177
|
+
...downloadOptions,
|
|
178
|
+
...(deps.free.fixtureBaseUrl ? { fixtureOrigin: deps.free.fixtureBaseUrl } : {}),
|
|
179
|
+
})
|
|
180
|
+
: await downloadProVersionPackages({
|
|
181
|
+
...downloadOptions,
|
|
182
|
+
accessToken: await runAccessTokenUseCase(context, deps.auth),
|
|
183
|
+
});
|
|
184
|
+
if (!packagePlan)
|
|
185
|
+
throw new CliError("VALIDATION_ERROR", "metadata refresh did not produce a verified package plan");
|
|
186
|
+
const catalogJson = `${JSON.stringify(packagePlan.catalog, null, 2)}\n`;
|
|
187
|
+
const catalogSha256 = sha256Bytes(catalogJson);
|
|
188
|
+
writes[".moeicons/catalog.json"] = catalogJson;
|
|
189
|
+
const managedFiles = {
|
|
190
|
+
...installed.managedFiles,
|
|
191
|
+
...Object.fromEntries(Object.entries(writes).map(([path, content]) => [path, sha256Bytes(content)])),
|
|
192
|
+
};
|
|
193
|
+
const nextMetadata = {
|
|
194
|
+
...installed,
|
|
195
|
+
catalogSha256,
|
|
196
|
+
installedAt: context.now().toISOString(),
|
|
197
|
+
managedFiles,
|
|
198
|
+
};
|
|
199
|
+
writes[".moeicons/install-metadata.json"] = serializePackageInstallMetadata(nextMetadata);
|
|
200
|
+
await withProjectLock(projectRoot, "update", () => {
|
|
201
|
+
if (deps.fs.readFileSync(join(projectRoot, ".moeicons", "install-metadata.json"), "utf8") !== rawMetadata)
|
|
202
|
+
throw new CliError("VALIDATION_ERROR", "installed package-set changed during metadata refresh; retry");
|
|
203
|
+
if (JSON.stringify(loadConfigDocument(projectRoot)) !== plannedConfigDocument)
|
|
204
|
+
throw new CliError("VALIDATION_ERROR", "config changed during metadata refresh; retry");
|
|
205
|
+
executeManagedReconcile(projectRoot, writes, [], deps.fs);
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
projectRoot,
|
|
209
|
+
tier,
|
|
210
|
+
artifactVersion: installed.candidate.fullVersion,
|
|
211
|
+
metadataSha256: catalogSha256,
|
|
212
|
+
files: Object.keys(writes).sort((a, b) => a.localeCompare(b, "en")),
|
|
213
|
+
networkBytes: report.networkBytes + report.metadataNetworkBytes,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -264,10 +264,24 @@ export function proSignedDownloadOptions(context, deps = {}) {
|
|
|
264
264
|
export async function downloadProArtifact(context, auth, expected, deps = {}) {
|
|
265
265
|
const fetchFn = deps.fetch ?? fetch;
|
|
266
266
|
const descriptor = await fetchProDescriptor(context, auth, expected, { fetch: fetchFn });
|
|
267
|
+
const localFixture = descriptor.channel === "local-test" || resolveProDescriptorEndpoint(context.env).allowLoopback;
|
|
268
|
+
// CI FLOW RULE (S05): tier-wide resource bundles are historical schema only.
|
|
269
|
+
// Do not fall through to `descriptor.url` when selected packages are absent;
|
|
270
|
+
// that URL points at the full Pro code archive. New installs must resolve the
|
|
271
|
+
// small package directory and fetch only the configured group/tuple set.
|
|
272
|
+
if (!localFixture &&
|
|
273
|
+
descriptor.resources?.bundle) {
|
|
274
|
+
throw new CliError("VALIDATION_ERROR", "this release uses the retired full-library package format; install requires the small-package release protocol");
|
|
275
|
+
}
|
|
276
|
+
if (deps.selection?.config.downloadMode === "full" && !localFixture) {
|
|
277
|
+
throw new CliError("VALIDATION_ERROR", 'downloadMode="full" is disabled for published releases; resolve bounded style-group/target packages instead');
|
|
278
|
+
}
|
|
279
|
+
const localRangeFixture = descriptor.channel === "local-test" &&
|
|
280
|
+
resolveProDescriptorEndpoint(context.env).allowLoopback;
|
|
267
281
|
if (deps.selection &&
|
|
268
282
|
deps.selection.config.downloadMode !== "full" &&
|
|
269
283
|
descriptor.resources &&
|
|
270
|
-
|
|
284
|
+
localRangeFixture) {
|
|
271
285
|
const selected = await downloadProSelected(context, auth, descriptor, deps.selection, deps);
|
|
272
286
|
if (selected)
|
|
273
287
|
return {
|
|
@@ -278,7 +292,10 @@ export async function downloadProArtifact(context, auth, expected, deps = {}) {
|
|
|
278
292
|
};
|
|
279
293
|
}
|
|
280
294
|
if (deps.selection?.config.downloadMode === "icons")
|
|
281
|
-
throw new CliError("VALIDATION_ERROR", "this release/backend does not support selected
|
|
295
|
+
throw new CliError("VALIDATION_ERROR", "this release/backend does not support bounded selected packages; refusing a full-library fallback");
|
|
296
|
+
if (!localFixture) {
|
|
297
|
+
throw new CliError("VALIDATION_ERROR", "published Pro release has no supported bounded package directory; refusing the full-library fallback");
|
|
298
|
+
}
|
|
282
299
|
context.ui.note(deps.selection?.config.downloadMode === "full"
|
|
283
300
|
? "Download mode: full (explicitly configured)."
|
|
284
301
|
: "Release does not advertise selected downloads; auto uses the verified full archive. Set downloadMode=icons to require selected downloads.", context.signal);
|
|
@@ -13,20 +13,26 @@ export declare function runProInstallUseCase(context: CommandContext, deps: {
|
|
|
13
13
|
readonly totalBytes?: number;
|
|
14
14
|
}) => void;
|
|
15
15
|
}, expected: {
|
|
16
|
-
readonly version
|
|
17
|
-
readonly descriptorSha256
|
|
16
|
+
readonly version?: string;
|
|
17
|
+
readonly descriptorSha256?: string;
|
|
18
18
|
readonly target?: Target;
|
|
19
19
|
/** A-1b: allow a declared local-test candidate (never true for production). */
|
|
20
20
|
readonly allowLocalTest?: boolean;
|
|
21
21
|
}): Promise<{
|
|
22
22
|
readonly projectRoot: string;
|
|
23
23
|
readonly artifactVersion: string;
|
|
24
|
-
readonly descriptorSha256
|
|
24
|
+
readonly descriptorSha256?: string;
|
|
25
|
+
readonly candidateRootSha256?: string;
|
|
26
|
+
readonly packageSetSha256?: string;
|
|
25
27
|
readonly catalogSha256: string;
|
|
26
28
|
readonly artifactBytes: number;
|
|
27
|
-
readonly downloadMode: "icons" | "full";
|
|
29
|
+
readonly downloadMode: "icons" | "full" | "packages";
|
|
28
30
|
readonly downloadNotes: readonly string[];
|
|
29
31
|
readonly networkBytes?: number;
|
|
30
32
|
readonly selectedFiles?: number;
|
|
33
|
+
readonly packageCount?: number;
|
|
34
|
+
readonly cacheHits?: number;
|
|
35
|
+
readonly metadataCacheHits?: number;
|
|
36
|
+
readonly transferRetryCount?: number;
|
|
31
37
|
}>;
|
|
32
38
|
export {};
|
package/dist/core/pro-install.js
CHANGED
|
@@ -14,6 +14,8 @@ import { catalog as bundledCatalog, parseCatalog } from "../catalog/catalog.js";
|
|
|
14
14
|
import { resolveBitmapTuples, resolveConfiguredBitmapShards } from "./bitmap-shard-resolver.js";
|
|
15
15
|
import { mergeBitmapShardCacheManifest, readBitmapShardCacheManifest, writeBitmapShardCacheManifest, } from "./bitmap-shard-cache.js";
|
|
16
16
|
import { allowLocalTestFromEnv } from "./local-test-env.js";
|
|
17
|
+
import { runSmallPackageInstall } from "./small-package-install.js";
|
|
18
|
+
import { fetchProStablePackageSet } from "./pro-small-package-download.js";
|
|
17
19
|
function toCacheIo(fs_) {
|
|
18
20
|
return {
|
|
19
21
|
// Shard cache keys are nested several levels deep; the CacheIo contract
|
|
@@ -64,6 +66,51 @@ export async function runProInstallUseCase(context, deps, expected) {
|
|
|
64
66
|
if (target !== bootstrap.config.target) {
|
|
65
67
|
throw new CliError("VALIDATION_ERROR", `config target is "${bootstrap.config.target}" but install target is "${target}"; set target to "${target}" in moeicons.config before installing`);
|
|
66
68
|
}
|
|
69
|
+
if (expected.allowLocalTest !== true) {
|
|
70
|
+
const accessToken = await runAccessTokenUseCase(context, deps.auth);
|
|
71
|
+
const identity = await fetchProStablePackageSet({
|
|
72
|
+
accessToken,
|
|
73
|
+
...(context.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: context.env.MOEICONS_API_BASE_URL } : {}),
|
|
74
|
+
...(deps.fetch ? { fetchFn: deps.fetch } : {}),
|
|
75
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
76
|
+
});
|
|
77
|
+
if (expected.version && expected.version !== identity.fullVersion) {
|
|
78
|
+
throw new CliError("VALIDATION_ERROR", "published Pro package-set changed after version selection; retry the install");
|
|
79
|
+
}
|
|
80
|
+
const installed = await runSmallPackageInstall({
|
|
81
|
+
projectRoot: project.root,
|
|
82
|
+
fullVersion: identity.fullVersion,
|
|
83
|
+
expectedPackageSetSha256: identity.packageSetSha256,
|
|
84
|
+
...(context.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: context.env.MOEICONS_API_BASE_URL } : {}),
|
|
85
|
+
config: bootstrap.config,
|
|
86
|
+
plannedConfigDocument: JSON.stringify(document),
|
|
87
|
+
fs: deps.fs,
|
|
88
|
+
cacheDir: resolveCacheDir(context.env),
|
|
89
|
+
now: context.now,
|
|
90
|
+
proAccessToken: accessToken,
|
|
91
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
92
|
+
...(deps.fetch ? { fetchFn: deps.fetch } : {}),
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
projectRoot: project.root,
|
|
96
|
+
artifactVersion: installed.artifactVersion,
|
|
97
|
+
candidateRootSha256: installed.candidateRootSha256,
|
|
98
|
+
packageSetSha256: installed.packageSetSha256,
|
|
99
|
+
catalogSha256: installed.catalogSha256,
|
|
100
|
+
artifactBytes: installed.artifactBytes,
|
|
101
|
+
downloadMode: "packages",
|
|
102
|
+
downloadNotes: installed.fallbacks.map((fallback) => `Theme fallback: ${fallback}`),
|
|
103
|
+
networkBytes: installed.networkBytes + identity.networkBytes,
|
|
104
|
+
selectedFiles: installed.selectedFiles,
|
|
105
|
+
packageCount: installed.packageCount,
|
|
106
|
+
cacheHits: installed.cacheHits,
|
|
107
|
+
metadataCacheHits: installed.metadataCacheHits,
|
|
108
|
+
transferRetryCount: installed.transferRetryCount,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (!expected.version || !expected.descriptorSha256) {
|
|
112
|
+
throw new CliError("VALIDATION_ERROR", "local Pro test install requires an exact legacy fixture pin");
|
|
113
|
+
}
|
|
67
114
|
const downloaded = await downloadProArtifact(context, deps.auth, {
|
|
68
115
|
version: expected.version,
|
|
69
116
|
descriptorSha256: expected.descriptorSha256,
|
|
@@ -48,6 +48,8 @@ export declare function runProPredownloadUseCase(context: CommandContext, auth:
|
|
|
48
48
|
readonly fetch?: typeof fetch;
|
|
49
49
|
readonly allowedProHosts?: readonly string[];
|
|
50
50
|
readonly force?: boolean;
|
|
51
|
+
/** Explicitly isolate the retired aggregate archive path to test fixtures. */
|
|
52
|
+
readonly allowLocalTest?: boolean;
|
|
51
53
|
readonly onProgress?: (event: {
|
|
52
54
|
readonly downloadedBytes: number;
|
|
53
55
|
readonly totalBytes?: number;
|
|
@@ -65,11 +67,32 @@ export interface ProWarmupDeps {
|
|
|
65
67
|
readonly descriptorSha256: string;
|
|
66
68
|
readonly metadataSha256: string;
|
|
67
69
|
readonly catalogSha256: string;
|
|
70
|
+
/** Explicit test-fixture opt-in for the retired legacy shard route. */
|
|
71
|
+
readonly allowLocalTest?: boolean;
|
|
68
72
|
readonly fetch?: typeof fetch;
|
|
69
73
|
readonly allowedProHosts?: readonly string[];
|
|
70
74
|
readonly onPlan?: (plan: WarmupPlan) => void;
|
|
71
75
|
readonly onProgress?: (event: BitmapShardFetchProgress) => void;
|
|
72
76
|
}
|
|
77
|
+
export interface ProPackageWarmupPlan {
|
|
78
|
+
readonly version: string;
|
|
79
|
+
readonly packageSetSha256: string;
|
|
80
|
+
readonly groups: number;
|
|
81
|
+
readonly packages: number;
|
|
82
|
+
readonly compressedBytes: number;
|
|
83
|
+
}
|
|
84
|
+
export interface ProPackageWarmupResult extends ProPackageWarmupPlan {
|
|
85
|
+
readonly downloaded: number;
|
|
86
|
+
readonly reused: number;
|
|
87
|
+
readonly networkBytes: number;
|
|
88
|
+
readonly metadataNetworkBytes: number;
|
|
89
|
+
}
|
|
90
|
+
/** Warm the explicit default preview tuple (128-WebP) as separate package objects. */
|
|
91
|
+
export declare function runProPreviewPackageWarmupUseCase(context: CommandContext, auth: AuthUseCaseDependencies, deps?: {
|
|
92
|
+
readonly fetch?: typeof fetch;
|
|
93
|
+
readonly apiBaseUrl?: string;
|
|
94
|
+
readonly onPlan?: (plan: ProPackageWarmupPlan) => Promise<void> | void;
|
|
95
|
+
}): Promise<ProPackageWarmupResult>;
|
|
73
96
|
/**
|
|
74
97
|
* W2-A: after the Pro code + metadata are cached, warm the default bitmap shard
|
|
75
98
|
* set (every bitmap style group's 128/webp shard) into the global cache. The
|
|
@@ -14,6 +14,8 @@ import { parseCatalog } from "../catalog/catalog.js";
|
|
|
14
14
|
import { runAccessTokenUseCase } from "./auth.js";
|
|
15
15
|
import { PRO_DOWNLOAD_HOSTS } from "./pro-download.js";
|
|
16
16
|
import { resolveBitmapShardDescriptorEndpoint } from "./bitmap-shard-download.js";
|
|
17
|
+
import { downloadProStablePackages } from "./pro-small-package-download.js";
|
|
18
|
+
import { planSelectedPackages } from "./small-package-selection.js";
|
|
17
19
|
import { warmDefaultBitmapShards, } from "./bitmap-shard-cache.js";
|
|
18
20
|
function cacheDir(env) {
|
|
19
21
|
return env.MOEICONS_CACHE_DIR ?? join(homedir(), ".moeicons", "cache");
|
|
@@ -91,6 +93,9 @@ export async function proResourceState(context, auth, deps = {}) {
|
|
|
91
93
|
* login or from the home screen, without installing anything into a project.
|
|
92
94
|
*/
|
|
93
95
|
export async function runProPredownloadUseCase(context, auth, deps = {}) {
|
|
96
|
+
if (deps.allowLocalTest !== true) {
|
|
97
|
+
throw new CliError('VALIDATION_ERROR', 'aggregate Pro archive pre-download is retired; use package-set project installs or preview package warm-up');
|
|
98
|
+
}
|
|
94
99
|
const versions = await fetchLibraryVersions({ ...(deps.fetch ? { fetch: deps.fetch } : {}), signal: context.signal, env: context.env });
|
|
95
100
|
if (!versions.pro)
|
|
96
101
|
throw new CliError("NOT_FOUND", "no pro release is published");
|
|
@@ -136,6 +141,49 @@ export async function runProPredownloadUseCase(context, auth, deps = {}) {
|
|
|
136
141
|
metadataBytes,
|
|
137
142
|
};
|
|
138
143
|
}
|
|
144
|
+
/** Warm the explicit default preview tuple (128-WebP) as separate package objects. */
|
|
145
|
+
export async function runProPreviewPackageWarmupUseCase(context, auth, deps = {}) {
|
|
146
|
+
const accessToken = await runAccessTokenUseCase(context, auth);
|
|
147
|
+
const cache = cacheDir(context.env);
|
|
148
|
+
let plan;
|
|
149
|
+
const report = await downloadProStablePackages({
|
|
150
|
+
accessToken,
|
|
151
|
+
cacheDir: cache,
|
|
152
|
+
...(deps.apiBaseUrl ? { apiBaseUrl: deps.apiBaseUrl } : context.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: context.env.MOEICONS_API_BASE_URL } : {}),
|
|
153
|
+
...(deps.fetch ? { fetchFn: deps.fetch } : {}),
|
|
154
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
155
|
+
resolveResourceKeys: async (candidate) => {
|
|
156
|
+
const selected = candidate.rows.filter((row) => row.kind === 'bitmap' && row.tier === 'pro'
|
|
157
|
+
&& row.variant?.width === 128 && row.variant.height === 128 && row.variant.format === 'webp')
|
|
158
|
+
.sort((a, b) => a.resourceKey.localeCompare(b.resourceKey, 'en'));
|
|
159
|
+
if (selected.length === 0)
|
|
160
|
+
throw new CliError('NOT_FOUND', 'the current Pro package set has no 128-WebP preview packages');
|
|
161
|
+
const selectedRows = planSelectedPackages(candidate, selected.map((row) => row.resourceKey));
|
|
162
|
+
const compressedBytes = selectedRows.reduce((sum, row) => sum + row.compressedBytes, 0);
|
|
163
|
+
if (!Number.isSafeInteger(compressedBytes) || compressedBytes > 4 * 1024 * 1024 * 1024)
|
|
164
|
+
throw new CliError('VALIDATION_ERROR', 'Pro preview warm-up exceeds the 4 GiB package budget');
|
|
165
|
+
plan = {
|
|
166
|
+
version: candidate.root.fullVersion,
|
|
167
|
+
packageSetSha256: candidate.root.packageSetSha256,
|
|
168
|
+
groups: new Set(selected.map((row) => row.styleGroupId)).size,
|
|
169
|
+
packages: selectedRows.length,
|
|
170
|
+
compressedBytes,
|
|
171
|
+
};
|
|
172
|
+
await deps.onPlan?.(plan);
|
|
173
|
+
return selected.map((row) => row.resourceKey);
|
|
174
|
+
},
|
|
175
|
+
consume: () => Promise.resolve(),
|
|
176
|
+
});
|
|
177
|
+
if (!plan)
|
|
178
|
+
throw new CliError('VALIDATION_ERROR', 'Pro preview warm-up did not produce a candidate plan');
|
|
179
|
+
return {
|
|
180
|
+
...plan,
|
|
181
|
+
downloaded: report.packageCount - report.cacheHits,
|
|
182
|
+
reused: report.cacheHits,
|
|
183
|
+
networkBytes: report.networkBytes,
|
|
184
|
+
metadataNetworkBytes: report.metadataNetworkBytes,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
139
187
|
function cacheIo() {
|
|
140
188
|
return {
|
|
141
189
|
mkdirSync: (path) => mkdirSync(path, { recursive: true }),
|
|
@@ -154,6 +202,9 @@ function cacheIo() {
|
|
|
154
202
|
* second artifact download is needed.
|
|
155
203
|
*/
|
|
156
204
|
export async function runProResourceWarmupUseCase(context, auth, deps) {
|
|
205
|
+
if (deps.allowLocalTest !== true) {
|
|
206
|
+
throw new CliError('VALIDATION_ERROR', 'legacy Pro bitmap shard warm-up is retired; use package-set preview package warm-up');
|
|
207
|
+
}
|
|
157
208
|
const dir = cacheDir(context.env);
|
|
158
209
|
const metadataPath = metadataCachePath(dir, deps.version, deps.metadataSha256);
|
|
159
210
|
if (!existsSync(metadataPath)) {
|
|
@@ -14,6 +14,12 @@ import { homedir } from "node:os";
|
|
|
14
14
|
export async function downloadProSelected(context, auth, descriptor, selection, deps) {
|
|
15
15
|
const fetchFn = deps.fetch ?? fetch;
|
|
16
16
|
const endpointInfo = resolveProDescriptorEndpoint(context.env);
|
|
17
|
+
// CI FLOW RULE (S05): this implementation reads the retired tier-wide
|
|
18
|
+
// resource-index / `.bin` bundle. Keep it reachable only from an explicitly
|
|
19
|
+
// declared local-test descriptor served by a loopback endpoint; a descriptor
|
|
20
|
+
// label alone must never reopen the production reader.
|
|
21
|
+
if (!endpointInfo.allowLoopback || descriptor.channel !== "local-test")
|
|
22
|
+
throw new CliError("VALIDATION_ERROR", "legacy Pro resource-bundle downloads require a local-test descriptor and loopback endpoint");
|
|
17
23
|
const endpoint = new URL(endpointInfo.url.replace(/\/artifact-descriptor$/, "/resource-descriptor"));
|
|
18
24
|
if (endpoint.origin !== "https://api.moeicons.com" && !endpointInfo.allowLoopback)
|
|
19
25
|
throw new CliError("VALIDATION_ERROR", "resource descriptor endpoint must be the trusted API or a test loopback");
|
|
@@ -150,7 +156,11 @@ export async function downloadProSelected(context, auth, descriptor, selection,
|
|
|
150
156
|
catch (error) {
|
|
151
157
|
if (!expired(error) || attempt === 2)
|
|
152
158
|
throw error;
|
|
153
|
-
const renewed = await fetchProDescriptor(context, auth, {
|
|
159
|
+
const renewed = await fetchProDescriptor(context, auth, {
|
|
160
|
+
version: descriptor.version,
|
|
161
|
+
descriptorSha256: descriptor.descriptorSha256,
|
|
162
|
+
...(descriptor.channel === "local-test" ? { allowLocalTest: true } : {}),
|
|
163
|
+
}, deps);
|
|
154
164
|
if (renewed.sha256 !== descriptor.sha256 ||
|
|
155
165
|
!renewed.metadata ||
|
|
156
166
|
renewed.metadata.sha256 !== originalMetadata.sha256 ||
|
|
@@ -161,7 +171,7 @@ export async function downloadProSelected(context, auth, descriptor, selection,
|
|
|
161
171
|
}
|
|
162
172
|
if (!metadataBytes)
|
|
163
173
|
throw new CliError("NETWORK_ERROR", "metadata URL renewal failed");
|
|
164
|
-
const metadata = extractProMetadata(metadataBytes, descriptor.catalogSha256, descriptor.version);
|
|
174
|
+
const metadata = extractProMetadata(metadataBytes, descriptor.catalogSha256, descriptor.version, descriptor.channel === "local-test");
|
|
165
175
|
const catalog = parseCatalog(JSON.parse(metadata.catalogJson));
|
|
166
176
|
const strict = validateConfigDocument(selection.document, catalog);
|
|
167
177
|
if (strict.kind !== "ok")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type PackageRow } from './small-pack/candidate-set.cjs';
|
|
2
|
+
import { type CandidateIdentity, type SelectedCandidate } from './small-package-selection.js';
|
|
3
|
+
export interface ProPackageSetIdentity extends CandidateIdentity {
|
|
4
|
+
readonly schemaVersion: 1;
|
|
5
|
+
readonly tier: 'pro';
|
|
6
|
+
readonly candidateRootSha256: string;
|
|
7
|
+
readonly candidateRootBytes: number;
|
|
8
|
+
readonly updatedAt: string;
|
|
9
|
+
readonly networkBytes: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ProSmallPackageOptions {
|
|
12
|
+
readonly expected: CandidateIdentity & {
|
|
13
|
+
readonly tier: 'pro';
|
|
14
|
+
};
|
|
15
|
+
readonly resourceKeys?: readonly string[];
|
|
16
|
+
readonly resolveResourceKeys?: (candidate: SelectedCandidate, loadVerifiedPackage: (row: PackageRow) => Promise<Readonly<Record<string, Uint8Array>>>) => Promise<readonly string[]>;
|
|
17
|
+
readonly cacheDir: string;
|
|
18
|
+
readonly consume: (row: PackageRow, files: Readonly<Record<string, Uint8Array>>) => Promise<void>;
|
|
19
|
+
readonly accessToken: string;
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
readonly fetchFn?: typeof fetch;
|
|
22
|
+
readonly apiBaseUrl?: string;
|
|
23
|
+
readonly expectedRootSha256?: string;
|
|
24
|
+
readonly expectedRootBytes?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function fetchProPackageSetIdentity(options: {
|
|
27
|
+
readonly version: string;
|
|
28
|
+
readonly packageSetSha256?: string;
|
|
29
|
+
readonly accessToken: string;
|
|
30
|
+
readonly apiBaseUrl?: string;
|
|
31
|
+
readonly fetchFn?: typeof fetch;
|
|
32
|
+
readonly signal?: AbortSignal;
|
|
33
|
+
}): Promise<ProPackageSetIdentity>;
|
|
34
|
+
export declare function fetchProStablePackageSet(options: Omit<Parameters<typeof fetchProPackageSetIdentity>[0], 'version' | 'packageSetSha256'>): Promise<ProPackageSetIdentity>;
|
|
35
|
+
export declare function downloadProSelectedPackages(options: ProSmallPackageOptions): Promise<{
|
|
36
|
+
packageCount: number;
|
|
37
|
+
networkBytes: number;
|
|
38
|
+
cacheHits: number;
|
|
39
|
+
metadataNetworkBytes: number;
|
|
40
|
+
metadataCacheHits: number;
|
|
41
|
+
transferRetryCount: number;
|
|
42
|
+
}>;
|
|
43
|
+
export declare function downloadProStablePackages(options: Omit<ProSmallPackageOptions, 'expected' | 'expectedRootSha256' | 'expectedRootBytes'>): Promise<{
|
|
44
|
+
packageCount: number;
|
|
45
|
+
networkBytes: number;
|
|
46
|
+
cacheHits: number;
|
|
47
|
+
metadataNetworkBytes: number;
|
|
48
|
+
metadataCacheHits: number;
|
|
49
|
+
transferRetryCount: number;
|
|
50
|
+
}>;
|
|
51
|
+
export declare function downloadProVersionPackages(options: Omit<ProSmallPackageOptions, 'expected' | 'expectedRootSha256' | 'expectedRootBytes'> & {
|
|
52
|
+
readonly fullVersion: string;
|
|
53
|
+
readonly expectedPackageSetSha256: string;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
packageCount: number;
|
|
56
|
+
networkBytes: number;
|
|
57
|
+
cacheHits: number;
|
|
58
|
+
metadataNetworkBytes: number;
|
|
59
|
+
metadataCacheHits: number;
|
|
60
|
+
transferRetryCount: number;
|
|
61
|
+
}>;
|