@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.
Files changed (56) hide show
  1. package/README.md +27 -19
  2. package/dist/cli.js +124 -146
  3. package/dist/config-package/generated/SOURCE.json +3 -3
  4. package/dist/config-package/generated/moeicons-config.schema.json +1 -1
  5. package/dist/core/bitmap-shard-budget.d.ts +2 -2
  6. package/dist/core/bitmap-shard-budget.js +1 -1
  7. package/dist/core/bootstrap.js +5 -3
  8. package/dist/core/candidate-version-pointer.d.ts +13 -0
  9. package/dist/core/candidate-version-pointer.js +37 -0
  10. package/dist/core/config-package-plan.d.ts +25 -0
  11. package/dist/core/config-package-plan.js +156 -0
  12. package/dist/core/doctor.js +4 -0
  13. package/dist/core/free-download.js +34 -4
  14. package/dist/core/free-small-package-download.d.ts +67 -0
  15. package/dist/core/free-small-package-download.js +344 -0
  16. package/dist/core/generate.js +49 -33
  17. package/dist/core/install.d.ts +8 -2
  18. package/dist/core/install.js +70 -0
  19. package/dist/core/library-update.d.ts +6 -1
  20. package/dist/core/library-update.js +54 -0
  21. package/dist/core/manage.d.ts +4 -0
  22. package/dist/core/manage.js +4 -0
  23. package/dist/core/metadata-sync.js +119 -1
  24. package/dist/core/pro-download.js +19 -2
  25. package/dist/core/pro-install.d.ts +10 -4
  26. package/dist/core/pro-install.js +47 -0
  27. package/dist/core/pro-resources.d.ts +23 -0
  28. package/dist/core/pro-resources.js +51 -0
  29. package/dist/core/pro-selected-download.js +12 -2
  30. package/dist/core/pro-small-package-download.d.ts +61 -0
  31. package/dist/core/pro-small-package-download.js +301 -0
  32. package/dist/core/release-descriptor.js +6 -2
  33. package/dist/core/selected-resources.js +2 -2
  34. package/dist/core/small-pack/PIN.json +11 -0
  35. package/dist/core/small-pack/candidate-set-v1.schema.json +187 -0
  36. package/dist/core/small-pack/candidate-set.cjs +286 -0
  37. package/dist/core/small-pack/candidate-set.d.cts +12 -0
  38. package/dist/core/small-pack/tar-read.cjs +220 -0
  39. package/dist/core/small-pack/tar-read.d.cts +2 -0
  40. package/dist/core/small-package-install.d.ts +40 -0
  41. package/dist/core/small-package-install.js +252 -0
  42. package/dist/core/small-package-selection.d.ts +27 -0
  43. package/dist/core/small-package-selection.js +114 -0
  44. package/dist/core/update-policy.d.ts +1 -0
  45. package/dist/core/update-policy.js +10 -3
  46. package/dist/mcp/services.js +1 -1
  47. package/dist/project/config.js +1 -1
  48. package/dist/project/install-metadata.d.ts +2 -1
  49. package/dist/project/install-metadata.js +6 -3
  50. package/dist/project/install.d.ts +11 -2
  51. package/dist/project/install.js +104 -12
  52. package/dist/project/package-install-metadata.d.ts +31 -0
  53. package/dist/project/package-install-metadata.js +99 -0
  54. package/dist/project/tar-gz.d.ts +4 -8
  55. package/dist/project/tar-gz.js +6 -10
  56. package/package.json +11 -11
@@ -0,0 +1,301 @@
1
+ import * as fs from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { cacheArtifact } from './cache.js';
4
+ import { downloadArtifact } from '../project/download.js';
5
+ import { parseCandidateSet, sha256 } from './small-pack/candidate-set.cjs';
6
+ import { readSelectedCandidate, consumeSelectedPackages, planSelectedPackages, verifySelectedPackage } from './small-package-selection.js';
7
+ const API_DEFAULT = 'https://api.moeicons.com';
8
+ const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:alpha|beta)(?:\.(?:0|[1-9]\d*))?)?$/;
9
+ const SHA = /^[a-f0-9]{64}$/;
10
+ const COMMIT = /^[a-f0-9]{40}$/;
11
+ function check(ok, message) { if (!ok)
12
+ throw new Error(`Pro package download: ${message}`); }
13
+ function readPrivateCache(file, maximum, exact) {
14
+ let handle;
15
+ try {
16
+ const entry = fs.lstatSync(file);
17
+ check(entry.isFile() && !entry.isSymbolicLink(), 'cached object must be a regular non-link file');
18
+ if (process.platform !== 'win32') {
19
+ check((entry.mode & 0o077) === 0 && (typeof process.getuid !== 'function' || entry.uid === process.getuid()), 'Pro cache permissions or owner are unsafe');
20
+ }
21
+ handle = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
22
+ }
23
+ catch (error) {
24
+ if (error.code === 'ENOENT')
25
+ return undefined;
26
+ throw error;
27
+ }
28
+ try {
29
+ const stat = fs.fstatSync(handle);
30
+ check(stat.isFile() && stat.size > 0 && stat.size <= maximum && (exact === undefined || stat.size === exact), 'cached object size/type invalid');
31
+ if (process.platform !== 'win32')
32
+ check((stat.mode & 0o077) === 0 && (typeof process.getuid !== 'function' || stat.uid === process.getuid()), 'Pro cache permissions or owner changed');
33
+ const bytes = Buffer.alloc(stat.size + 1);
34
+ let count = 0;
35
+ while (count < bytes.length) {
36
+ const read = fs.readSync(handle, bytes, count, bytes.length - count, count);
37
+ if (!read)
38
+ break;
39
+ count += read;
40
+ }
41
+ check(count === stat.size, 'cache changed during bounded read');
42
+ return bytes.subarray(0, count);
43
+ }
44
+ finally {
45
+ fs.closeSync(handle);
46
+ }
47
+ }
48
+ function privateCacheIo() {
49
+ return {
50
+ mkdirSync(path) {
51
+ fs.mkdirSync(path, { recursive: true, mode: 0o700 });
52
+ if (process.platform !== 'win32')
53
+ fs.chmodSync(path, 0o700);
54
+ },
55
+ writeFileSync(path, bytes) { fs.writeFileSync(path, bytes, { mode: 0o600 }); },
56
+ renameSync: fs.renameSync,
57
+ existsSync: fs.existsSync,
58
+ rmSync: fs.rmSync,
59
+ readFileSync: fs.readFileSync,
60
+ readdirSync: fs.readdirSync,
61
+ };
62
+ }
63
+ function resolveApiBase(value) {
64
+ const base = new URL(value ?? API_DEFAULT);
65
+ const loopback = base.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(base.hostname);
66
+ check((base.protocol === 'https:' || loopback) && !base.username && !base.password && !base.search && !base.hash && base.pathname === '/', 'API base must be HTTPS or loopback HTTP');
67
+ return base;
68
+ }
69
+ function authorizedFetch(fetchFn, apiBase, accessToken) {
70
+ check(typeof accessToken === 'string' && accessToken.length > 0 && accessToken.length <= 8192 && !/[\r\n]/.test(accessToken), 'authenticated Pro access token required');
71
+ return async (input, init) => {
72
+ const address = input instanceof Request ? input.url : input instanceof URL ? input.href : String(input);
73
+ if (new URL(address).origin !== apiBase.origin)
74
+ throw new Error('Pro package download: refusing to send credentials outside the configured API origin');
75
+ const headers = new Headers(input instanceof Request ? input.headers : undefined);
76
+ new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
77
+ headers.set('Authorization', `Bearer ${accessToken}`);
78
+ return await fetchFn(input, { ...init, headers });
79
+ };
80
+ }
81
+ function identityUrl(apiBase, version, packageSetSha256) {
82
+ const url = new URL('/v1/icon-library/pro/package-set', apiBase);
83
+ url.searchParams.set('version', version);
84
+ if (packageSetSha256)
85
+ url.searchParams.set('packageSetSha256', packageSetSha256);
86
+ return url.href;
87
+ }
88
+ function assetUrl(apiBase, identity, path) {
89
+ const url = new URL(`/v1/icon-library/pro/package-set/${path}`, apiBase);
90
+ url.searchParams.set('version', identity.fullVersion);
91
+ url.searchParams.set('packageSetSha256', identity.packageSetSha256);
92
+ return url.href;
93
+ }
94
+ export async function fetchProPackageSetIdentity(options) {
95
+ check(options.version === 'stable' || VERSION.test(options.version), 'requested Pro version is invalid');
96
+ if (options.version === 'stable')
97
+ check(options.packageSetSha256 === undefined, 'stable lookup cannot include a package-set digest');
98
+ else
99
+ check(SHA.test(options.packageSetSha256 ?? ''), 'exact Pro version lookup requires a package-set digest');
100
+ const apiBase = resolveApiBase(options.apiBaseUrl);
101
+ const fetchFn = authorizedFetch(options.fetchFn ?? globalThis.fetch, apiBase, options.accessToken);
102
+ const result = await downloadArtifact(identityUrl(apiBase, options.version, options.packageSetSha256), {
103
+ maxBytes: 16 * 1024,
104
+ timeoutMs: 120000,
105
+ maxRedirects: 0,
106
+ allowedHosts: [apiBase.host],
107
+ ...(apiBase.protocol === 'http:' ? { allowHttpLoopback: true } : {}),
108
+ userAgent: 'moeicons/small-package',
109
+ }, { fetchFn, ...(options.signal ? { signal: options.signal } : {}) });
110
+ if (!result.ok)
111
+ throw new Error(`Pro package-set identity: ${result.code}: ${result.message}`);
112
+ let raw;
113
+ try {
114
+ raw = JSON.parse(Buffer.from(result.bytes).toString('utf8'));
115
+ }
116
+ catch {
117
+ throw new Error('Pro package-set identity: invalid JSON');
118
+ }
119
+ check(raw !== null && typeof raw === 'object' && !Array.isArray(raw), 'identity object required');
120
+ const value = raw;
121
+ const fields = ['schemaVersion', 'tier', 'fullVersion', 'sourceCommit', 'generatorCommit', 'packageSetSha256', 'candidateRootSha256', 'candidateRootBytes', 'updatedAt'];
122
+ check(Object.keys(value).sort().join(',') === [...fields].sort().join(','), 'identity fields differ from the Pro package-set contract');
123
+ check(value.schemaVersion === 1 && value.tier === 'pro' && typeof value.fullVersion === 'string' && VERSION.test(value.fullVersion), 'Pro version identity invalid');
124
+ check(options.version === 'stable' || value.fullVersion === options.version, 'identity version differs from request');
125
+ check(typeof value.sourceCommit === 'string' && COMMIT.test(value.sourceCommit), 'Source commit invalid');
126
+ check(typeof value.generatorCommit === 'string' && COMMIT.test(value.generatorCommit), 'Generator commit invalid');
127
+ check(typeof value.packageSetSha256 === 'string' && SHA.test(value.packageSetSha256), 'package-set digest invalid');
128
+ check(options.packageSetSha256 === undefined || value.packageSetSha256 === options.packageSetSha256, 'identity package-set digest differs from request');
129
+ check(typeof value.candidateRootSha256 === 'string' && SHA.test(value.candidateRootSha256), 'candidate root digest invalid');
130
+ check(Number.isSafeInteger(value.candidateRootBytes) && Number(value.candidateRootBytes) > 0 && Number(value.candidateRootBytes) <= 1024 ** 2, 'candidate root size invalid');
131
+ check(typeof value.updatedAt === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(value.updatedAt) && !Number.isNaN(Date.parse(value.updatedAt)), 'identity timestamp invalid');
132
+ return { ...value, networkBytes: result.bytes.length };
133
+ }
134
+ export async function fetchProStablePackageSet(options) {
135
+ return fetchProPackageSetIdentity({ ...options, version: 'stable' });
136
+ }
137
+ export async function downloadProSelectedPackages(options) {
138
+ options.signal?.throwIfAborted();
139
+ check((options.resourceKeys !== undefined) !== (options.resolveResourceKeys !== undefined), 'provide exactly one explicit selection or candidate-based planner');
140
+ if (options.resourceKeys)
141
+ check(options.resourceKeys.length > 0 && new Set(options.resourceKeys).size === options.resourceKeys.length, 'explicit unique resource selection required');
142
+ const expected = options.expected;
143
+ check(expected.tier === 'pro' && SHA.test(expected.packageSetSha256) && COMMIT.test(expected.sourceCommit) && COMMIT.test(expected.generatorCommit) && VERSION.test(expected.fullVersion), 'exact Pro candidate pin required');
144
+ if (options.expectedRootSha256 !== undefined)
145
+ check(SHA.test(options.expectedRootSha256), 'candidate root pin must be lowercase SHA-256');
146
+ if (options.expectedRootBytes !== undefined)
147
+ check(Number.isSafeInteger(options.expectedRootBytes) && options.expectedRootBytes > 0 && options.expectedRootBytes <= 1024 ** 2, 'candidate root byte pin is invalid');
148
+ const apiBase = resolveApiBase(options.apiBaseUrl);
149
+ const fetchFn = authorizedFetch(options.fetchFn ?? globalThis.fetch, apiBase, options.accessToken);
150
+ let transferRetryCount = 0;
151
+ const fetchBytes = async (address, maximum) => {
152
+ for (let attempt = 0; attempt < 3; attempt++) {
153
+ options.signal?.throwIfAborted();
154
+ const result = await downloadArtifact(address, {
155
+ maxBytes: maximum,
156
+ timeoutMs: 120000,
157
+ maxRedirects: 0,
158
+ allowedHosts: [apiBase.host],
159
+ ...(apiBase.protocol === 'http:' ? { allowHttpLoopback: true } : {}),
160
+ userAgent: 'moeicons/small-package',
161
+ }, { fetchFn, ...(options.signal ? { signal: options.signal } : {}) });
162
+ if (result.ok)
163
+ return result.bytes;
164
+ const transient = result.code === 'NETWORK_ERROR' || result.code === 'HTTP_ERROR' && /\b(?:429|500|502|503|504)\b/.test(result.message);
165
+ if (!transient || attempt === 2)
166
+ throw new Error(`Pro package download: ${result.code}: ${result.message}`);
167
+ transferRetryCount++;
168
+ await new Promise((resolve, reject) => {
169
+ const aborted = () => { clearTimeout(timer); options.signal?.removeEventListener('abort', aborted); reject(new Error('Pro package download cancelled')); };
170
+ const timer = setTimeout(() => { options.signal?.removeEventListener('abort', aborted); resolve(); }, 250 * (attempt + 1));
171
+ options.signal?.addEventListener('abort', aborted, { once: true });
172
+ if (options.signal?.aborted)
173
+ aborted();
174
+ });
175
+ }
176
+ throw new Error('Pro package download retry budget exhausted');
177
+ };
178
+ const cache = join(options.cacheDir, 'small-pack-v1', 'pro');
179
+ const io = privateCacheIo();
180
+ const persist = (name, bytes, digest) => cacheArtifact(io, join(cache, name), bytes, digest);
181
+ const rootName = `candidate-set-${expected.packageSetSha256}.json`;
182
+ let metadataNetworkBytes = 0;
183
+ let metadataCacheHits = 0;
184
+ let rootBytes = readPrivateCache(join(cache, rootName), 1024 ** 2);
185
+ const rootCached = rootBytes !== undefined;
186
+ if (rootBytes)
187
+ metadataCacheHits++;
188
+ else {
189
+ rootBytes = await fetchBytes(assetUrl(apiBase, expected, 'root'), 1024 ** 2);
190
+ metadataNetworkBytes += rootBytes.length;
191
+ }
192
+ if (options.expectedRootSha256)
193
+ check(sha256(rootBytes) === options.expectedRootSha256, 'candidate root differs from authenticated identity');
194
+ if (options.expectedRootBytes !== undefined)
195
+ check(rootBytes.length === options.expectedRootBytes, 'candidate root size differs from authenticated identity');
196
+ const root = parseCandidateSet(rootBytes);
197
+ for (const key of ['fullVersion', 'tier', 'sourceCommit', 'generatorCommit', 'packageSetSha256'])
198
+ check(root[key] === expected[key], 'root differs from authenticated Pro identity');
199
+ const pending = [];
200
+ const candidate = await readSelectedCandidate(rootBytes, expected, async (ref) => {
201
+ const name = `page-${ref.sha256}.json`;
202
+ let bytes = readPrivateCache(join(cache, name), ref.sizeBytes, ref.sizeBytes);
203
+ if (bytes)
204
+ metadataCacheHits++;
205
+ else {
206
+ const index = root.packagePages.findIndex(item => item.filename === ref.filename && item.sha256 === ref.sha256) + 1;
207
+ check(index > 0, 'page reference is outside the verified root');
208
+ bytes = await fetchBytes(assetUrl(apiBase, expected, `pages/${index}`), ref.sizeBytes);
209
+ metadataNetworkBytes += bytes.length;
210
+ pending.push({ name, bytes, digest: ref.sha256 });
211
+ }
212
+ return bytes;
213
+ });
214
+ if (!rootCached)
215
+ persist(rootName, rootBytes, sha256(rootBytes));
216
+ for (const page of pending)
217
+ persist(page.name, page.bytes, page.digest);
218
+ let bootstrapNetworkBytes = 0;
219
+ const prefetched = new Map();
220
+ const loadVerifiedPackage = async (row) => {
221
+ const trusted = candidate.rows.find(item => item.resourceKey === row.resourceKey);
222
+ check(trusted && trusted.packageSha256 === row.packageSha256 && trusted.compressedBytes === row.compressedBytes, 'bootstrap package is outside the exact candidate rows');
223
+ const path = join(cache, `${row.packageSha256}.tgz`);
224
+ const cached = readPrivateCache(path, row.compressedBytes, row.compressedBytes);
225
+ const bytes = cached ?? await fetchBytes(assetUrl(apiBase, expected, `packages/${row.packageSha256}.tgz`), row.compressedBytes);
226
+ const files = verifySelectedPackage(trusted, bytes);
227
+ if (!cached) {
228
+ bootstrapNetworkBytes += bytes.length;
229
+ persist(`${row.packageSha256}.tgz`, bytes, row.packageSha256);
230
+ }
231
+ prefetched.set(row.packageSha256, { bytes, network: !cached });
232
+ return files;
233
+ };
234
+ const resourceKeys = options.resolveResourceKeys
235
+ ? await options.resolveResourceKeys(candidate, loadVerifiedPackage)
236
+ : options.resourceKeys;
237
+ check(resourceKeys && resourceKeys.length > 0 && new Set(resourceKeys).size === resourceKeys.length, 'candidate planner must return explicit unique resource keys');
238
+ const selectedRows = planSelectedPackages(candidate, resourceKeys);
239
+ const prefetchedNetworkDigests = new Set([...prefetched].filter(([, value]) => value.network).map(([digest]) => digest));
240
+ const report = await consumeSelectedPackages(candidate, resourceKeys, {
241
+ readCache: row => Promise.resolve(prefetched.get(row.packageSha256)?.bytes ?? readPrivateCache(join(cache, `${row.packageSha256}.tgz`), row.compressedBytes, row.compressedBytes)),
242
+ writeCache: (row, bytes) => { persist(`${row.packageSha256}.tgz`, bytes, row.packageSha256); return Promise.resolve(); },
243
+ download: row => fetchBytes(assetUrl(apiBase, expected, `packages/${row.packageSha256}.tgz`), row.compressedBytes),
244
+ consume: options.consume,
245
+ ...(options.signal ? { signal: options.signal } : {}),
246
+ });
247
+ const prefetchedSelectedNetworkCount = selectedRows.filter(row => prefetchedNetworkDigests.has(row.packageSha256)).length;
248
+ return {
249
+ ...report,
250
+ networkBytes: report.networkBytes + bootstrapNetworkBytes,
251
+ cacheHits: report.cacheHits - prefetchedSelectedNetworkCount,
252
+ metadataNetworkBytes,
253
+ metadataCacheHits,
254
+ transferRetryCount,
255
+ };
256
+ }
257
+ export async function downloadProStablePackages(options) {
258
+ const identity = await fetchProStablePackageSet({
259
+ accessToken: options.accessToken,
260
+ ...(options.apiBaseUrl ? { apiBaseUrl: options.apiBaseUrl } : {}),
261
+ ...(options.fetchFn ? { fetchFn: options.fetchFn } : {}),
262
+ ...(options.signal ? { signal: options.signal } : {}),
263
+ });
264
+ const { accessToken, apiBaseUrl, fetchFn, signal, ...rest } = options;
265
+ const report = await downloadProSelectedPackages({
266
+ ...rest,
267
+ accessToken,
268
+ ...(apiBaseUrl ? { apiBaseUrl } : {}),
269
+ ...(fetchFn ? { fetchFn } : {}),
270
+ ...(signal ? { signal } : {}),
271
+ expected: identity,
272
+ expectedRootSha256: identity.candidateRootSha256,
273
+ expectedRootBytes: identity.candidateRootBytes,
274
+ });
275
+ return { ...report, metadataNetworkBytes: report.metadataNetworkBytes + identity.networkBytes };
276
+ }
277
+ export async function downloadProVersionPackages(options) {
278
+ check(VERSION.test(options.fullVersion) && SHA.test(options.expectedPackageSetSha256), 'exact Pro version and package-set pin required');
279
+ const identity = await fetchProPackageSetIdentity({
280
+ version: options.fullVersion,
281
+ packageSetSha256: options.expectedPackageSetSha256,
282
+ accessToken: options.accessToken,
283
+ ...(options.apiBaseUrl ? { apiBaseUrl: options.apiBaseUrl } : {}),
284
+ ...(options.fetchFn ? { fetchFn: options.fetchFn } : {}),
285
+ ...(options.signal ? { signal: options.signal } : {}),
286
+ });
287
+ const report = await downloadProSelectedPackages({
288
+ cacheDir: options.cacheDir,
289
+ ...(options.resourceKeys ? { resourceKeys: options.resourceKeys } : {}),
290
+ ...(options.resolveResourceKeys ? { resolveResourceKeys: options.resolveResourceKeys } : {}),
291
+ consume: options.consume,
292
+ accessToken: options.accessToken,
293
+ ...(options.apiBaseUrl ? { apiBaseUrl: options.apiBaseUrl } : {}),
294
+ ...(options.fetchFn ? { fetchFn: options.fetchFn } : {}),
295
+ ...(options.signal ? { signal: options.signal } : {}),
296
+ expected: identity,
297
+ expectedRootSha256: identity.candidateRootSha256,
298
+ expectedRootBytes: identity.candidateRootBytes,
299
+ });
300
+ return { ...report, metadataNetworkBytes: report.metadataNetworkBytes + identity.networkBytes };
301
+ }
@@ -213,8 +213,12 @@ export const FREE_DOWNLOAD_HOSTS = [
213
213
  "release-assets.githubusercontent.com",
214
214
  ];
215
215
  export function githubReleaseAssetUrl(tag, filename) {
216
- if (!tag.startsWith("v"))
217
- throw new Error("release tag must be v<fullVersion>");
216
+ // Legacy Free releases use v<fullVersion>; the bounded package publisher
217
+ // uses immutable content-addressed/version-pointer tags as well.
218
+ const version = String.raw `(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:alpha|beta)(?:\.(?:0|[1-9]\d*))?)?`;
219
+ const validTag = new RegExp(`^(?:v${version}|candidate-set-v1-free-[a-f0-9]{64}|free-package-v1-[a-f0-9]{64}|candidate-version-v1-free-${version})$`);
220
+ if (!validTag.test(tag))
221
+ throw new Error("release tag is not a supported Moeicons release tag");
218
222
  return `https://github.com/${PUBLIC_FREE_REPO.owner}/${PUBLIC_FREE_REPO.name}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(filename)}`;
219
223
  }
220
224
  export function cacheKey(fullVersion, sha256) {
@@ -169,8 +169,8 @@ export function planSelectedResources(config, catalog, index) {
169
169
  const paths = [...chosen].sort();
170
170
  const payloadBytes = paths.reduce((sum, p) => sum + index.files[p].compressedSize, 0);
171
171
  const expandedBytes = paths.reduce((sum, p) => sum + index.files[p].size, 0);
172
- if (paths.length > 20000 || expandedBytes > 512 * 1024 * 1024 || payloadBytes > 512 * 1024 * 1024)
173
- return fail("selected resources exceed 20,000 files/512 MiB budget; reduce icons/themes or explicitly use downloadMode=full");
172
+ if (paths.length > 20000 || expandedBytes > 768 * 1024 * 1024 || payloadBytes > 768 * 1024 * 1024)
173
+ return fail("selected resources exceed 20,000 files/768 MiB budget; reduce icons/themes or explicitly use downloadMode=full");
174
174
  return { paths, payloadBytes, expandedBytes, fallbacks: [...fallbacks].sort() };
175
175
  }
176
176
  export function verifyResource(bytes, entry, name) {
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "sourceCommit": "24d7e9ea5d4ac7c338ee08995ebf09b485950092",
4
+ "files": {
5
+ "candidate-set.cjs": "eeb3882a77e2ad29f73a2d741f140750715e9f4a5700ab338b5716013b862732",
6
+ "tar-read.cjs": "053cd19f14d2a779b9e964e497129aac6a0226d3766797b1f281e230ab41ad49"
7
+ },
8
+ "contracts": {
9
+ "candidate-set-v1.schema.json": "8e0aac41672fd9929e89f669487aaa6c100ea9460eaeb1935542e8393c6139fc"
10
+ }
11
+ }
@@ -0,0 +1,187 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/moewolf-dev/moe-icons-code-library/main/schemas/small-pack-candidate-set-v1.schema.json",
4
+ "title": "Moe Icons small-package candidate-set v1",
5
+ "description": "Wire shape and per-object budgets for a tier-isolated package-set root or package page. Canonical bytes, digest equality, sorted order, cross-page uniqueness, dependency closure, and package contents are enforced by the executable candidate-set validator.",
6
+ "oneOf": [
7
+ { "$ref": "#/$defs/candidateRoot" },
8
+ { "$ref": "#/$defs/packagePage" }
9
+ ],
10
+ "$defs": {
11
+ "sha256": {
12
+ "type": "string",
13
+ "pattern": "^[a-f0-9]{64}$"
14
+ },
15
+ "commit": {
16
+ "type": "string",
17
+ "pattern": "^[a-f0-9]{40}$"
18
+ },
19
+ "version": {
20
+ "type": "string",
21
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-(?:alpha|beta)(?:\\.(0|[1-9][0-9]*))?)?$"
22
+ },
23
+ "tier": {
24
+ "enum": ["free", "pro"]
25
+ },
26
+ "styleGroupId": {
27
+ "type": "string",
28
+ "pattern": "^moe-[a-z0-9]+(?:-[a-z0-9]+)*$"
29
+ },
30
+ "target": {
31
+ "enum": ["react", "vue", "vanilla"]
32
+ },
33
+ "candidateRoot": {
34
+ "type": "object",
35
+ "required": [
36
+ "schemaVersion",
37
+ "fullVersion",
38
+ "tier",
39
+ "sourceCommit",
40
+ "generatorCommit",
41
+ "packagePageCount",
42
+ "packagePages",
43
+ "packageSetSha256"
44
+ ],
45
+ "properties": {
46
+ "schemaVersion": { "const": 1 },
47
+ "fullVersion": { "$ref": "#/$defs/version" },
48
+ "tier": { "$ref": "#/$defs/tier" },
49
+ "sourceCommit": { "$ref": "#/$defs/commit" },
50
+ "generatorCommit": { "$ref": "#/$defs/commit" },
51
+ "packagePageCount": { "type": "integer", "minimum": 1, "maximum": 128 },
52
+ "packagePages": {
53
+ "type": "array",
54
+ "minItems": 1,
55
+ "maxItems": 128,
56
+ "items": { "$ref": "#/$defs/pageReference" }
57
+ },
58
+ "packageSetSha256": { "$ref": "#/$defs/sha256" }
59
+ },
60
+ "additionalProperties": false
61
+ },
62
+ "pageReference": {
63
+ "type": "object",
64
+ "required": ["filename", "sha256", "sizeBytes", "packageCount"],
65
+ "properties": {
66
+ "filename": { "type": "string", "pattern": "^package-page-(?:free|pro)-[0-9]{4}\\.json$" },
67
+ "sha256": { "$ref": "#/$defs/sha256" },
68
+ "sizeBytes": { "type": "integer", "minimum": 1, "maximum": 8388608 },
69
+ "packageCount": { "type": "integer", "minimum": 1, "maximum": 20000 }
70
+ },
71
+ "additionalProperties": false
72
+ },
73
+ "packagePage": {
74
+ "type": "object",
75
+ "required": ["schemaVersion", "fullVersion", "tier", "packages"],
76
+ "properties": {
77
+ "schemaVersion": { "const": 1 },
78
+ "fullVersion": { "$ref": "#/$defs/version" },
79
+ "tier": { "$ref": "#/$defs/tier" },
80
+ "packages": {
81
+ "type": "array",
82
+ "minItems": 1,
83
+ "maxItems": 20000,
84
+ "items": { "$ref": "#/$defs/packageRow" }
85
+ }
86
+ },
87
+ "additionalProperties": false
88
+ },
89
+ "packageRow": {
90
+ "type": "object",
91
+ "required": [
92
+ "packageSha256",
93
+ "filename",
94
+ "kind",
95
+ "tier",
96
+ "resourceKey",
97
+ "fullVersion",
98
+ "compressedBytes",
99
+ "expandedBytes",
100
+ "fileCount",
101
+ "dependencies"
102
+ ],
103
+ "properties": {
104
+ "packageSha256": { "$ref": "#/$defs/sha256" },
105
+ "filename": { "type": "string", "pattern": "^packages/[a-f0-9]{64}\\.tgz$" },
106
+ "kind": { "enum": ["bitmap", "svg", "code", "shared", "metadata", "website"] },
107
+ "tier": { "$ref": "#/$defs/tier" },
108
+ "resourceKey": { "type": "string", "minLength": 1, "maxLength": 256 },
109
+ "fullVersion": { "$ref": "#/$defs/version" },
110
+ "compressedBytes": { "type": "integer", "minimum": 1, "maximum": 134217728 },
111
+ "expandedBytes": { "type": "integer", "minimum": 1, "maximum": 134217728 },
112
+ "fileCount": { "type": "integer", "minimum": 1, "maximum": 100000 },
113
+ "dependencies": {
114
+ "type": "array",
115
+ "uniqueItems": true,
116
+ "items": { "$ref": "#/$defs/sha256" }
117
+ },
118
+ "styleGroupId": { "$ref": "#/$defs/styleGroupId" },
119
+ "target": { "$ref": "#/$defs/target" },
120
+ "variant": {
121
+ "type": "object",
122
+ "required": ["width", "height", "format"],
123
+ "properties": {
124
+ "width": { "type": "integer", "minimum": 1, "maximum": 4096 },
125
+ "height": { "type": "integer", "minimum": 1, "maximum": 4096 },
126
+ "format": { "enum": ["png", "webp"] }
127
+ },
128
+ "additionalProperties": false
129
+ },
130
+ "inputSha256": { "$ref": "#/$defs/sha256" }
131
+ },
132
+ "allOf": [
133
+ {
134
+ "if": { "properties": { "kind": { "const": "bitmap" } }, "required": ["kind"] },
135
+ "then": {
136
+ "required": ["styleGroupId", "variant"],
137
+ "not": { "required": ["target"] },
138
+ "properties": {
139
+ "resourceKey": { "type": "string", "pattern": "^bitmap/(?:free|pro)/moe-[a-z0-9]+(?:-[a-z0-9]+)*/[1-9][0-9]*x[1-9][0-9]*/(?:png|webp)$" }
140
+ }
141
+ }
142
+ },
143
+ {
144
+ "if": { "properties": { "kind": { "const": "svg" } }, "required": ["kind"] },
145
+ "then": {
146
+ "required": ["styleGroupId"],
147
+ "not": { "anyOf": [{ "required": ["target"] }, { "required": ["variant"] }] },
148
+ "properties": { "compressedBytes": { "type": "integer", "maximum": 33554432 }, "expandedBytes": { "type": "integer", "maximum": 33554432 }, "resourceKey": { "type": "string", "pattern": "^svg/(?:free|pro)/moe-[a-z0-9]+(?:-[a-z0-9]+)*$" } }
149
+ }
150
+ },
151
+ {
152
+ "if": { "properties": { "kind": { "const": "code" } }, "required": ["kind"] },
153
+ "then": {
154
+ "required": ["styleGroupId", "target"],
155
+ "not": { "required": ["variant"] },
156
+ "properties": { "compressedBytes": { "type": "integer", "maximum": 33554432 }, "expandedBytes": { "type": "integer", "maximum": 33554432 }, "resourceKey": { "type": "string", "pattern": "^code/(?:free|pro)/(?:react|vue|vanilla)/moe-[a-z0-9]+(?:-[a-z0-9]+)*$" } }
157
+ }
158
+ },
159
+ {
160
+ "if": { "properties": { "kind": { "const": "shared" } }, "required": ["kind"] },
161
+ "then": {
162
+ "required": ["target"],
163
+ "not": { "anyOf": [{ "required": ["styleGroupId"] }, { "required": ["variant"] }] },
164
+ "properties": { "compressedBytes": { "type": "integer", "maximum": 33554432 }, "expandedBytes": { "type": "integer", "maximum": 33554432 }, "resourceKey": { "type": "string", "pattern": "^shared/(?:free|pro)/(?:react|vue|vanilla)$" } }
165
+ }
166
+ },
167
+ {
168
+ "if": { "properties": { "kind": { "const": "metadata" } }, "required": ["kind"] },
169
+ "then": {
170
+ "required": ["styleGroupId"],
171
+ "not": { "anyOf": [{ "required": ["target"] }, { "required": ["variant"] }] },
172
+ "properties": { "compressedBytes": { "type": "integer", "maximum": 8388608 }, "expandedBytes": { "type": "integer", "maximum": 8388608 }, "resourceKey": { "type": "string", "pattern": "^metadata/(?:free|pro)/moe-[a-z0-9]+(?:-[a-z0-9]+)*$" } }
173
+ }
174
+ },
175
+ {
176
+ "if": { "properties": { "kind": { "const": "website" } }, "required": ["kind"] },
177
+ "then": {
178
+ "required": ["styleGroupId"],
179
+ "not": { "anyOf": [{ "required": ["target"] }, { "required": ["variant"] }] },
180
+ "properties": { "compressedBytes": { "type": "integer", "maximum": 33554432 }, "expandedBytes": { "type": "integer", "maximum": 33554432 }, "resourceKey": { "type": "string", "pattern": "^website/(?:free|pro)/moe-[a-z0-9]+(?:-[a-z0-9]+)*$" } }
181
+ }
182
+ }
183
+ ],
184
+ "additionalProperties": false
185
+ }
186
+ }
187
+ }