@moewolf/moe-icons-cli 0.0.11 → 0.0.15
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/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/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
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@ moeicons generate generate target-specific project files
|
|
|
16
16
|
moeicons doctor --check check configuration and integration
|
|
17
17
|
moeicons recover recover an interrupted transaction
|
|
18
18
|
moeicons init create moeicons.config.jsonc
|
|
19
|
-
moeicons update metadata sync
|
|
20
|
-
moeicons update
|
|
19
|
+
moeicons update metadata sync metadata for supported legacy installs
|
|
20
|
+
moeicons update update the installed Free or Pro package set
|
|
21
21
|
moeicons mcp start the MCP stdio server
|
|
22
22
|
moeicons --version show version
|
|
23
23
|
moeicons --help show help
|
|
@@ -73,10 +73,12 @@ first, then run `moeicons install free --target <target>` and `moeicons generate
|
|
|
73
73
|
For React/Vue, installation now keeps only the configured SVG component modules
|
|
74
74
|
and their required files in `.moeicons/artifact`. After adding icon IDs to the
|
|
75
75
|
config, run `moeicons install` again before `moeicons generate`; generate reports
|
|
76
|
-
the missing installed component if this step is skipped.
|
|
77
|
-
selected
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
the missing installed component if this step is skipped. Published releases
|
|
77
|
+
must provide bounded selected packages. A legacy descriptor without the new
|
|
78
|
+
package-set contract is rejected; the CLI never downloads a complete tier
|
|
79
|
+
archive as a fallback. See Download modes and resource selection below.
|
|
80
|
+
In this branch, Free and Pro installs resolve their stable package-set identity, verify the root and pages, then download only the selected package closure before the transactional install. Pro requests use the logged-in access token; the Worker checks current entitlement on identity, root, page, and package requests. Pro package cache files are private to the current OS user. `moeicons update` compares the installed package-set identity with the tier's Worker stable pointer and pins the exact set before reconciling files. `moeicons update metadata` refreshes only the selected metadata packages for the exact installed package-set, verifies the installed selection still matches the project config, and transactionally restores the derived catalog without fetching code or icon packages. The global Pro preview warm-up also uses separately addressed packages from the current package set. These changes are local to this branch; the Worker and CLI have not been deployed or published.
|
|
81
|
+
|
|
80
82
|
Each theme may select a subset of the registered project icons:
|
|
81
83
|
|
|
82
84
|
```json
|
|
@@ -138,16 +140,21 @@ Installing or updating a tier writes `<project>/.moeicons/`:
|
|
|
138
140
|
└── artifact/<target>/
|
|
139
141
|
```
|
|
140
142
|
|
|
141
|
-
`moeicons update metadata`
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
`moeicons update metadata` refreshes metadata for a pinned package-set install
|
|
144
|
+
by reading only its selected scoped metadata packages. Legacy installs continue
|
|
145
|
+
to use their version-pinned metadata archive. Neither path changes the installed
|
|
146
|
+
code or resource package selection.
|
|
144
147
|
|
|
145
148
|
## Pro resources
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
Project-level Pro installation uses authenticated, selected package sets and
|
|
151
|
+
does not require pre-downloading the full Pro library. The separate global
|
|
152
|
+
`Warm default Pro previews` action downloads only the current package set's
|
|
153
|
+
separately addressed 128-WebP bitmap tuple packages, after showing their total
|
|
154
|
+
compressed size and asking for confirmation. It does not pre-download a Pro
|
|
155
|
+
code archive. Project installs continue to fetch only their configured package
|
|
156
|
+
closure. Logging out keeps cached objects but blocks new package-set installs,
|
|
157
|
+
updates, and Pro preview warm-ups.
|
|
151
158
|
|
|
152
159
|
## Security
|
|
153
160
|
|
|
@@ -179,7 +186,8 @@ out keeps cached archives but blocks new unauthenticated installs/updates.
|
|
|
179
186
|
|
|
180
187
|
Metadata distribution (`MANUAL-DIST`) implemented: builder-generated manual,
|
|
181
188
|
catalog and manifest, per-tier archives and descriptors, Free bootstrap,
|
|
182
|
-
metadata-only sync, and authenticated Pro
|
|
189
|
+
metadata-only sync, and authenticated Pro package-set install/update and
|
|
190
|
+
bounded preview warm-up.
|
|
183
191
|
|
|
184
192
|
### Download modes and resource selection
|
|
185
193
|
|
|
@@ -202,17 +210,17 @@ Set `downloadMode` in `moeicons.config.jsonc` (schemaVersion 3):
|
|
|
202
210
|
}
|
|
203
211
|
```
|
|
204
212
|
|
|
205
|
-
- `auto` (default):
|
|
206
|
-
- `icons`: require selected
|
|
207
|
-
- `full`:
|
|
213
|
+
- `auto` (default): resolve only the fixed release's selected package set. A legacy descriptor without that contract fails before resource transfer.
|
|
214
|
+
- `icons`: require selected packages. Unsupported releases, authorization failures, invalid package identities and bad digests stop the operation; they never trigger a full archive download.
|
|
215
|
+
- `full`: retained only for explicit local `-test` fixtures. Published releases reject this mode because tier-wide archives are retired.
|
|
208
216
|
|
|
209
217
|
`icons` registers the allowed icon IDs. Install/update requires at least one global icon ID; an empty global list stops before version queries or downloads and preserves the existing installation. An individual theme may still use `icons: []`. A theme's `icons` restricts that theme to a subset; omitting it selects every registered ID available in the style group, and `[]` selects none. Fallback chooses the requested theme, then the default theme, then theme names in ASCII order. An icon with no selected variant fails validation. Bitmap themes also select `format` and `imageSize`; changing these fields may require new resources.
|
|
210
218
|
|
|
211
|
-
|
|
219
|
+
For package-set installs, the CLI reads the Worker identity and candidate root/pages before planning resources, so validation uses that candidate's catalog. It shows the target, registered icon/theme counts, required file count and compressed payload budget before reading selected package objects. Verified cache entries reduce traffic. `--json` reports `downloadMode`, `downloadNotes`, `selectedFiles` and `networkBytes` (resource payload only; identity and metadata traffic is separate).
|
|
212
220
|
|
|
213
221
|
Run `moeicons install free` or `moeicons install pro` after changing the selected icons, themes, bitmap format or size, then `moeicons generate`. `moeicons update` also reconciles the configuration when the release version is unchanged. Missing resources name the affected path and ask for reinstall; stale resources are removed only if still owned and unmodified. A configuration change during download stops before project writes; retry with the final configuration.
|
|
214
222
|
|
|
215
|
-
Generation works offline from the project's verified installed resources; `moeicons recover` also needs no network.
|
|
223
|
+
Generation works offline from the project's verified installed resources; `moeicons recover` also needs no network. Package-set installs and updates pin a verified tier candidate; every Pro identity and payload read checks current entitlement. A resource cache hit does not bypass the authenticated stable identity check, and there is no offline install flag. Corrupt cache entries are rejected and fetched again online. Interrupted downloads retain only verified cache members; project files change in one recoverable transaction after all required resources have verified.
|
|
216
224
|
|
|
217
225
|
Version queries have a deadline covering both response headers and body, with bounded JSON sizes. Invalid or unavailable version responses make update fail explicitly. Cache cleanup preserves staging files owned by a live process; disk-full and permission failures report actionable errors.
|
|
218
226
|
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { createCommandUi } from "./ui/create-ui.js";
|
|
|
17
17
|
import { CLI_VERSION, MOEICONS_BANNER, renderNoticeBox, renderProjectNotice, renderWordmarkText, } from "./ui/banner.js";
|
|
18
18
|
import { createTheme, isThemeEnabled } from "./ui/theme.js";
|
|
19
19
|
import { renderHistoryBar } from "./tui/components.js";
|
|
20
|
-
import { describeAuthEnvironment, runAccountUseCase, runLoginUseCase, runLogoutUseCase, runRemoteAccountUseCase, runSessionStatusUseCase, } from "./core/auth.js";
|
|
20
|
+
import { describeAuthEnvironment, runAccountUseCase, runLoginUseCase, runLogoutUseCase, runRemoteAccountUseCase, runSessionStatusUseCase, runAccessTokenUseCase, } from "./core/auth.js";
|
|
21
21
|
import { formatLibraryVersionStatus, getLibraryVersionStatus } from "./core/manage.js";
|
|
22
22
|
import { runCliUpdateCheck } from "./core/cli-update.js";
|
|
23
23
|
import { fetchLibraryVersions } from "./core/version-service.js";
|
|
@@ -25,9 +25,11 @@ import { runProInstallUseCase } from "./core/pro-install.js";
|
|
|
25
25
|
import { resolveLocalTestContext } from "./core/local-test-env.js";
|
|
26
26
|
import { runLibraryUpdateUseCase } from "./core/library-update.js";
|
|
27
27
|
import { runMetadataSyncUseCase } from "./core/metadata-sync.js";
|
|
28
|
+
import { fetchFreeStablePackageSet } from "./core/free-small-package-download.js";
|
|
29
|
+
import { fetchProStablePackageSet } from "./core/pro-small-package-download.js";
|
|
30
|
+
import { decideUpdate } from "./core/update-policy.js";
|
|
28
31
|
import { runBootstrapUseCase } from "./core/bootstrap.js";
|
|
29
|
-
import {
|
|
30
|
-
import { proResourceState, runProPredownloadUseCase, runProResourceWarmupUseCase, } from "./core/pro-resources.js";
|
|
32
|
+
import { runProPreviewPackageWarmupUseCase } from "./core/pro-resources.js";
|
|
31
33
|
import { formatBytes } from "./metadata/version.js";
|
|
32
34
|
import { isLocalTestVersion } from "./core/release-descriptor.js";
|
|
33
35
|
import { buildInitPlan, checkRequiresFix, collectSafeFixes, doctorJson, formatDoctorReport, formatPlannedDiffs, plannedDiffsJson, runDoctorApply, runDoctorDiagnose, } from "./core/doctor.js";
|
|
@@ -91,6 +93,9 @@ function freeDownloadDeps(runtime) {
|
|
|
91
93
|
...(runtime.env.MOEICONS_FREE_RELEASE_DIR
|
|
92
94
|
? { fixtureDir: runtime.env.MOEICONS_FREE_RELEASE_DIR }
|
|
93
95
|
: {}),
|
|
96
|
+
...(runtime.env.MOEICONS_FREE_PACKAGE_SET_FIXTURE_ORIGIN
|
|
97
|
+
? { fixtureBaseUrl: runtime.env.MOEICONS_FREE_PACKAGE_SET_FIXTURE_ORIGIN }
|
|
98
|
+
: {}),
|
|
94
99
|
cacheDir: runtime.env.MOEICONS_CACHE_DIR ?? join(homedir(), ".moeicons", "cache"),
|
|
95
100
|
cliVersion: versionString(),
|
|
96
101
|
};
|
|
@@ -241,52 +246,36 @@ async function runLogin(runtime, json, yes) {
|
|
|
241
246
|
else
|
|
242
247
|
runtime.stdout(`Environment: ${environment}\nLogged in as ${session.accountId}\n`);
|
|
243
248
|
if (!json && runtime.isTTY())
|
|
244
|
-
await
|
|
249
|
+
await offerProPreviewWarmup(runtime);
|
|
245
250
|
return 0;
|
|
246
251
|
}
|
|
247
252
|
/**
|
|
248
|
-
* After
|
|
249
|
-
*
|
|
253
|
+
* After login, offer an explicit bounded warm-up of default Pro preview units.
|
|
254
|
+
* Cancelling or a failed probe never logs the user out.
|
|
250
255
|
*/
|
|
251
|
-
async function
|
|
256
|
+
async function offerProPreviewWarmup(runtime) {
|
|
252
257
|
const context = commandContext(runtime, { json: false, yes: false });
|
|
258
|
+
const progress = startResourceProgress(context, "Warming Pro preview packages");
|
|
253
259
|
try {
|
|
254
|
-
const
|
|
260
|
+
const result = await runProPreviewPackageWarmupUseCase(context, runtime.auth ?? {}, {
|
|
255
261
|
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
onPlan: async (plan) => {
|
|
263
|
+
progress.phase(`${plan.groups} preview groups · ${plan.packages} packages · ${formatBytes(plan.compressedBytes)}`);
|
|
264
|
+
const accepted = await context.ui.confirm(`Warm ${plan.groups} Pro preview groups (${formatBytes(plan.compressedBytes)} compressed package data)?`, context.signal);
|
|
265
|
+
if (accepted !== true)
|
|
266
|
+
throw new CliError("CANCELLED", "Pro preview warm-up cancelled");
|
|
267
|
+
},
|
|
258
268
|
});
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const accepted = await context.ui.confirm("Download Pro resources now? (code + metadata)", context.signal);
|
|
267
|
-
if (accepted !== true) {
|
|
268
|
-
runtime.stdout("Skipped Pro download; you can download it later from the home screen.\n");
|
|
269
|
+
progress.stop("Pro preview package warm-up complete");
|
|
270
|
+
runtime.stdout(`Warmed ${result.groups} Pro preview groups (${result.downloaded} downloaded, ${result.reused} cached).\n`);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
progress.stop("Pro preview warm-up stopped");
|
|
274
|
+
if (error instanceof CliError && error.code === "CANCELLED") {
|
|
275
|
+
runtime.stdout("Skipped Pro preview warm-up; project installs still fetch only configured packages.\n");
|
|
269
276
|
return;
|
|
270
277
|
}
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const result = await runProPredownloadUseCase(context, runtime.auth ?? {}, {
|
|
274
|
-
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
275
|
-
onProgress: ({ downloadedBytes, totalBytes }) => progress.bytes({ downloadedBytes, ...(totalBytes !== undefined ? { totalBytes } : {}) }),
|
|
276
|
-
});
|
|
277
|
-
const warmed = await warmProBitmapShards(runtime, result, runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}, progress);
|
|
278
|
-
progress.stop("Pro resources downloaded");
|
|
279
|
-
runtime.stdout(`Cached Pro ${result.version} code + metadata.\n`);
|
|
280
|
-
if (warmed)
|
|
281
|
-
runtime.stdout(`${warmed}\n`);
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
progress.stop("Pro download stopped");
|
|
285
|
-
runtime.stderr(`warning: Pro download failed (${error instanceof Error ? error.message : String(error)}). You can retry from the home screen.\n`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
// A failed probe must not make login fail.
|
|
278
|
+
runtime.stderr(`warning: Pro preview warm-up skipped (${error instanceof Error ? error.message : String(error)}).\n`);
|
|
290
279
|
}
|
|
291
280
|
}
|
|
292
281
|
/**
|
|
@@ -313,84 +302,34 @@ function startResourceProgress(context, label) {
|
|
|
313
302
|
stop: (message) => legacy.stop(message),
|
|
314
303
|
};
|
|
315
304
|
}
|
|
316
|
-
/**
|
|
317
|
-
* W2-A: best-effort warm-up of the default bitmap shard set after the Pro code
|
|
318
|
-
* and metadata are cached. Never fails the surrounding download.
|
|
319
|
-
*/
|
|
320
|
-
async function warmProBitmapShards(runtime, result, fetchDeps, progress) {
|
|
321
|
-
if (!result.metadataSha256)
|
|
322
|
-
return undefined;
|
|
323
|
-
try {
|
|
324
|
-
const warm = await runProResourceWarmupUseCase(commandContext(runtime, { json: false, yes: false }), runtime.auth ?? {}, {
|
|
325
|
-
version: result.version,
|
|
326
|
-
descriptorSha256: result.descriptorSha256,
|
|
327
|
-
metadataSha256: result.metadataSha256,
|
|
328
|
-
catalogSha256: result.catalogSha256,
|
|
329
|
-
...fetchDeps,
|
|
330
|
-
onPlan: (plan) => progress.phase(`Default bitmap shards: ${plan.missing.length} to download, ${plan.reused} cached`),
|
|
331
|
-
onProgress: (event) => progress.phase(`Shard ${event.index}/${event.total} ${event.phase} — ${event.tuple.styleGroupId} ${event.tuple.imageSize.width}x${event.tuple.imageSize.height} ${event.tuple.format}`),
|
|
332
|
-
});
|
|
333
|
-
return `Warmed ${warm.shards} default bitmap shards (${warm.downloaded} downloaded, ${warm.reused} cached).`;
|
|
334
|
-
}
|
|
335
|
-
catch (error) {
|
|
336
|
-
runtime.stderr(`warning: default bitmap shard warm-up skipped (${error instanceof Error ? error.message : String(error)}).\n`);
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Home-screen Pro resources flow: show the current cache state and drive the
|
|
342
|
-
* matching action (download / update / repair / verify). Requires auth.
|
|
343
|
-
*/
|
|
305
|
+
/** The Pro resource action warms only separately addressed 128-WebP tuple packages. */
|
|
344
306
|
async function runProResources(runtime, yes) {
|
|
345
307
|
const context = commandContext(runtime, { json: false, yes });
|
|
346
|
-
const
|
|
347
|
-
const state = await proResourceState(context, runtime.auth ?? {}, fetchDeps);
|
|
348
|
-
if (state.kind === "unavailable") {
|
|
349
|
-
runtime.stdout(`Pro resources unavailable: ${state.reason}\n`);
|
|
350
|
-
return 0;
|
|
351
|
-
}
|
|
352
|
-
let action = "";
|
|
353
|
-
if (state.kind === "current") {
|
|
354
|
-
runtime.stdout(`Pro resources are up to date (v${state.version}).\n`);
|
|
355
|
-
return 0;
|
|
356
|
-
}
|
|
357
|
-
if (state.kind === "not-cached") {
|
|
358
|
-
runtime.stdout(`Pro ${state.version}: ${formatBytes(state.codeBytes)} code + ${formatBytes(state.metadataBytes)} metadata.\n`);
|
|
359
|
-
action = "Download";
|
|
360
|
-
}
|
|
361
|
-
else if (state.kind === "outdated") {
|
|
362
|
-
runtime.stdout(`Pro ${state.version} → ${state.latestVersion}: ${formatBytes(state.codeBytes)} code + ${formatBytes(state.metadataBytes)} metadata.\n`);
|
|
363
|
-
action = "Update";
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
runtime.stdout(`Pro ${state.version} cache is corrupt; re-downloading.\n`);
|
|
367
|
-
action = "Repair";
|
|
368
|
-
}
|
|
369
|
-
const confirmed = await context.ui.confirm(`${action} Pro resources now? (code + metadata)`, context.signal);
|
|
370
|
-
if (confirmed !== true) {
|
|
371
|
-
runtime.stdout("Cancelled; Pro resources were not downloaded.\n");
|
|
372
|
-
return 0;
|
|
373
|
-
}
|
|
374
|
-
const progress = startResourceProgress(context, "Downloading Pro resources");
|
|
308
|
+
const progress = startResourceProgress(context, "Warming Pro preview packages");
|
|
375
309
|
try {
|
|
376
|
-
const result = await
|
|
377
|
-
...
|
|
378
|
-
|
|
379
|
-
|
|
310
|
+
const result = await runProPreviewPackageWarmupUseCase(context, runtime.auth ?? {}, {
|
|
311
|
+
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
312
|
+
onPlan: async (plan) => {
|
|
313
|
+
progress.phase(`${plan.groups} preview groups · ${plan.packages} packages · ${formatBytes(plan.compressedBytes)}`);
|
|
314
|
+
const confirmed = await context.ui.confirm(`Warm ${plan.groups} Pro preview groups (${formatBytes(plan.compressedBytes)} compressed package data)?`, context.signal);
|
|
315
|
+
if (confirmed !== true)
|
|
316
|
+
throw new CliError("CANCELLED", "Pro preview warm-up cancelled");
|
|
317
|
+
},
|
|
380
318
|
});
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
runtime.stdout(`Cached Pro ${result.version} code + metadata.\n`);
|
|
384
|
-
if (warmed)
|
|
385
|
-
runtime.stdout(`${warmed}\n`);
|
|
319
|
+
progress.stop("Pro preview package warm-up complete");
|
|
320
|
+
runtime.stdout(`Warmed ${result.groups} Pro preview groups (${result.downloaded} downloaded, ${result.reused} cached).\n`);
|
|
386
321
|
return 0;
|
|
387
322
|
}
|
|
388
323
|
catch (error) {
|
|
389
|
-
progress.stop("Pro
|
|
324
|
+
progress.stop("Pro preview warm-up stopped");
|
|
325
|
+
if (error instanceof CliError && error.code === "CANCELLED") {
|
|
326
|
+
runtime.stdout("Cancelled; no Pro preview packages were downloaded.\n");
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
390
329
|
if (error instanceof CliError && (error.code === "FORBIDDEN" || error.code === "AUTH_ERROR")) {
|
|
391
330
|
throw error;
|
|
392
331
|
}
|
|
393
|
-
runtime.stderr(`
|
|
332
|
+
runtime.stderr(`Pro preview warm-up failed (${error instanceof Error ? error.message : String(error)}).\n`);
|
|
394
333
|
return 0;
|
|
395
334
|
}
|
|
396
335
|
}
|
|
@@ -411,8 +350,11 @@ async function runAccount(runtime, json) {
|
|
|
411
350
|
catch (error) {
|
|
412
351
|
remoteError = error;
|
|
413
352
|
}
|
|
414
|
-
if (json && remoteError)
|
|
415
|
-
|
|
353
|
+
if (json && remoteError) {
|
|
354
|
+
if (remoteError instanceof Error)
|
|
355
|
+
throw remoteError;
|
|
356
|
+
throw new Error("remote account command failed", { cause: remoteError });
|
|
357
|
+
}
|
|
416
358
|
const account = { ...session, ...(remote ?? {}) };
|
|
417
359
|
if (json)
|
|
418
360
|
writeJson(runtime, { ok: true, environment, account });
|
|
@@ -638,21 +580,10 @@ async function runWizard(runtime, json, yes) {
|
|
|
638
580
|
session: session.kind,
|
|
639
581
|
...(historyLine ? { historyLine } : {}),
|
|
640
582
|
getLibraryStatus: getStatus,
|
|
641
|
-
getProResourceLabel:
|
|
583
|
+
getProResourceLabel: () => {
|
|
642
584
|
if (session.kind !== "authenticated")
|
|
643
|
-
return undefined;
|
|
644
|
-
|
|
645
|
-
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
646
|
-
}).catch(() => undefined);
|
|
647
|
-
if (!state || state.kind === "unavailable")
|
|
648
|
-
return undefined;
|
|
649
|
-
if (state.kind === "not-cached")
|
|
650
|
-
return "Download Pro resources";
|
|
651
|
-
if (state.kind === "outdated")
|
|
652
|
-
return `Update Pro resources (${state.version} → ${state.latestVersion})`;
|
|
653
|
-
if (state.kind === "corrupt")
|
|
654
|
-
return "Repair Pro resources";
|
|
655
|
-
return "Pro resources are up to date";
|
|
585
|
+
return Promise.resolve(undefined);
|
|
586
|
+
return Promise.resolve("Warm default Pro previews");
|
|
656
587
|
},
|
|
657
588
|
});
|
|
658
589
|
if (!result.ok) {
|
|
@@ -789,7 +720,7 @@ async function runWizardLogin(runtime, yes) {
|
|
|
789
720
|
}
|
|
790
721
|
}
|
|
791
722
|
}
|
|
792
|
-
async function runLibraryUpdate(runtime, tier, version, descriptorSha256, json = false) {
|
|
723
|
+
async function runLibraryUpdate(runtime, tier, version, descriptorSha256, json = false, packageSetSha256, preflightIdentityBytes) {
|
|
793
724
|
const context = commandContext(runtime, { json, yes: false });
|
|
794
725
|
// W3-A: a single progress surface. `task` is deliberately NOT used here: the
|
|
795
726
|
// clack adapter implements both with `p.spinner()`, so stacking them would
|
|
@@ -812,14 +743,17 @@ async function runLibraryUpdate(runtime, tier, version, descriptorSha256, json =
|
|
|
812
743
|
auth: runtime.auth ?? {},
|
|
813
744
|
fetch: runtime.auth?.fetch ?? globalThis.fetch.bind(globalThis),
|
|
814
745
|
onProgress: ({ downloadedBytes, totalBytes }) => progress.bytes({ downloadedBytes, ...(totalBytes !== undefined ? { totalBytes } : {}) }),
|
|
815
|
-
}, { tier, version, descriptorSha256 });
|
|
746
|
+
}, { tier, version, descriptorSha256, ...(packageSetSha256 ? { packageSetSha256 } : {}) });
|
|
747
|
+
const output = preflightIdentityBytes === undefined
|
|
748
|
+
? result
|
|
749
|
+
: { ...result, networkBytes: (result.networkBytes ?? 0) + preflightIdentityBytes };
|
|
816
750
|
progress.stop("Icon library update complete");
|
|
817
751
|
if (json)
|
|
818
|
-
writeJson(runtime, { ok: true, ...
|
|
752
|
+
writeJson(runtime, { ok: true, ...output });
|
|
819
753
|
else {
|
|
820
|
-
runtime.stdout(`Updated ${tier} artifact to ${
|
|
821
|
-
runtime.stdout(`Download mode: ${
|
|
822
|
-
for (const note of
|
|
754
|
+
runtime.stdout(`Updated ${tier} artifact to ${output.artifactVersion}; reconciled ${output.files.length} generated files.\n`);
|
|
755
|
+
runtime.stdout(`Download mode: ${output.downloadMode}${output.selectedFiles !== undefined ? `; ${output.selectedFiles} resource files; ${output.networkBytes} resource bytes received` : ""}.\n`);
|
|
756
|
+
for (const note of output.downloadNotes)
|
|
823
757
|
runtime.stdout(`${note}\n`);
|
|
824
758
|
}
|
|
825
759
|
return 0;
|
|
@@ -888,6 +822,41 @@ async function runUpdate(runtime, json, yes, metadataOnly) {
|
|
|
888
822
|
throw new CliError("NETWORK_ERROR", status.message);
|
|
889
823
|
if (status.kind === "installation-invalid")
|
|
890
824
|
throw new CliError("VALIDATION_ERROR", status.message);
|
|
825
|
+
if (status.kind === "package-set-update-check-required") {
|
|
826
|
+
const tier = status.metadata.candidate.tier;
|
|
827
|
+
const identity = tier === "free"
|
|
828
|
+
? await fetchFreeStablePackageSet({
|
|
829
|
+
...(runtime.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: runtime.env.MOEICONS_API_BASE_URL } : {}),
|
|
830
|
+
...(runtime.auth?.fetch ? { fetchFn: runtime.auth.fetch } : {}),
|
|
831
|
+
signal: context.signal,
|
|
832
|
+
})
|
|
833
|
+
: await fetchProStablePackageSet({
|
|
834
|
+
accessToken: await runAccessTokenUseCase(context, runtime.auth ?? {}),
|
|
835
|
+
...(runtime.env.MOEICONS_API_BASE_URL ? { apiBaseUrl: runtime.env.MOEICONS_API_BASE_URL } : {}),
|
|
836
|
+
...(runtime.auth?.fetch ? { fetchFn: runtime.auth.fetch } : {}),
|
|
837
|
+
signal: context.signal,
|
|
838
|
+
});
|
|
839
|
+
const current = status.metadata.candidate;
|
|
840
|
+
const decision = decideUpdate({ installedTier: tier, remoteTier: tier, installedVersion: current.fullVersion, remoteVersion: identity.fullVersion });
|
|
841
|
+
if (decision.kind === "current") {
|
|
842
|
+
if (current.packageSetSha256 !== identity.packageSetSha256 || current.candidateRootSha256 !== identity.candidateRootSha256)
|
|
843
|
+
throw new CliError("VALIDATION_ERROR", "published package-set identity changed for the installed version; refusing replacement");
|
|
844
|
+
if (json)
|
|
845
|
+
writeJson(runtime, { ok: true, status: "current", version: current.fullVersion, packageSetSha256: current.packageSetSha256 });
|
|
846
|
+
else
|
|
847
|
+
runtime.stdout(`Current: ${current.fullVersion} / Latest: ${identity.fullVersion} / Status: up to date\n`);
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
if (decision.kind !== "update") {
|
|
851
|
+
const label = decision.reason === "channel-mismatch" ? "prerelease channel differs" : "remote version is older";
|
|
852
|
+
if (json)
|
|
853
|
+
writeJson(runtime, { ok: true, status: decision.reason === "channel-mismatch" ? "channel-mismatch" : "remote-older", currentVersion: current.fullVersion, latestVersion: identity.fullVersion });
|
|
854
|
+
else
|
|
855
|
+
runtime.stdout(`Current: ${current.fullVersion} / Latest: ${identity.fullVersion} / Status: ${label}\n`);
|
|
856
|
+
return 0;
|
|
857
|
+
}
|
|
858
|
+
return runLibraryUpdate(runtime, tier, identity.fullVersion, identity.packageSetSha256, json, identity.packageSetSha256, identity.networkBytes);
|
|
859
|
+
}
|
|
891
860
|
if (status.kind === "current" &&
|
|
892
861
|
status.latestDescriptorSha256 !== status.metadata.descriptorSha256)
|
|
893
862
|
throw new CliError("VALIDATION_ERROR", "published identity changed for the installed version; refusing to replace the pinned release");
|
|
@@ -921,21 +890,23 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
921
890
|
const progress = context.ui.progress("Downloading icon library", context.signal);
|
|
922
891
|
if (group === "pro" || group === "ent") {
|
|
923
892
|
const { allowLocalTest } = resolveLocalTestContext(runtime.env);
|
|
924
|
-
const identity =
|
|
925
|
-
?
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
893
|
+
const identity = allowLocalTest
|
|
894
|
+
? sourceVersion && expectedDescriptorSha256
|
|
895
|
+
? { version: sourceVersion, descriptorSha256: expectedDescriptorSha256 }
|
|
896
|
+
: await fetchLibraryVersions({
|
|
897
|
+
signal: context.signal,
|
|
898
|
+
env: runtime.env,
|
|
899
|
+
allowLocalTest,
|
|
900
|
+
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
901
|
+
}).then((versions) => {
|
|
902
|
+
if (!versions.pro)
|
|
903
|
+
throw new CliError("NOT_FOUND", "no pro release is published");
|
|
904
|
+
return {
|
|
905
|
+
version: versions.pro.version,
|
|
906
|
+
descriptorSha256: versions.pro.descriptorSha256,
|
|
907
|
+
};
|
|
908
|
+
})
|
|
909
|
+
: {};
|
|
939
910
|
try {
|
|
940
911
|
const result = await runProInstallUseCase(context, {
|
|
941
912
|
fs: {
|
|
@@ -958,6 +929,7 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
958
929
|
},
|
|
959
930
|
}, {
|
|
960
931
|
...identity,
|
|
932
|
+
...(!allowLocalTest && sourceVersion ? { version: sourceVersion } : {}),
|
|
961
933
|
...(allowLocalTest ? { allowLocalTest: true } : {}),
|
|
962
934
|
...(target ? { target } : {}),
|
|
963
935
|
});
|
|
@@ -1025,12 +997,15 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
1025
997
|
projectRoot: result.projectRoot,
|
|
1026
998
|
packageManager: result.packageManager,
|
|
1027
999
|
group: result.group,
|
|
1028
|
-
zipBytes: result.artifactBytes,
|
|
1000
|
+
...(result.downloadMode === "packages" ? {} : { zipBytes: result.artifactBytes }),
|
|
1029
1001
|
artifactBytes: result.artifactBytes,
|
|
1030
1002
|
planItems: result.planItems,
|
|
1031
1003
|
config: result.config,
|
|
1032
1004
|
artifactVersion: result.artifactVersion,
|
|
1033
1005
|
descriptorSha256: result.descriptorSha256,
|
|
1006
|
+
candidateRootSha256: result.candidateRootSha256,
|
|
1007
|
+
packageSetSha256: result.packageSetSha256,
|
|
1008
|
+
packageCount: result.packageCount,
|
|
1034
1009
|
catalogSha256: result.catalogSha256,
|
|
1035
1010
|
metadataSha256: result.metadataSha256,
|
|
1036
1011
|
cacheHit: result.cacheHit,
|
|
@@ -1038,6 +1013,9 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
1038
1013
|
downloadNotes: result.downloadNotes,
|
|
1039
1014
|
networkBytes: result.networkBytes,
|
|
1040
1015
|
selectedFiles: result.selectedFiles,
|
|
1016
|
+
cacheHits: result.cacheHits,
|
|
1017
|
+
metadataCacheHits: result.metadataCacheHits,
|
|
1018
|
+
transferRetryCount: result.transferRetryCount,
|
|
1041
1019
|
});
|
|
1042
1020
|
}
|
|
1043
1021
|
else {
|
|
@@ -1048,7 +1026,7 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
1048
1026
|
runtime.stdout(`Download mode: ${result.downloadMode}${result.selectedFiles !== undefined ? `; ${result.selectedFiles} resource files; ${result.networkBytes} resource bytes received` : ""}.\n`);
|
|
1049
1027
|
for (const note of result.downloadNotes ?? [])
|
|
1050
1028
|
runtime.stdout(`${note}\n`);
|
|
1051
|
-
runtime.stdout(`Installed free artifact metadata to .moeicons (cacheHit=${String(result.cacheHit)}).\n`);
|
|
1029
|
+
runtime.stdout(`${result.downloadMode === "packages" ? `Installed ${result.packageCount ?? 0} selected packages` : "Installed free artifact metadata"} to .moeicons (cacheHit=${String(result.cacheHit)}).\n`);
|
|
1052
1030
|
}
|
|
1053
1031
|
return 0;
|
|
1054
1032
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"sourceRepo": "moewolf-dev/moe-icons-code-library",
|
|
4
4
|
"sourceDir": "config-package",
|
|
5
|
-
"sourceCommit": "
|
|
5
|
+
"sourceCommit": "8a14d3e05a6073896708413bef31b3793b718021",
|
|
6
6
|
"sourceCommitExact": true,
|
|
7
7
|
"files": {
|
|
8
8
|
"render-config.cjs": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"sha256": "36c35b686335eab4e7db002afb631aa631c80f66656f137b1e2437317b126b0f"
|
|
15
15
|
},
|
|
16
16
|
"moeicons-config.schema.json": {
|
|
17
|
-
"size":
|
|
18
|
-
"sha256": "
|
|
17
|
+
"size": 4854,
|
|
18
|
+
"sha256": "aa0a688104fd66431d91bb089ae70c3306cc289b0a4d5a24c9786062b6ae56ab"
|
|
19
19
|
},
|
|
20
20
|
"moeicons.config.jsonc": {
|
|
21
21
|
"size": 1207,
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
"full"
|
|
186
186
|
],
|
|
187
187
|
"default": "auto",
|
|
188
|
-
"description": "
|
|
188
|
+
"description": "Resolve selected packages (auto), require selected packages (icons), or use full archives only with explicit local test fixtures (full). Published releases never download a tier-wide archive."
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
}
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -32,9 +32,11 @@ export async function runBootstrapUseCase(context, deps) {
|
|
|
32
32
|
return { kind: "skipped", reason: "no-project" };
|
|
33
33
|
const state = readInstalledResourceState(project.root, "free");
|
|
34
34
|
if (state.kind === "ok" &&
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
(state.metadata.schemaVersion === 2 ||
|
|
36
|
+
(deps.fs.existsSync(join(project.root, ".moeicons", "MANUAL.md")) &&
|
|
37
|
+
deps.fs.existsSync(join(project.root, ".moeicons", "manifest.json"))))) {
|
|
38
|
+
const version = state.metadata.schemaVersion === 1 ? state.metadata.artifactVersion : state.metadata.candidate.fullVersion;
|
|
39
|
+
writeMarker(deps, context.env, { cliVersion: deps.cliVersion, libraryVersion: version, completedAt: new Date().toISOString() });
|
|
38
40
|
return { kind: "already" };
|
|
39
41
|
}
|
|
40
42
|
const confirmed = await context.ui.confirm("Set up moeicons Free icons in this project now? (recommended)", context.signal);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CandidateRoot } from './small-pack/candidate-set.cjs';
|
|
2
|
+
export interface FreeCandidateVersionPointer {
|
|
3
|
+
readonly schemaVersion: 1;
|
|
4
|
+
readonly tier: 'free';
|
|
5
|
+
readonly fullVersion: string;
|
|
6
|
+
readonly sourceCommit: string;
|
|
7
|
+
readonly generatorCommit: string;
|
|
8
|
+
readonly packageSetSha256: string;
|
|
9
|
+
readonly candidateRootSha256: string;
|
|
10
|
+
readonly candidateRootBytes: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseFreeCandidateVersionPointer(input: Uint8Array, expectedVersion: string): FreeCandidateVersionPointer;
|
|
13
|
+
export declare function pointerMatchesCandidateRoot(pointer: FreeCandidateVersionPointer, root: CandidateRoot): boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { canonicalBytes } from './small-pack/candidate-set.cjs';
|
|
2
|
+
const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:alpha|beta)(?:\.(?:0|[1-9]\d*))?)?$/;
|
|
3
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
4
|
+
const COMMIT = /^[a-f0-9]{40}$/;
|
|
5
|
+
const MAX_POINTER_BYTES = 16 * 1024;
|
|
6
|
+
function check(ok, message) {
|
|
7
|
+
if (!ok)
|
|
8
|
+
throw new Error(`Free candidate version pointer: ${message}`);
|
|
9
|
+
}
|
|
10
|
+
export function parseFreeCandidateVersionPointer(input, expectedVersion) {
|
|
11
|
+
check(VERSION.test(expectedVersion), 'requested version is invalid');
|
|
12
|
+
const bytes = Buffer.from(input);
|
|
13
|
+
check(bytes.length > 0 && bytes.length <= MAX_POINTER_BYTES, 'pointer exceeds 16 KiB');
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
raw = JSON.parse(bytes.toString('utf8'));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error('Free candidate version pointer: invalid JSON');
|
|
20
|
+
}
|
|
21
|
+
check(raw !== null && typeof raw === 'object' && !Array.isArray(raw), 'pointer object required');
|
|
22
|
+
const value = raw;
|
|
23
|
+
const fields = ['schemaVersion', 'tier', 'fullVersion', 'sourceCommit', 'generatorCommit', 'packageSetSha256', 'candidateRootSha256', 'candidateRootBytes'];
|
|
24
|
+
check(Object.keys(value).sort().join(',') === [...fields].sort().join(','), 'unexpected pointer fields');
|
|
25
|
+
check(bytes.equals(canonicalBytes(value)), 'pointer must use canonical JSON');
|
|
26
|
+
check(value.schemaVersion === 1 && value.tier === 'free' && value.fullVersion === expectedVersion, 'pointer differs from exact Free version');
|
|
27
|
+
check(typeof value.sourceCommit === 'string' && COMMIT.test(value.sourceCommit), 'Source commit is invalid');
|
|
28
|
+
check(typeof value.generatorCommit === 'string' && COMMIT.test(value.generatorCommit), 'generator commit is invalid');
|
|
29
|
+
check(typeof value.packageSetSha256 === 'string' && SHA256.test(value.packageSetSha256), 'package-set digest is invalid');
|
|
30
|
+
check(typeof value.candidateRootSha256 === 'string' && SHA256.test(value.candidateRootSha256), 'candidate root digest is invalid');
|
|
31
|
+
check(Number.isSafeInteger(value.candidateRootBytes) && Number(value.candidateRootBytes) > 0 && Number(value.candidateRootBytes) <= 1024 ** 2, 'candidate root size is invalid');
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
export function pointerMatchesCandidateRoot(pointer, root) {
|
|
35
|
+
return pointer.tier === root.tier && pointer.fullVersion === root.fullVersion && pointer.sourceCommit === root.sourceCommit
|
|
36
|
+
&& pointer.generatorCommit === root.generatorCommit && pointer.packageSetSha256 === root.packageSetSha256;
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type IconCatalog } from '../catalog/catalog.js';
|
|
2
|
+
import type { MoeiconsConfigFile } from '../project/config.js';
|
|
3
|
+
import { type SelectedCandidate } from './small-package-selection.js';
|
|
4
|
+
import type { PackageRow } from './small-pack/candidate-set.cjs';
|
|
5
|
+
export declare function planConfigPackages(config: MoeiconsConfigFile, catalog: IconCatalog, candidate: SelectedCandidate): {
|
|
6
|
+
resourceKeys: readonly string[];
|
|
7
|
+
packages: readonly PackageRow[];
|
|
8
|
+
fallbacks: readonly string[];
|
|
9
|
+
};
|
|
10
|
+
/** Bootstrap only the scoped metadata needed by this project's themes, then
|
|
11
|
+
* plan exact content units against the resulting candidate-bound catalog. The
|
|
12
|
+
* loader must return bytes from the selected-package verifier. */
|
|
13
|
+
export declare function planConfigPackagesFromMetadata(config: MoeiconsConfigFile, candidate: SelectedCandidate, loadVerifiedPackage: (row: PackageRow) => Promise<Readonly<Record<string, Uint8Array>>>): Promise<{
|
|
14
|
+
catalog: IconCatalog;
|
|
15
|
+
resourceKeys: readonly string[];
|
|
16
|
+
packages: readonly PackageRow[];
|
|
17
|
+
fallbacks: readonly string[];
|
|
18
|
+
}>;
|
|
19
|
+
export interface PackageTargetFiles {
|
|
20
|
+
readonly area: 'target' | 'assets' | 'metadata';
|
|
21
|
+
readonly files: Readonly<Record<string, Uint8Array>>;
|
|
22
|
+
}
|
|
23
|
+
/** Map one already verified unit; never accumulate multiple packages or invent
|
|
24
|
+
* an all-tier archive identity. Callers stage these files transactionally. */
|
|
25
|
+
export declare function packageTargetFiles(row: PackageRow, files: Readonly<Record<string, Uint8Array>>, target: MoeiconsConfigFile['target']): PackageTargetFiles;
|