@moewolf/moe-icons-cli 0.0.1 → 0.0.2
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 +243 -9
- package/bin/check-node.cjs +36 -0
- package/bin/moeicons.js +12 -1
- package/dist/api/client.d.ts +13 -2
- package/dist/api/client.js +145 -21
- package/dist/auth/device-login.d.ts +10 -7
- package/dist/auth/device-login.js +60 -37
- package/dist/auth/login-schemas.d.ts +24 -0
- package/dist/auth/login-schemas.js +67 -0
- package/dist/catalog/catalog.d.ts +11 -0
- package/dist/catalog/catalog.js +43 -0
- package/dist/catalog/catalog.json +538 -3395
- package/dist/cli.d.ts +11 -2
- package/dist/cli.js +773 -147
- package/dist/commands/parser.d.ts +13 -2
- package/dist/commands/parser.js +138 -50
- package/dist/config-package/generated/SOURCE.json +25 -0
- package/dist/config-package/generated/moeicons-config.schema.json +191 -0
- package/dist/config-package/generated/moeicons.config.jsonc +42 -0
- package/dist/config-package/generated/render-config.cjs +145 -0
- package/dist/config-package/generated/validate-config.cjs +293 -0
- package/dist/config-package/generated-config.d.ts +32 -0
- package/dist/config-package/generated-config.js +19 -0
- package/dist/core/auth.d.ts +24 -0
- package/dist/core/auth.js +127 -8
- package/dist/core/bitmap-assets.d.ts +19 -5
- package/dist/core/bitmap-assets.js +40 -31
- package/dist/core/bitmap-shard-budget.d.ts +43 -0
- package/dist/core/bitmap-shard-budget.js +16 -0
- package/dist/core/bitmap-shard-cache.d.ts +68 -0
- package/dist/core/bitmap-shard-cache.js +115 -0
- package/dist/core/bitmap-shard-download.d.ts +130 -0
- package/dist/core/bitmap-shard-download.js +359 -0
- package/dist/core/bitmap-shard-resolver.d.ts +129 -0
- package/dist/core/bitmap-shard-resolver.js +262 -0
- package/dist/core/bitmap-shards.d.ts +70 -0
- package/dist/core/bitmap-shards.js +249 -0
- package/dist/core/bootstrap.d.ts +37 -0
- package/dist/core/bootstrap.js +54 -0
- package/dist/core/cache.d.ts +34 -0
- package/dist/core/cache.js +96 -0
- package/dist/core/context.d.ts +26 -0
- package/dist/core/doctor.d.ts +48 -0
- package/dist/core/doctor.js +190 -0
- package/dist/core/free-download.d.ts +64 -6
- package/dist/core/free-download.js +402 -30
- package/dist/core/generate.d.ts +29 -0
- package/dist/core/generate.js +341 -65
- package/dist/core/icon-selection.d.ts +8 -0
- package/dist/core/icon-selection.js +19 -0
- package/dist/core/install.d.ts +14 -0
- package/dist/core/install.js +132 -13
- package/dist/core/library-update.d.ts +5 -1
- package/dist/core/library-update.js +176 -17
- package/dist/core/local-test-env.d.ts +24 -0
- package/dist/core/local-test-env.js +43 -0
- package/dist/core/metadata-sync.d.ts +30 -0
- package/dist/core/metadata-sync.js +97 -0
- package/dist/core/pro-download.d.ts +63 -0
- package/dist/core/pro-download.js +247 -27
- package/dist/core/pro-install.d.ts +11 -3
- package/dist/core/pro-install.js +181 -19
- package/dist/core/pro-resources.d.ts +79 -0
- package/dist/core/pro-resources.js +204 -0
- package/dist/core/pro-selected-download.d.ts +18 -0
- package/dist/core/pro-selected-download.js +269 -0
- package/dist/core/release-descriptor.d.ts +42 -1
- package/dist/core/release-descriptor.js +117 -9
- package/dist/core/resource-variant.d.ts +27 -10
- package/dist/core/resource-variant.js +75 -34
- package/dist/core/selected-resources.d.ts +59 -0
- package/dist/core/selected-resources.js +350 -0
- package/dist/core/signed-artifact.d.ts +8 -1
- package/dist/core/signed-artifact.js +35 -11
- package/dist/core/target-subtree.d.ts +8 -0
- package/dist/core/target-subtree.js +54 -3
- package/dist/core/version-service.d.ts +11 -0
- package/dist/core/version-service.js +129 -24
- package/dist/core/wizard.d.ts +20 -17
- package/dist/core/wizard.js +66 -28
- package/dist/errors/errors.d.ts +2 -1
- package/dist/errors/errors.js +1 -0
- package/dist/generator/generate.d.ts +1 -1
- package/dist/generator/generate.js +420 -213
- package/dist/generator/shared/SOURCE.json +7 -0
- package/dist/generator/shared/svg-model.cjs +156 -0
- package/dist/generator/svg-model.d.ts +16 -0
- package/dist/generator/svg-model.js +4 -0
- package/dist/generator/theme-resolve.d.ts +2 -0
- package/dist/generator/theme-resolve.js +57 -26
- package/dist/mcp/server.d.ts +23 -36
- package/dist/mcp/server.js +74 -152
- package/dist/mcp/services.d.ts +4 -0
- package/dist/mcp/services.js +230 -0
- package/dist/metadata/archive.d.ts +38 -0
- package/dist/metadata/archive.js +94 -0
- package/dist/metadata/manifest.d.ts +33 -0
- package/dist/metadata/manifest.js +136 -0
- package/dist/metadata/version.d.ts +22 -0
- package/dist/metadata/version.js +53 -0
- package/dist/project/anchors/application.d.ts +13 -0
- package/dist/project/anchors/application.js +377 -0
- package/dist/project/anchors/apply.d.ts +28 -0
- package/dist/project/anchors/apply.js +65 -0
- package/dist/project/anchors/diagnose.d.ts +26 -0
- package/dist/project/anchors/diagnose.js +90 -0
- package/dist/project/anchors/helpers.d.ts +20 -0
- package/dist/project/anchors/helpers.js +55 -0
- package/dist/project/anchors/moeicons-config.d.ts +21 -0
- package/dist/project/anchors/moeicons-config.js +83 -0
- package/dist/project/anchors/parse.d.ts +14 -0
- package/dist/project/anchors/parse.js +36 -0
- package/dist/project/anchors/project-manifest.d.ts +19 -0
- package/dist/project/anchors/project-manifest.js +109 -0
- package/dist/project/anchors/style.d.ts +12 -0
- package/dist/project/anchors/style.js +87 -0
- package/dist/project/anchors/types.d.ts +32 -0
- package/dist/project/anchors/types.js +9 -0
- package/dist/project/config.d.ts +59 -5
- package/dist/project/config.js +234 -102
- package/dist/project/install-metadata.d.ts +23 -2
- package/dist/project/install-metadata.js +58 -7
- package/dist/project/install.d.ts +24 -4
- package/dist/project/install.js +402 -190
- package/dist/project/project-lock.d.ts +2 -2
- package/dist/project/project-lock.js +4 -4
- package/dist/project/tailwind.d.ts +2 -2
- package/dist/project/tailwind.js +3 -4
- package/dist/project/tar-gz.d.ts +18 -1
- package/dist/project/tar-gz.js +40 -3
- package/dist/tui/components.d.ts +62 -0
- package/dist/tui/components.js +100 -0
- package/dist/tui/primitives.d.ts +1 -0
- package/dist/tui/primitives.js +8 -1
- package/dist/ui/banner.d.ts +31 -3
- package/dist/ui/banner.js +120 -25
- package/dist/ui/branded-prompts.d.ts +41 -0
- package/dist/ui/branded-prompts.js +80 -0
- package/dist/ui/clack.d.ts +6 -3
- package/dist/ui/clack.js +56 -5
- package/dist/ui/create-ui.d.ts +1 -0
- package/dist/ui/create-ui.js +5 -1
- package/dist/ui/generated/wordmark-large.d.ts +17 -0
- package/dist/ui/generated/wordmark-large.js +28 -0
- package/dist/ui/generated/wordmark.d.ts +2 -0
- package/dist/ui/generated/wordmark.js +2 -0
- package/dist/ui/non-interactive.js +8 -0
- package/dist/ui/stream.js +30 -0
- package/dist/ui/theme.d.ts +42 -0
- package/dist/ui/theme.js +59 -0
- package/package.json +14 -6
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
|
+
import { safeMcpValue } from "./mcp/server.js";
|
|
2
|
+
import { createProjectMcpServices } from "./mcp/services.js";
|
|
3
|
+
import { assertDownloadSelection } from "./core/selected-resources.js";
|
|
1
4
|
import { parseArgs, HELP_TEXT } from "./commands/parser.js";
|
|
2
5
|
import { CliError, isCliError, jsonErrorBody } from "./errors/index.js";
|
|
3
6
|
import { detectProject } from "./project/detect.js";
|
|
4
|
-
import { readMoeiconsConfig } from "./project/config.js";
|
|
5
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync, rmSync, readdirSync, copyFileSync, } from "node:fs";
|
|
7
|
+
import { readMoeiconsConfig, loadConfigDocument, validateConfigDocument, } from "./project/config.js";
|
|
8
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync, rmSync, readdirSync, copyFileSync, statfsSync, } from "node:fs";
|
|
6
9
|
import { homedir } from "node:os";
|
|
7
10
|
import { join } from "node:path";
|
|
8
|
-
import { runInitUseCase } from "./core/init.js";
|
|
9
11
|
import { runGenerateUseCase } from "./core/generate.js";
|
|
12
|
+
import { recoverManagedReconcile, safeManagedPath } from "./project/install.js";
|
|
13
|
+
import { withProjectLockSync } from "./project/project-lock.js";
|
|
10
14
|
import { runInstallUseCase } from "./core/install.js";
|
|
11
|
-
import { runWizardUseCase } from "./core/wizard.js";
|
|
15
|
+
import { runWizardUseCase, loginRecoveryChoices } from "./core/wizard.js";
|
|
12
16
|
import { createCommandUi } from "./ui/create-ui.js";
|
|
13
|
-
import { CLI_VERSION, MOEICONS_BANNER,
|
|
14
|
-
import {
|
|
17
|
+
import { CLI_VERSION, MOEICONS_BANNER, renderNoticeBox, renderProjectNotice, renderWordmarkText, } from "./ui/banner.js";
|
|
18
|
+
import { createTheme, isThemeEnabled } from "./ui/theme.js";
|
|
19
|
+
import { renderHistoryBar } from "./tui/components.js";
|
|
20
|
+
import { describeAuthEnvironment, runAccountUseCase, runLoginUseCase, runLogoutUseCase, runRemoteAccountUseCase, runSessionStatusUseCase, } from "./core/auth.js";
|
|
15
21
|
import { formatLibraryVersionStatus, getLibraryVersionStatus } from "./core/manage.js";
|
|
16
22
|
import { runCliUpdateCheck } from "./core/cli-update.js";
|
|
17
23
|
import { fetchLibraryVersions } from "./core/version-service.js";
|
|
18
24
|
import { runProInstallUseCase } from "./core/pro-install.js";
|
|
25
|
+
import { resolveLocalTestContext } from "./core/local-test-env.js";
|
|
19
26
|
import { runLibraryUpdateUseCase } from "./core/library-update.js";
|
|
27
|
+
import { runMetadataSyncUseCase } from "./core/metadata-sync.js";
|
|
28
|
+
import { runBootstrapUseCase } from "./core/bootstrap.js";
|
|
29
|
+
import { fetchProDescriptor } from "./core/pro-download.js";
|
|
30
|
+
import { proResourceState, runProPredownloadUseCase, runProResourceWarmupUseCase, } from "./core/pro-resources.js";
|
|
31
|
+
import { formatBytes } from "./metadata/version.js";
|
|
32
|
+
import { isLocalTestVersion } from "./core/release-descriptor.js";
|
|
33
|
+
import { buildInitPlan, checkRequiresFix, collectSafeFixes, doctorJson, formatDoctorReport, formatPlannedDiffs, plannedDiffsJson, runDoctorApply, runDoctorDiagnose, } from "./core/doctor.js";
|
|
20
34
|
function commandContext(runtime, flags) {
|
|
21
35
|
const streams = runtime.readLine !== undefined && runtime.readKey !== undefined
|
|
22
36
|
? {
|
|
@@ -31,19 +45,64 @@ function commandContext(runtime, flags) {
|
|
|
31
45
|
json: flags.json,
|
|
32
46
|
yes: flags.yes,
|
|
33
47
|
isTTY: runtime.isTTY(),
|
|
48
|
+
env: runtime.env,
|
|
34
49
|
...(streams ? { streams } : {}),
|
|
35
50
|
}),
|
|
36
51
|
cwd: runtime.cwd(),
|
|
37
52
|
env: runtime.env,
|
|
38
|
-
signal: new AbortController().signal,
|
|
53
|
+
signal: runtime.signal ?? new AbortController().signal,
|
|
39
54
|
now: () => new Date(),
|
|
40
55
|
};
|
|
41
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* E2E-E3: allow a local candidate `X.Y.Z-test` to be installed by overriding
|
|
59
|
+
* the bundled catalog source version. Only honored when the process is pointed
|
|
60
|
+
* at a local release fixture (`MOEICONS_FREE_RELEASE_DIR`), never for the
|
|
61
|
+
* production GitHub/npm path.
|
|
62
|
+
*/
|
|
63
|
+
function localFixtureSourceVersion(env) {
|
|
64
|
+
const fixture = env.MOEICONS_FREE_RELEASE_DIR;
|
|
65
|
+
const override = env.MOEICONS_SOURCE_VERSION;
|
|
66
|
+
if (!fixture || !override)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (!isLocalTestVersion(override)) {
|
|
69
|
+
throw new CliError("VALIDATION_ERROR", "MOEICONS_SOURCE_VERSION override is only allowed for local X.Y.Z-test candidates");
|
|
70
|
+
}
|
|
71
|
+
return override;
|
|
72
|
+
}
|
|
73
|
+
function freeDownloadDeps(runtime) {
|
|
74
|
+
return {
|
|
75
|
+
fetchFn: globalThis.fetch.bind(globalThis),
|
|
76
|
+
readFileSync: (path) => new Uint8Array(readFileSync(path)),
|
|
77
|
+
writeFileSync: (path, data) => writeFileSync(path, data),
|
|
78
|
+
mkdirSync: (path) => mkdirSync(path, { recursive: true }),
|
|
79
|
+
existsSync,
|
|
80
|
+
renameSync,
|
|
81
|
+
rmSync,
|
|
82
|
+
statfs: (dir) => {
|
|
83
|
+
try {
|
|
84
|
+
const stats = statfsSync(dir);
|
|
85
|
+
return { availableBytes: stats.bavail * stats.bsize };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
...(runtime.env.MOEICONS_FREE_RELEASE_DIR
|
|
92
|
+
? { fixtureDir: runtime.env.MOEICONS_FREE_RELEASE_DIR }
|
|
93
|
+
: {}),
|
|
94
|
+
cacheDir: runtime.env.MOEICONS_CACHE_DIR ?? join(homedir(), ".moeicons", "cache"),
|
|
95
|
+
cliVersion: versionString(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
42
98
|
export { MOEICONS_BANNER };
|
|
43
99
|
export const BANNER = MOEICONS_BANNER;
|
|
44
|
-
/**
|
|
100
|
+
/** Wordmark rendered for interactive wizard startup (notices come later). */
|
|
45
101
|
export function renderBanner(runtime) {
|
|
46
|
-
runtime.stdout(
|
|
102
|
+
runtime.stdout(`${renderWordmarkText({
|
|
103
|
+
columns: runtime.columns?.() ?? 80,
|
|
104
|
+
color: createTheme(isThemeEnabled(runtime.env, runtime.isTTY())).enabled,
|
|
105
|
+
})}\n`);
|
|
47
106
|
}
|
|
48
107
|
function argvRequestsJson(argv) {
|
|
49
108
|
return argv.includes("--json");
|
|
@@ -53,6 +112,13 @@ function writeJson(runtime, value) {
|
|
|
53
112
|
}
|
|
54
113
|
function reportFailure(runtime, json, error) {
|
|
55
114
|
if (isCliError(error)) {
|
|
115
|
+
if (error.code === "CANCELLED") {
|
|
116
|
+
if (json)
|
|
117
|
+
writeJson(runtime, jsonErrorBody(error.code, error.message));
|
|
118
|
+
else
|
|
119
|
+
runtime.stderr("Cancelled\n");
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
56
122
|
if (json)
|
|
57
123
|
writeJson(runtime, jsonErrorBody(error.code, error.message));
|
|
58
124
|
else
|
|
@@ -83,6 +149,31 @@ export async function main(argv, runtime) {
|
|
|
83
149
|
return reportFailure(runtime, parsed.json, error);
|
|
84
150
|
}
|
|
85
151
|
}
|
|
152
|
+
async function runBootstrap(runtime) {
|
|
153
|
+
const context = commandContext(runtime, { json: false, yes: false });
|
|
154
|
+
try {
|
|
155
|
+
const result = await runBootstrapUseCase(context, {
|
|
156
|
+
fs: {
|
|
157
|
+
existsSync,
|
|
158
|
+
readFileSync: (path) => readFileSync(path, "utf8"),
|
|
159
|
+
writeFileSync: (path, content) => writeFileSync(path, content),
|
|
160
|
+
mkdirSync: (path) => mkdirSync(path, { recursive: true }),
|
|
161
|
+
},
|
|
162
|
+
installFs: { mkdirSync, writeFileSync, existsSync, renameSync, rmSync },
|
|
163
|
+
free: freeDownloadDeps(runtime),
|
|
164
|
+
cliVersion: versionString(),
|
|
165
|
+
});
|
|
166
|
+
if (result.kind === "installed") {
|
|
167
|
+
runtime.stdout(`Installed moeicons Free v${result.version} with metadata into the current project.\n`);
|
|
168
|
+
}
|
|
169
|
+
else if (result.kind === "failed") {
|
|
170
|
+
runtime.stderr(`warning: automatic Free setup failed: ${result.message}\nRun: ${result.retry}\n`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Bootstrap must never block the wizard.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
86
177
|
async function dispatchSync(command, runtime, json, yes, noTailwind, target) {
|
|
87
178
|
switch (command.name) {
|
|
88
179
|
case "version":
|
|
@@ -97,45 +188,254 @@ async function dispatchSync(command, runtime, json, yes, noTailwind, target) {
|
|
|
97
188
|
case "wizard":
|
|
98
189
|
return await runWizard(runtime, json, yes);
|
|
99
190
|
case "install":
|
|
100
|
-
return await runInstall(command.group, runtime, json, noTailwind,
|
|
191
|
+
return await runInstall(command.group, runtime, json, noTailwind, localFixtureSourceVersion(runtime.env), undefined, command.target);
|
|
101
192
|
case "login":
|
|
102
193
|
return await runLogin(runtime, json, yes);
|
|
103
194
|
case "logout":
|
|
104
|
-
return await runLogout(runtime, json);
|
|
195
|
+
return await runLogout(runtime, json, yes);
|
|
105
196
|
case "account":
|
|
106
197
|
return await runAccount(runtime, json);
|
|
107
198
|
case "groups":
|
|
108
199
|
throw new CliError("NOT_IMPLEMENTED", "groups is not implemented yet");
|
|
109
200
|
case "generate":
|
|
110
201
|
return await runGenerate(runtime, json, noTailwind, false, target, yes);
|
|
202
|
+
case "recover": {
|
|
203
|
+
const root = runtime.cwd();
|
|
204
|
+
safeManagedPath(root, ".moeicons");
|
|
205
|
+
const recovered = withProjectLockSync(root, "recover", () => recoverManagedReconcile(root, {
|
|
206
|
+
mkdirSync,
|
|
207
|
+
writeFileSync,
|
|
208
|
+
readFileSync,
|
|
209
|
+
existsSync,
|
|
210
|
+
renameSync,
|
|
211
|
+
rmSync,
|
|
212
|
+
readdirSync,
|
|
213
|
+
copyFileSync,
|
|
214
|
+
}));
|
|
215
|
+
if (json)
|
|
216
|
+
writeJson(runtime, { ok: true, recovered });
|
|
217
|
+
else
|
|
218
|
+
runtime.stdout(`Recovered ${recovered} interrupted transaction(s). Rerun the original command.\n`);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
111
221
|
case "init":
|
|
112
|
-
return runInit(runtime, json, target);
|
|
222
|
+
return await runInit(runtime, json, yes, command.dryRun === true, target);
|
|
223
|
+
case "doctor":
|
|
224
|
+
return runDoctor(runtime, json, command.check === true, command.dryRun === true);
|
|
113
225
|
case "mcp":
|
|
114
|
-
|
|
226
|
+
await runMcp(runtime);
|
|
115
227
|
return 0;
|
|
228
|
+
case "update":
|
|
229
|
+
return await runUpdate(runtime, json, yes, command.metadata === true);
|
|
116
230
|
}
|
|
117
231
|
}
|
|
118
232
|
async function runLogin(runtime, json, yes) {
|
|
119
|
-
const
|
|
233
|
+
const context = commandContext(runtime, { json, yes });
|
|
234
|
+
const environment = describeAuthEnvironment(context);
|
|
235
|
+
const session = await runLoginUseCase(context, {
|
|
120
236
|
...runtime.auth,
|
|
121
237
|
fileFallbackAllowed: runtime.isTTY(),
|
|
122
238
|
});
|
|
123
239
|
if (json)
|
|
124
|
-
writeJson(runtime, { ok: true, account: session });
|
|
240
|
+
writeJson(runtime, { ok: true, environment, account: session });
|
|
125
241
|
else
|
|
126
|
-
runtime.stdout(`
|
|
242
|
+
runtime.stdout(`Environment: ${environment}\nLogged in as ${session.accountId}\n`);
|
|
243
|
+
if (!json && runtime.isTTY())
|
|
244
|
+
await offerProPredownload(runtime);
|
|
127
245
|
return 0;
|
|
128
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* After a successful interactive login, offer to pre-download the full Pro
|
|
249
|
+
* code + metadata. Cancelling or a failed probe never logs the user out.
|
|
250
|
+
*/
|
|
251
|
+
async function offerProPredownload(runtime) {
|
|
252
|
+
const context = commandContext(runtime, { json: false, yes: false });
|
|
253
|
+
try {
|
|
254
|
+
const versions = await fetchLibraryVersions({
|
|
255
|
+
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
256
|
+
signal: context.signal,
|
|
257
|
+
env: runtime.env,
|
|
258
|
+
});
|
|
259
|
+
if (!versions.pro) {
|
|
260
|
+
runtime.stdout("No Pro release is published yet.\n");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const descriptor = await fetchProDescriptor(context, runtime.auth ?? {}, { version: versions.pro.version, descriptorSha256: versions.pro.descriptorSha256 }, runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {});
|
|
264
|
+
const sizes = `${formatBytes(descriptor.size)} code + ${descriptor.metadata ? formatBytes(descriptor.metadata.size) : "0 B"} metadata`;
|
|
265
|
+
runtime.stdout(`Pro ${versions.pro.version} available (${sizes}).\n`);
|
|
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
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const progress = startResourceProgress(context, "Downloading Pro resources");
|
|
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.
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* W3-A: normalize the optional progress-bar surface. Falls back to the legacy
|
|
294
|
+
* line-oriented `progress` port for adapters that do not render a bar.
|
|
295
|
+
*/
|
|
296
|
+
function startResourceProgress(context, label) {
|
|
297
|
+
const bar = context.ui.progressBar?.(label, context.signal);
|
|
298
|
+
if (bar) {
|
|
299
|
+
return {
|
|
300
|
+
bytes: (event) => bar.update({
|
|
301
|
+
done: event.downloadedBytes,
|
|
302
|
+
...(event.totalBytes !== undefined ? { total: event.totalBytes } : {}),
|
|
303
|
+
...(event.detail !== undefined ? { detail: event.detail } : {}),
|
|
304
|
+
}),
|
|
305
|
+
phase: (detail) => bar.update({ detail }),
|
|
306
|
+
stop: (message) => bar.stop(message),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const legacy = context.ui.progress(label, context.signal);
|
|
310
|
+
return {
|
|
311
|
+
bytes: (event) => legacy.update?.(`Downloaded ${event.downloadedBytes} bytes${event.totalBytes ? ` of ${event.totalBytes}` : ""}${event.detail ? ` (${event.detail})` : ""}`),
|
|
312
|
+
phase: (detail) => legacy.update?.(detail),
|
|
313
|
+
stop: (message) => legacy.stop(message),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
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
|
+
*/
|
|
344
|
+
async function runProResources(runtime, yes) {
|
|
345
|
+
const context = commandContext(runtime, { json: false, yes });
|
|
346
|
+
const fetchDeps = runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {};
|
|
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");
|
|
375
|
+
try {
|
|
376
|
+
const result = await runProPredownloadUseCase(context, runtime.auth ?? {}, {
|
|
377
|
+
...fetchDeps,
|
|
378
|
+
force: state.kind === "corrupt",
|
|
379
|
+
onProgress: ({ downloadedBytes, totalBytes }) => progress.bytes({ downloadedBytes, ...(totalBytes !== undefined ? { totalBytes } : {}) }),
|
|
380
|
+
});
|
|
381
|
+
const warmed = await warmProBitmapShards(runtime, result, fetchDeps, progress);
|
|
382
|
+
progress.stop("Pro resources downloaded");
|
|
383
|
+
runtime.stdout(`Cached Pro ${result.version} code + metadata.\n`);
|
|
384
|
+
if (warmed)
|
|
385
|
+
runtime.stdout(`${warmed}\n`);
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
progress.stop("Pro download stopped");
|
|
390
|
+
if (error instanceof CliError && (error.code === "FORBIDDEN" || error.code === "AUTH_ERROR")) {
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
runtime.stderr(`warning: Pro download failed (${error instanceof Error ? error.message : String(error)}). Retry from this menu.\n`);
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
129
397
|
async function runAccount(runtime, json) {
|
|
130
|
-
const
|
|
398
|
+
const context = commandContext(runtime, { json, yes: false });
|
|
399
|
+
const environment = describeAuthEnvironment(context);
|
|
400
|
+
const session = await runAccountUseCase(context, runtime.auth);
|
|
401
|
+
// H6/account: never silently report success when the Worker entitlement
|
|
402
|
+
// lookup failed. JSON/automation fails non-zero; interactive mode says so.
|
|
403
|
+
let remote;
|
|
404
|
+
let remoteError;
|
|
405
|
+
try {
|
|
406
|
+
remote = await runRemoteAccountUseCase(commandContext(runtime, { json, yes: false }), {
|
|
407
|
+
...runtime.auth,
|
|
408
|
+
fetch: runtime.auth?.fetch ?? globalThis.fetch.bind(globalThis),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
remoteError = error;
|
|
413
|
+
}
|
|
414
|
+
if (json && remoteError)
|
|
415
|
+
throw remoteError;
|
|
416
|
+
const account = { ...session, ...(remote ?? {}) };
|
|
131
417
|
if (json)
|
|
132
|
-
writeJson(runtime, { ok: true, account
|
|
133
|
-
else
|
|
134
|
-
runtime.stdout(`
|
|
418
|
+
writeJson(runtime, { ok: true, environment, account });
|
|
419
|
+
else {
|
|
420
|
+
runtime.stdout(`Environment: ${environment}\nAccount: ${account.accountId}\nSession expires: ${new Date(account.expiresAt).toISOString()}\n`);
|
|
421
|
+
if (remote)
|
|
422
|
+
runtime.stdout(`Tier: ${account.tier}\nEntitlement: ${account.entitlementStatus}\n`);
|
|
423
|
+
else
|
|
424
|
+
runtime.stdout("Remote account: unavailable\n");
|
|
425
|
+
}
|
|
135
426
|
return 0;
|
|
136
427
|
}
|
|
137
|
-
async function runLogout(runtime, json) {
|
|
138
|
-
const
|
|
428
|
+
async function runLogout(runtime, json, yes) {
|
|
429
|
+
const context = commandContext(runtime, { json, yes });
|
|
430
|
+
// H3: interactive logout is explicit; declining leaves credentials intact.
|
|
431
|
+
if (!json && !yes && runtime.isTTY()) {
|
|
432
|
+
const confirmed = await context.ui.confirm("Log out and revoke the current session?", context.signal);
|
|
433
|
+
if (confirmed !== true) {
|
|
434
|
+
runtime.stdout("Cancelled; you are still logged in.\n");
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const result = await runLogoutUseCase(context, runtime.auth);
|
|
139
439
|
if (json)
|
|
140
440
|
writeJson(runtime, { ok: true, ...result });
|
|
141
441
|
else
|
|
@@ -144,42 +444,126 @@ async function runLogout(runtime, json) {
|
|
|
144
444
|
: "Logged out locally; remote revocation was not confirmed.\n");
|
|
145
445
|
return 0;
|
|
146
446
|
}
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Four-anchor init (E2E-B5): diagnose → plan → confirm/--yes → transactional apply.
|
|
449
|
+
* `--dry-run` never writes. Non-TTY / `--json` requires `--yes` to write.
|
|
450
|
+
*/
|
|
451
|
+
async function runInit(runtime, json, yes, dryRun = false, target) {
|
|
452
|
+
const { outcome } = buildInitPlan(runtime.cwd(), target);
|
|
453
|
+
const { report } = outcome;
|
|
454
|
+
const fixes = collectSafeFixes(outcome);
|
|
455
|
+
const manifest = report.anchors.find((a) => a.kind === "manifest");
|
|
456
|
+
if (!manifest || manifest.status === "missing" || manifest.status === "invalid") {
|
|
457
|
+
throw new CliError("VALIDATION_ERROR", "no project found");
|
|
458
|
+
}
|
|
459
|
+
if (dryRun) {
|
|
460
|
+
if (json) {
|
|
461
|
+
writeJson(runtime, {
|
|
462
|
+
...doctorJson(report),
|
|
463
|
+
mode: "dry-run",
|
|
464
|
+
diffs: plannedDiffsJson(fixes),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
runtime.stdout(`${formatDoctorReport(report)}\n`);
|
|
469
|
+
const diffs = formatPlannedDiffs(fixes);
|
|
470
|
+
if (diffs)
|
|
471
|
+
runtime.stdout(`${diffs}\n`);
|
|
472
|
+
}
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
if (fixes.length === 0) {
|
|
476
|
+
if (report.anchors.some((anchor) => anchor.status === "ambiguous"))
|
|
477
|
+
throw new CliError("VALIDATION_ERROR", "select --target before initializing this project");
|
|
151
478
|
if (json)
|
|
152
|
-
writeJson(runtime, { ok: true,
|
|
479
|
+
writeJson(runtime, { ok: true, alreadyConfigured: true, ...doctorJson(report) });
|
|
153
480
|
else
|
|
154
|
-
runtime.stdout("
|
|
481
|
+
runtime.stdout("already configured\n");
|
|
155
482
|
return 0;
|
|
156
483
|
}
|
|
157
|
-
if (!
|
|
158
|
-
|
|
484
|
+
if (!json) {
|
|
485
|
+
runtime.stdout(`${formatDoctorReport(report)}\n`);
|
|
486
|
+
const diffs = formatPlannedDiffs(fixes);
|
|
487
|
+
if (diffs)
|
|
488
|
+
runtime.stdout(`${diffs}\n`);
|
|
489
|
+
}
|
|
490
|
+
const context = commandContext(runtime, { json, yes });
|
|
491
|
+
const confirmed = await context.ui.confirm(`Apply ${fixes.length} planned change(s)?`, context.signal);
|
|
492
|
+
if (confirmed !== true) {
|
|
493
|
+
if (json)
|
|
494
|
+
writeJson(runtime, { ok: true, cancelled: true });
|
|
495
|
+
else
|
|
496
|
+
runtime.stdout("Cancelled; no files written.\n");
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
const result = runDoctorApply(runtime.cwd(), fixes);
|
|
500
|
+
if (result.ok === false) {
|
|
501
|
+
throw new CliError("UNEXPECTED", result.message);
|
|
502
|
+
}
|
|
503
|
+
if (result.alreadyConfigured) {
|
|
504
|
+
if (json)
|
|
505
|
+
writeJson(runtime, { ok: true, alreadyConfigured: true, written: [] });
|
|
506
|
+
else
|
|
507
|
+
runtime.stdout("already configured\n");
|
|
508
|
+
return 0;
|
|
159
509
|
}
|
|
160
510
|
if (json)
|
|
161
|
-
writeJson(runtime, { ok: true,
|
|
511
|
+
writeJson(runtime, { ok: true, written: result.written ?? [] });
|
|
162
512
|
else
|
|
163
|
-
runtime.stdout(`
|
|
513
|
+
runtime.stdout(`Applied ${(result.written ?? []).join(", ")}\n`);
|
|
164
514
|
return 0;
|
|
165
515
|
}
|
|
516
|
+
/** Read-only four-anchor diagnosis command (`doctor`). */
|
|
517
|
+
function runDoctor(runtime, json, check, dryRun) {
|
|
518
|
+
const { report } = runDoctorDiagnose(runtime.cwd());
|
|
519
|
+
void dryRun; // doctor is always read-only; --dry-run is accepted for parity
|
|
520
|
+
if (json)
|
|
521
|
+
writeJson(runtime, doctorJson(report));
|
|
522
|
+
else
|
|
523
|
+
runtime.stdout(formatDoctorReport(report));
|
|
524
|
+
return check ? (checkRequiresFix(report) ? 1 : 0) : 0;
|
|
525
|
+
}
|
|
166
526
|
/** Start the MCP stdio server; protocol data to stdout, logs to stderr. */
|
|
527
|
+
/**
|
|
528
|
+
* AUD-CL-01: the MCP surface exposes no single style-group install. It fails
|
|
529
|
+
* closed with guidance rather than reporting a fake success.
|
|
530
|
+
*/
|
|
531
|
+
export function createMcpServices(runtime) {
|
|
532
|
+
return createProjectMcpServices(runtime, async (argv) => {
|
|
533
|
+
let output = "";
|
|
534
|
+
const status = await main(argv, { ...runtime, isTTY: () => false, stdout: (text) => { output += text; }, stderr: (text) => runtime.stderr(String(safeMcpValue(text)).split(runtime.cwd()).join(".")) });
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(output);
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return { ok: false, code: "COMMAND_FAILED", message: `command returned ${status} without a JSON result` };
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
167
543
|
async function runMcp(runtime) {
|
|
168
544
|
const { runMcpStdio } = await import("./mcp/server.js");
|
|
545
|
+
if (runtime.mcpLines) {
|
|
546
|
+
await runMcpStdio({ services: createMcpServices(runtime), projectRoot: runtime.cwd(), stdout: runtime.stdout, stderr: runtime.stderr, lines: runtime.mcpLines, ...(runtime.signal ? { signal: runtime.signal } : {}) });
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
169
549
|
const readline = await import("node:readline");
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
{ id: "free", displayName: "Free icons" },
|
|
173
|
-
{ id: "pro", displayName: "Pro icons" },
|
|
174
|
-
]),
|
|
175
|
-
getAccount: () => Promise.resolve(undefined),
|
|
176
|
-
installIconGroup: (args) => {
|
|
177
|
-
runtime.stderr(`installing ${args.groupId} into ${args.projectPath} (stub)\n`);
|
|
178
|
-
return Promise.resolve({ ok: true, message: `installed ${args.groupId}` });
|
|
179
|
-
},
|
|
180
|
-
};
|
|
550
|
+
const controller = new AbortController();
|
|
551
|
+
const services = createMcpServices({ ...runtime, signal: controller.signal });
|
|
181
552
|
const rl = readline.createInterface({ input: process.stdin });
|
|
182
|
-
|
|
553
|
+
const stop = () => { controller.abort(); rl.close(); };
|
|
554
|
+
const eof = () => controller.abort();
|
|
555
|
+
process.once("SIGINT", stop);
|
|
556
|
+
process.once("SIGTERM", stop);
|
|
557
|
+
rl.once("close", eof);
|
|
558
|
+
try {
|
|
559
|
+
await runMcpStdio({ services, projectRoot: runtime.cwd(), stdout: runtime.stdout, stderr: runtime.stderr, lines: rl, signal: controller.signal });
|
|
560
|
+
}
|
|
561
|
+
finally {
|
|
562
|
+
process.removeListener("SIGINT", stop);
|
|
563
|
+
process.removeListener("SIGTERM", stop);
|
|
564
|
+
rl.removeListener("close", eof);
|
|
565
|
+
rl.close();
|
|
566
|
+
}
|
|
183
567
|
}
|
|
184
568
|
function versionString() {
|
|
185
569
|
return CLI_VERSION;
|
|
@@ -197,7 +581,15 @@ function renderCliUpdateNotice(update) {
|
|
|
197
581
|
}
|
|
198
582
|
/** Guided flow: Free / Pro / Login, with project-root confirmation before write. */
|
|
199
583
|
async function runWizard(runtime, json, yes) {
|
|
200
|
-
if (
|
|
584
|
+
if (json) {
|
|
585
|
+
const context = commandContext(runtime, { json, yes });
|
|
586
|
+
const result = await runWizardUseCase(context, { json: true });
|
|
587
|
+
if (result.ok && result.action === "json-hint") {
|
|
588
|
+
writeJson(runtime, { ok: true, message: result.message });
|
|
589
|
+
}
|
|
590
|
+
return 0;
|
|
591
|
+
}
|
|
592
|
+
if (runtime.isTTY()) {
|
|
201
593
|
renderBanner(runtime);
|
|
202
594
|
runtime.stdout(`CLI ${versionString()}\n`);
|
|
203
595
|
try {
|
|
@@ -212,14 +604,15 @@ async function runWizard(runtime, json, yes) {
|
|
|
212
604
|
runtime.stdout(`${renderCliUpdateNotice(update)}\n`);
|
|
213
605
|
}
|
|
214
606
|
catch {
|
|
215
|
-
runtime.stdout(`${renderNoticeBox([
|
|
607
|
+
runtime.stdout(`${renderNoticeBox([
|
|
608
|
+
"Moeicons CLI version status",
|
|
609
|
+
`Current ${versionString()} / Latest unavailable / Update: unavailable`,
|
|
610
|
+
])}\n`);
|
|
216
611
|
}
|
|
612
|
+
runtime.stdout(`${renderProjectNotice(runtime.columns?.() ?? 80)}\n`);
|
|
613
|
+
if (runtime.readLine === undefined)
|
|
614
|
+
await runBootstrap(runtime);
|
|
217
615
|
}
|
|
218
|
-
const context = commandContext(runtime, { json, yes });
|
|
219
|
-
const session = await runSessionStatusUseCase(context, runtime.auth).catch((error) => ({
|
|
220
|
-
kind: "unknown",
|
|
221
|
-
reason: error instanceof Error ? error.message : "session status unavailable",
|
|
222
|
-
}));
|
|
223
616
|
const getStatus = async () => {
|
|
224
617
|
const project = detectProject(runtime.cwd());
|
|
225
618
|
if (!project)
|
|
@@ -229,70 +622,180 @@ async function runWizard(runtime, json, yes) {
|
|
|
229
622
|
return `Current: invalid / Latest: unavailable / Status: config ${config.kind}`;
|
|
230
623
|
return formatLibraryVersionStatus(await getLibraryVersionStatus(project.root, config.config.tier));
|
|
231
624
|
};
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
625
|
+
// W3-E2: single-line preview of the most recent task, colour-coded by state.
|
|
626
|
+
const theme = createTheme(isThemeEnabled(runtime.env, runtime.isTTY()));
|
|
627
|
+
let lastTask;
|
|
628
|
+
// Single-level loop: never recurse runWizard; banner/bootstrap once per process.
|
|
629
|
+
for (;;) {
|
|
630
|
+
const context = commandContext(runtime, { json: false, yes });
|
|
631
|
+
const session = await runSessionStatusUseCase(context, runtime.auth).catch((error) => ({
|
|
632
|
+
kind: "unknown",
|
|
633
|
+
reason: error instanceof Error ? error.message : "session status unavailable",
|
|
634
|
+
}));
|
|
635
|
+
const historyLine = renderHistoryBar(lastTask, theme);
|
|
636
|
+
const result = await runWizardUseCase(context, {
|
|
637
|
+
json: false,
|
|
638
|
+
session: session.kind,
|
|
639
|
+
...(historyLine ? { historyLine } : {}),
|
|
640
|
+
getLibraryStatus: getStatus,
|
|
641
|
+
getProResourceLabel: async () => {
|
|
642
|
+
if (session.kind !== "authenticated")
|
|
643
|
+
return undefined;
|
|
644
|
+
const state = await proResourceState(context, runtime.auth ?? {}, {
|
|
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";
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
if (!result.ok) {
|
|
659
|
+
// Select/confirm cancel frames already render "■ Cancelled"; do not duplicate.
|
|
264
660
|
return 0;
|
|
265
661
|
}
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
662
|
+
if (result.action === "exit") {
|
|
663
|
+
if (result.via === "menu")
|
|
664
|
+
runtime.stdout("Exited\n");
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
if (result.action === "back")
|
|
668
|
+
continue;
|
|
669
|
+
if (result.action === "pro-resources") {
|
|
670
|
+
const code = await runProResources(runtime, yes);
|
|
671
|
+
lastTask = {
|
|
672
|
+
label: "Pro resources",
|
|
673
|
+
state: code === 0 ? "success" : "failure",
|
|
674
|
+
};
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (result.action === "install") {
|
|
678
|
+
const project = detectProject(runtime.cwd());
|
|
679
|
+
if (project)
|
|
680
|
+
runtime.stdout(`Project root: ${project.root}\n`);
|
|
681
|
+
// W3-E1: never leave the user stuck at the end of a task; return to the
|
|
682
|
+
// home menu (with the history bar) instead of exiting.
|
|
683
|
+
const code = await runInstall(result.group, runtime, false, false, undefined, undefined, result.target);
|
|
684
|
+
lastTask = {
|
|
685
|
+
label: `Install ${result.group} (${result.target})`,
|
|
686
|
+
state: code === 0 ? "success" : "failure",
|
|
687
|
+
};
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (result.action === "settings") {
|
|
691
|
+
if (result.flow === "logout") {
|
|
692
|
+
await runLogout(runtime, false, true);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const update = await runCliUpdateCheck({
|
|
696
|
+
currentVersion: versionString(),
|
|
697
|
+
cwd: runtime.cwd(),
|
|
698
|
+
env: runtime.env,
|
|
699
|
+
fs: { existsSync, readFileSync: (path) => readFileSync(path, "utf8") },
|
|
700
|
+
signal: context.signal,
|
|
701
|
+
timeoutMs: 1_000,
|
|
702
|
+
...(runtime.fetchVersions ? { fetchVersions: runtime.fetchVersions } : {}),
|
|
703
|
+
}).catch(() => undefined);
|
|
704
|
+
if (!update) {
|
|
705
|
+
runtime.stdout(`${renderNoticeBox([
|
|
706
|
+
"Moeicons CLI version status",
|
|
707
|
+
`Current ${versionString()} / Latest unavailable / Update: unavailable`,
|
|
708
|
+
])}\n`);
|
|
709
|
+
}
|
|
710
|
+
else if (update.status === "update") {
|
|
711
|
+
runtime.stdout(`CLI ${update.currentVersion} → ${update.latestVersion}\nRun: ${update.instruction}\n`);
|
|
712
|
+
}
|
|
713
|
+
else if (update.status === "current") {
|
|
714
|
+
runtime.stdout(`CLI ${update.currentVersion} is up to date.\n`);
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
runtime.stdout(`Unable to find a CLI release in the current channel.\n`);
|
|
718
|
+
}
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (result.action === "manage") {
|
|
722
|
+
if (result.flow === "reload") {
|
|
723
|
+
const code = await runGenerate(runtime, false, false, true, undefined, yes);
|
|
724
|
+
lastTask = { label: "Update project resources", state: code === 0 ? "success" : "failure" };
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const project = detectProject(runtime.cwd());
|
|
728
|
+
if (!project)
|
|
729
|
+
throw new CliError("VALIDATION_ERROR", "no project found");
|
|
730
|
+
const config = readMoeiconsConfig(project.root);
|
|
731
|
+
if (config.kind !== "ok")
|
|
732
|
+
throw new CliError("VALIDATION_ERROR", `config ${config.kind}`);
|
|
733
|
+
assertDownloadSelection(config.config);
|
|
734
|
+
const status = await getLibraryVersionStatus(project.root, config.config.tier);
|
|
735
|
+
runtime.stdout(`${formatLibraryVersionStatus(status)}\n`);
|
|
736
|
+
if (status.kind === "update") {
|
|
737
|
+
const code = await runLibraryUpdate(runtime, status.metadata.tier, status.latestVersion, status.latestDescriptorSha256);
|
|
738
|
+
lastTask = { label: "Update icon library", state: code === 0 ? "success" : "failure" };
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
lastTask = { label: "Update icon library", state: "success", detail: "already current" };
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (result.action === "pending" && result.flow === "login") {
|
|
746
|
+
const loginOutcome = await runWizardLogin(runtime, yes);
|
|
747
|
+
if (loginOutcome === "exit")
|
|
748
|
+
return 0;
|
|
749
|
+
lastTask = { label: "Login", state: loginOutcome === "home" ? "success" : "failure" };
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
272
752
|
return 0;
|
|
273
753
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Wizard-local login with Retry/Back. Explicit `moeicons login` still uses runLogin.
|
|
757
|
+
* Returns "home" to resume the wizard loop, or "exit" on cancel/Ctrl+C.
|
|
758
|
+
*/
|
|
759
|
+
async function runWizardLogin(runtime, yes) {
|
|
760
|
+
for (;;) {
|
|
761
|
+
try {
|
|
762
|
+
const code = await runLogin(runtime, false, yes);
|
|
763
|
+
if (code !== 0) {
|
|
764
|
+
// runLogin only returns non-zero via thrown errors normally; keep home on soft failure.
|
|
765
|
+
return "home";
|
|
766
|
+
}
|
|
767
|
+
return "home";
|
|
768
|
+
}
|
|
769
|
+
catch (error) {
|
|
770
|
+
if (isCliError(error) && error.code === "CANCELLED") {
|
|
771
|
+
runtime.stderr("Cancelled\n");
|
|
772
|
+
return "exit";
|
|
773
|
+
}
|
|
774
|
+
const message = isCliError(error)
|
|
775
|
+
? error.message
|
|
776
|
+
: error instanceof Error
|
|
777
|
+
? error.message
|
|
778
|
+
: String(error);
|
|
779
|
+
runtime.stderr(`Login failed: ${message}\n`);
|
|
780
|
+
const context = commandContext(runtime, { json: false, yes });
|
|
781
|
+
const choice = await context.ui.select("Login", loginRecoveryChoices(), context.signal);
|
|
782
|
+
if (choice === undefined) {
|
|
783
|
+
// Select cancel frame already rendered "■ Cancelled"; do not duplicate.
|
|
784
|
+
return "exit";
|
|
785
|
+
}
|
|
786
|
+
if (choice === "back")
|
|
787
|
+
return "home";
|
|
788
|
+
// retry → loop with a fresh session
|
|
789
|
+
}
|
|
288
790
|
}
|
|
289
|
-
if (result.flow === "login")
|
|
290
|
-
return runLogin(runtime, false, yes);
|
|
291
|
-
return 0;
|
|
292
791
|
}
|
|
293
|
-
async function runLibraryUpdate(runtime, tier, version, descriptorSha256) {
|
|
294
|
-
const context = commandContext(runtime, { json
|
|
295
|
-
|
|
792
|
+
async function runLibraryUpdate(runtime, tier, version, descriptorSha256, json = false) {
|
|
793
|
+
const context = commandContext(runtime, { json, yes: false });
|
|
794
|
+
// W3-A: a single progress surface. `task` is deliberately NOT used here: the
|
|
795
|
+
// clack adapter implements both with `p.spinner()`, so stacking them would
|
|
796
|
+
// drive two concurrent spinners over stdout. The bar's stop message already
|
|
797
|
+
// carries the success/failure status.
|
|
798
|
+
const progress = startResourceProgress(context, "Downloading icon library update");
|
|
296
799
|
try {
|
|
297
800
|
const result = await runLibraryUpdateUseCase(context, {
|
|
298
801
|
fs: {
|
|
@@ -305,24 +808,20 @@ async function runLibraryUpdate(runtime, tier, version, descriptorSha256) {
|
|
|
305
808
|
readdirSync,
|
|
306
809
|
copyFileSync,
|
|
307
810
|
},
|
|
308
|
-
free:
|
|
309
|
-
fetchFn: globalThis.fetch.bind(globalThis),
|
|
310
|
-
readFileSync: (path) => new Uint8Array(readFileSync(path)),
|
|
311
|
-
writeFileSync: (path, data) => writeFileSync(path, data),
|
|
312
|
-
mkdirSync: (path) => mkdirSync(path, { recursive: true }),
|
|
313
|
-
existsSync,
|
|
314
|
-
...(runtime.env.MOEICONS_FREE_RELEASE_DIR
|
|
315
|
-
? { fixtureDir: runtime.env.MOEICONS_FREE_RELEASE_DIR }
|
|
316
|
-
: {}),
|
|
317
|
-
cacheDir: runtime.env.MOEICONS_CACHE_DIR ?? join(homedir(), ".moeicons", "cache"),
|
|
318
|
-
cliVersion: versionString(),
|
|
319
|
-
},
|
|
811
|
+
free: freeDownloadDeps(runtime),
|
|
320
812
|
auth: runtime.auth ?? {},
|
|
321
813
|
fetch: runtime.auth?.fetch ?? globalThis.fetch.bind(globalThis),
|
|
322
|
-
onProgress: ({ downloadedBytes, totalBytes }) => progress.
|
|
814
|
+
onProgress: ({ downloadedBytes, totalBytes }) => progress.bytes({ downloadedBytes, ...(totalBytes !== undefined ? { totalBytes } : {}) }),
|
|
323
815
|
}, { tier, version, descriptorSha256 });
|
|
324
816
|
progress.stop("Icon library update complete");
|
|
325
|
-
|
|
817
|
+
if (json)
|
|
818
|
+
writeJson(runtime, { ok: true, ...result });
|
|
819
|
+
else {
|
|
820
|
+
runtime.stdout(`Updated ${tier} artifact to ${result.artifactVersion}; reconciled ${result.files.length} generated files.\n`);
|
|
821
|
+
runtime.stdout(`Download mode: ${result.downloadMode}${result.selectedFiles !== undefined ? `; ${result.selectedFiles} resource files; ${result.networkBytes} resource bytes received` : ""}.\n`);
|
|
822
|
+
for (const note of result.downloadNotes)
|
|
823
|
+
runtime.stdout(`${note}\n`);
|
|
824
|
+
}
|
|
326
825
|
return 0;
|
|
327
826
|
}
|
|
328
827
|
catch (error) {
|
|
@@ -330,15 +829,104 @@ async function runLibraryUpdate(runtime, tier, version, descriptorSha256) {
|
|
|
330
829
|
throw error;
|
|
331
830
|
}
|
|
332
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* `update` command: `update metadata` syncs only the small metadata archive for
|
|
834
|
+
* the installed version; `update` performs a full code + metadata update.
|
|
835
|
+
*/
|
|
836
|
+
async function runUpdate(runtime, json, yes, metadataOnly) {
|
|
837
|
+
const context = commandContext(runtime, { json, yes });
|
|
838
|
+
if (metadataOnly) {
|
|
839
|
+
try {
|
|
840
|
+
const result = await runMetadataSyncUseCase(context, {
|
|
841
|
+
fs: {
|
|
842
|
+
mkdirSync,
|
|
843
|
+
writeFileSync,
|
|
844
|
+
readFileSync,
|
|
845
|
+
existsSync,
|
|
846
|
+
renameSync,
|
|
847
|
+
rmSync,
|
|
848
|
+
readdirSync,
|
|
849
|
+
copyFileSync,
|
|
850
|
+
},
|
|
851
|
+
free: freeDownloadDeps(runtime),
|
|
852
|
+
auth: runtime.auth ?? {},
|
|
853
|
+
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
854
|
+
}, {});
|
|
855
|
+
if (json)
|
|
856
|
+
writeJson(runtime, { ok: true, ...result });
|
|
857
|
+
else
|
|
858
|
+
runtime.stdout(`Synced ${result.tier} metadata ${result.artifactVersion} into ${result.projectRoot}.\n`);
|
|
859
|
+
return 0;
|
|
860
|
+
}
|
|
861
|
+
catch (error) {
|
|
862
|
+
if (json)
|
|
863
|
+
return reportFailure(runtime, true, error);
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const project = detectProject(runtime.cwd());
|
|
868
|
+
if (!project)
|
|
869
|
+
throw new CliError("VALIDATION_ERROR", "no project found; run install first");
|
|
870
|
+
const config = validateConfigDocument(loadConfigDocument(project.root), undefined, {
|
|
871
|
+
lenientCatalog: true,
|
|
872
|
+
});
|
|
873
|
+
if (config.kind !== "ok")
|
|
874
|
+
throw new CliError("VALIDATION_ERROR", config.kind === "invalid" ? config.message : `config ${config.kind}; run moeicons init`);
|
|
875
|
+
assertDownloadSelection(config.config);
|
|
876
|
+
const { allowLocalTest } = resolveLocalTestContext(runtime.env);
|
|
877
|
+
const status = await getLibraryVersionStatus(project.root, config.config.tier, {
|
|
878
|
+
fetchVersions: () => fetchLibraryVersions({
|
|
879
|
+
signal: context.signal,
|
|
880
|
+
env: runtime.env,
|
|
881
|
+
allowLocalTest,
|
|
882
|
+
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
883
|
+
}),
|
|
884
|
+
});
|
|
885
|
+
if (context.signal?.aborted)
|
|
886
|
+
throw new CliError("CANCELLED", "update cancelled");
|
|
887
|
+
if (status.kind === "check-failed")
|
|
888
|
+
throw new CliError("NETWORK_ERROR", status.message);
|
|
889
|
+
if (status.kind === "installation-invalid")
|
|
890
|
+
throw new CliError("VALIDATION_ERROR", status.message);
|
|
891
|
+
if (status.kind === "current" &&
|
|
892
|
+
status.latestDescriptorSha256 !== status.metadata.descriptorSha256)
|
|
893
|
+
throw new CliError("VALIDATION_ERROR", "published identity changed for the installed version; refusing to replace the pinned release");
|
|
894
|
+
if (status.kind !== "update" && status.kind !== "current") {
|
|
895
|
+
if (json)
|
|
896
|
+
writeJson(runtime, { ok: true, status: status.kind });
|
|
897
|
+
else
|
|
898
|
+
runtime.stdout(`${formatLibraryVersionStatus(status)}\n`);
|
|
899
|
+
return 0;
|
|
900
|
+
}
|
|
901
|
+
return runLibraryUpdate(runtime, status.metadata.tier, status.latestVersion, status.latestDescriptorSha256, json);
|
|
902
|
+
}
|
|
333
903
|
/** Free install orchestration: download/verify then map to JSON/human output. */
|
|
334
904
|
async function runInstall(group, runtime, json, noTailwind, sourceVersion, expectedDescriptorSha256, target) {
|
|
335
905
|
const context = commandContext(runtime, { json, yes: false });
|
|
906
|
+
const project = detectProject(runtime.cwd());
|
|
907
|
+
if (!project)
|
|
908
|
+
throw new CliError("VALIDATION_ERROR", "no package.json found in the current directory or parents; run inside a project");
|
|
909
|
+
const bootstrap = validateConfigDocument(loadConfigDocument(project.root), undefined, {
|
|
910
|
+
lenientCatalog: true,
|
|
911
|
+
});
|
|
912
|
+
if (bootstrap.kind === "invalid")
|
|
913
|
+
throw new CliError("VALIDATION_ERROR", bootstrap.message);
|
|
914
|
+
if (bootstrap.kind === "ok")
|
|
915
|
+
assertDownloadSelection(bootstrap.config);
|
|
916
|
+
const requestedTier = group === "pro" || group === "ent" ? "pro" : "free";
|
|
917
|
+
if (bootstrap.kind === "ok" && bootstrap.config.tier !== requestedTier)
|
|
918
|
+
throw new CliError("VALIDATION_ERROR", `config.tier=${bootstrap.config.tier} conflicts with install ${requestedTier}; run moeicons install ${bootstrap.config.tier} or edit config.tier`);
|
|
919
|
+
if (bootstrap.kind === "ok" && target && bootstrap.config.target !== target)
|
|
920
|
+
throw new CliError("VALIDATION_ERROR", `config.target=${bootstrap.config.target} conflicts with --target=${target}; edit config.target before downloading`);
|
|
336
921
|
const progress = context.ui.progress("Downloading icon library", context.signal);
|
|
337
922
|
if (group === "pro" || group === "ent") {
|
|
923
|
+
const { allowLocalTest } = resolveLocalTestContext(runtime.env);
|
|
338
924
|
const identity = sourceVersion && expectedDescriptorSha256
|
|
339
925
|
? { version: sourceVersion, descriptorSha256: expectedDescriptorSha256 }
|
|
340
926
|
: await fetchLibraryVersions({
|
|
341
927
|
signal: context.signal,
|
|
928
|
+
env: runtime.env,
|
|
929
|
+
allowLocalTest,
|
|
342
930
|
...(runtime.auth?.fetch ? { fetch: runtime.auth.fetch } : {}),
|
|
343
931
|
}).then((versions) => {
|
|
344
932
|
if (!versions.pro)
|
|
@@ -350,7 +938,16 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
350
938
|
});
|
|
351
939
|
try {
|
|
352
940
|
const result = await runProInstallUseCase(context, {
|
|
353
|
-
fs: {
|
|
941
|
+
fs: {
|
|
942
|
+
mkdirSync,
|
|
943
|
+
writeFileSync,
|
|
944
|
+
existsSync,
|
|
945
|
+
renameSync,
|
|
946
|
+
rmSync,
|
|
947
|
+
readFileSync,
|
|
948
|
+
readdirSync,
|
|
949
|
+
copyFileSync,
|
|
950
|
+
},
|
|
354
951
|
auth: runtime.auth ?? {},
|
|
355
952
|
fetch: runtime.auth?.fetch ?? globalThis.fetch.bind(globalThis),
|
|
356
953
|
onProgress: ({ downloadedBytes, totalBytes }) => {
|
|
@@ -359,12 +956,20 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
359
956
|
: "";
|
|
360
957
|
progress.update?.(`Downloaded ${downloadedBytes} bytes${totalBytes ? ` of ${totalBytes}` : ""}${percent}`);
|
|
361
958
|
},
|
|
362
|
-
}, {
|
|
959
|
+
}, {
|
|
960
|
+
...identity,
|
|
961
|
+
...(allowLocalTest ? { allowLocalTest: true } : {}),
|
|
962
|
+
...(target ? { target } : {}),
|
|
963
|
+
});
|
|
363
964
|
progress.stop("Icon library download complete");
|
|
364
965
|
if (json)
|
|
365
966
|
writeJson(runtime, { ok: true, group: "pro", ...result });
|
|
366
|
-
else
|
|
967
|
+
else {
|
|
367
968
|
runtime.stdout(`Installed pro artifact ${result.artifactVersion} into ${result.projectRoot}.\n`);
|
|
969
|
+
runtime.stdout(`Download mode: ${result.downloadMode}${result.selectedFiles !== undefined ? `; ${result.selectedFiles} resource files; ${result.networkBytes} resource bytes received` : ""}.\n`);
|
|
970
|
+
for (const note of result.downloadNotes)
|
|
971
|
+
runtime.stdout(`${note}\n`);
|
|
972
|
+
}
|
|
368
973
|
return 0;
|
|
369
974
|
}
|
|
370
975
|
catch (error) {
|
|
@@ -375,16 +980,7 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
375
980
|
const result = await runInstallUseCase(context, {
|
|
376
981
|
fs: { mkdirSync, writeFileSync, existsSync, renameSync, rmSync },
|
|
377
982
|
download: {
|
|
378
|
-
|
|
379
|
-
readFileSync: (path) => new Uint8Array(readFileSync(path)),
|
|
380
|
-
writeFileSync: (path, data) => writeFileSync(path, data),
|
|
381
|
-
mkdirSync: (path) => mkdirSync(path, { recursive: true }),
|
|
382
|
-
existsSync,
|
|
383
|
-
...(runtime.env.MOEICONS_FREE_RELEASE_DIR
|
|
384
|
-
? { fixtureDir: runtime.env.MOEICONS_FREE_RELEASE_DIR }
|
|
385
|
-
: {}),
|
|
386
|
-
cacheDir: runtime.env.MOEICONS_CACHE_DIR ?? join(homedir(), ".moeicons", "cache"),
|
|
387
|
-
cliVersion: versionString(),
|
|
983
|
+
...freeDownloadDeps(runtime),
|
|
388
984
|
onProgress: ({ downloadedBytes, totalBytes }) => {
|
|
389
985
|
const percent = totalBytes
|
|
390
986
|
? ` (${Math.min(100, Math.floor((downloadedBytes / totalBytes) * 100))}%)`
|
|
@@ -414,6 +1010,9 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
414
1010
|
if (!result.ok && result.reason === "not-found") {
|
|
415
1011
|
throw new CliError("NOT_FOUND", result.message);
|
|
416
1012
|
}
|
|
1013
|
+
if (!result.ok && result.reason === "disk-full") {
|
|
1014
|
+
throw new CliError("DISK_FULL", result.message);
|
|
1015
|
+
}
|
|
417
1016
|
if (!result.ok && result.reason === "validation") {
|
|
418
1017
|
throw new CliError("VALIDATION_ERROR", result.message);
|
|
419
1018
|
}
|
|
@@ -433,7 +1032,12 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
433
1032
|
artifactVersion: result.artifactVersion,
|
|
434
1033
|
descriptorSha256: result.descriptorSha256,
|
|
435
1034
|
catalogSha256: result.catalogSha256,
|
|
1035
|
+
metadataSha256: result.metadataSha256,
|
|
436
1036
|
cacheHit: result.cacheHit,
|
|
1037
|
+
downloadMode: result.downloadMode,
|
|
1038
|
+
downloadNotes: result.downloadNotes,
|
|
1039
|
+
networkBytes: result.networkBytes,
|
|
1040
|
+
selectedFiles: result.selectedFiles,
|
|
437
1041
|
});
|
|
438
1042
|
}
|
|
439
1043
|
else {
|
|
@@ -441,6 +1045,9 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
441
1045
|
runtime.stdout(`Group: ${result.group}\n`);
|
|
442
1046
|
runtime.stdout(`Artifact: ${result.artifactVersion}\n`);
|
|
443
1047
|
runtime.stdout(`Config state: ${result.config}\n`);
|
|
1048
|
+
runtime.stdout(`Download mode: ${result.downloadMode}${result.selectedFiles !== undefined ? `; ${result.selectedFiles} resource files; ${result.networkBytes} resource bytes received` : ""}.\n`);
|
|
1049
|
+
for (const note of result.downloadNotes ?? [])
|
|
1050
|
+
runtime.stdout(`${note}\n`);
|
|
444
1051
|
runtime.stdout(`Installed free artifact metadata to .moeicons (cacheHit=${String(result.cacheHit)}).\n`);
|
|
445
1052
|
}
|
|
446
1053
|
return 0;
|
|
@@ -448,24 +1055,42 @@ async function runInstall(group, runtime, json, noTailwind, sourceVersion, expec
|
|
|
448
1055
|
function generateFailureCode(reason) {
|
|
449
1056
|
if (reason === "cancelled")
|
|
450
1057
|
return "CANCELLED";
|
|
451
|
-
if (reason === "validation" ||
|
|
1058
|
+
if (reason === "validation" ||
|
|
1059
|
+
reason === "no-project" ||
|
|
1060
|
+
reason.startsWith("config state:") ||
|
|
1061
|
+
reason.startsWith("installed artifact targets") ||
|
|
1062
|
+
reason.startsWith("generate requires an installed")) {
|
|
452
1063
|
return "VALIDATION_ERROR";
|
|
453
1064
|
}
|
|
454
1065
|
return "UNEXPECTED";
|
|
455
1066
|
}
|
|
456
1067
|
/** Generate proxy components from config. */
|
|
457
1068
|
async function runGenerate(runtime, json, noTailwind, reconcileInstalled = false, target, yes = false) {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
1069
|
+
const context = commandContext(runtime, { json, yes });
|
|
1070
|
+
// W3-C: generation has no byte progress, so the strict task status covers it.
|
|
1071
|
+
const task = !json && runtime.readLine === undefined
|
|
1072
|
+
? context.ui.task?.("Generating icon components", context.signal)
|
|
1073
|
+
: undefined;
|
|
1074
|
+
let result;
|
|
1075
|
+
try {
|
|
1076
|
+
// W3-C: generation has no byte progress, so its status is the task handle.
|
|
1077
|
+
result = await runGenerateUseCase(context, {
|
|
1078
|
+
mkdirSync,
|
|
1079
|
+
writeFileSync,
|
|
1080
|
+
readFileSync,
|
|
1081
|
+
existsSync,
|
|
1082
|
+
renameSync,
|
|
1083
|
+
rmSync,
|
|
1084
|
+
readdirSync,
|
|
1085
|
+
copyFileSync,
|
|
1086
|
+
}, { noTailwind, reconcileInstalled, ...(target ? { target } : {}) });
|
|
1087
|
+
}
|
|
1088
|
+
catch (error) {
|
|
1089
|
+
task?.fail(error instanceof Error ? error.message : String(error));
|
|
1090
|
+
throw error;
|
|
1091
|
+
}
|
|
468
1092
|
if (!result.ok) {
|
|
1093
|
+
task?.fail(result.reason);
|
|
469
1094
|
if (result.code === "TAILWIND_VERSION_UNSUPPORTED") {
|
|
470
1095
|
if (json)
|
|
471
1096
|
writeJson(runtime, jsonErrorBody("TAILWIND_VERSION_UNSUPPORTED", result.reason));
|
|
@@ -497,6 +1122,7 @@ async function runGenerate(runtime, json, noTailwind, reconcileInstalled = false
|
|
|
497
1122
|
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
498
1123
|
});
|
|
499
1124
|
else {
|
|
1125
|
+
task?.succeed(`Generated ${result.files.length} files`);
|
|
500
1126
|
runtime.stdout(`Generated ${result.files.length} files.\n`);
|
|
501
1127
|
if (result.warnings) {
|
|
502
1128
|
for (const warning of result.warnings)
|