@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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ReleaseTarget, ReleaseTargetMetadata } from "./release-descriptor.js";
|
|
2
2
|
import type { Target } from "../commands/parser.js";
|
|
3
|
+
import type { MoeiconsConfigFile } from "../project/config.js";
|
|
4
|
+
import { type IconCatalog } from "../catalog/catalog.js";
|
|
3
5
|
/**
|
|
4
6
|
* Select the target subtree the CLI must install. Mirrors the archive layout
|
|
5
7
|
* frozen in the code-library contract: each tier archive contains
|
|
@@ -27,6 +29,12 @@ export type TargetSubtreeFailure = {
|
|
|
27
29
|
readonly message: string;
|
|
28
30
|
};
|
|
29
31
|
export type TargetSubtreeResult = TargetSubtreeSelection | TargetSubtreeFailure;
|
|
32
|
+
/** Keep only the component modules reachable from configured icon proxies.
|
|
33
|
+
* The full target subtree must be verified against its release descriptor
|
|
34
|
+
* before this projection is called; this is a local installation filter, not
|
|
35
|
+
* a replacement for the immutable archive digest.
|
|
36
|
+
*/
|
|
37
|
+
export declare function configuredComponentFiles(files: Readonly<Record<string, Uint8Array>>, target: Target, config: MoeiconsConfigFile | undefined, catalog: IconCatalog): Readonly<Record<string, Uint8Array>>;
|
|
30
38
|
/**
|
|
31
39
|
* Deterministic subtree hash over subtree-relative files. Files and directories
|
|
32
40
|
* are visited depth-first sorted by `localeCompare`, and each file contributes
|
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { extractTarGz } from "../project/tar-gz.js";
|
|
2
|
+
import { extractTarGz, ICON_ARCHIVE_MAX_ENTRIES, ICON_ARCHIVE_MAX_EXPANDED_BYTES } from "../project/tar-gz.js";
|
|
3
|
+
import { posix } from "node:path";
|
|
4
|
+
import { toProxyName } from "./icon-names.js";
|
|
5
|
+
import { findCatalogStyleGroup } from "../catalog/catalog.js";
|
|
6
|
+
import { resolveIconTheme } from "./icon-selection.js";
|
|
7
|
+
/** Keep only the component modules reachable from configured icon proxies.
|
|
8
|
+
* The full target subtree must be verified against its release descriptor
|
|
9
|
+
* before this projection is called; this is a local installation filter, not
|
|
10
|
+
* a replacement for the immutable archive digest.
|
|
11
|
+
*/
|
|
12
|
+
export function configuredComponentFiles(files, target, config, catalog) {
|
|
13
|
+
if (!config || (target !== "react" && target !== "vue"))
|
|
14
|
+
return files;
|
|
15
|
+
const selected = new Set();
|
|
16
|
+
const visit = (file) => {
|
|
17
|
+
if (selected.has(file))
|
|
18
|
+
return;
|
|
19
|
+
const bytes = files[file];
|
|
20
|
+
if (!bytes)
|
|
21
|
+
throw new Error(`verified ${target} archive is missing configured module: ${file}`);
|
|
22
|
+
selected.add(file);
|
|
23
|
+
const source = Buffer.from(bytes).toString("utf8");
|
|
24
|
+
const imports = /(?:\b(?:import|export)\s+(?:[^'";]*?\s+from\s*)?|\bimport\s*\()\s*["'](\.[^"']+)["']/g;
|
|
25
|
+
for (const match of source.matchAll(imports)) {
|
|
26
|
+
const specifier = match[1];
|
|
27
|
+
if (!specifier)
|
|
28
|
+
continue;
|
|
29
|
+
const resolved = posix.normalize(posix.join(posix.dirname(file), specifier));
|
|
30
|
+
if (resolved.startsWith("../") || resolved === "..")
|
|
31
|
+
throw new Error(`component import escapes target: ${file} → ${specifier}`);
|
|
32
|
+
const dependency = files[resolved] ? resolved : files[`${resolved}.d.ts`] ? `${resolved}.d.ts` : undefined;
|
|
33
|
+
if (!dependency)
|
|
34
|
+
throw new Error(`verified ${target} archive is missing dependency: ${file} → ${specifier}`);
|
|
35
|
+
visit(dependency);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
visit("types.d.ts");
|
|
39
|
+
for (const iconId of config.icons) {
|
|
40
|
+
for (const theme of Object.keys(config.themes)) {
|
|
41
|
+
const actual = resolveIconTheme(config, catalog, theme, iconId);
|
|
42
|
+
if (!actual)
|
|
43
|
+
throw new Error(`icon "${iconId}" has no selected variant for theme "${theme}"`);
|
|
44
|
+
const group = config.themes[actual].styleGroup;
|
|
45
|
+
if (findCatalogStyleGroup(group, catalog)?.type === "bitmap")
|
|
46
|
+
continue;
|
|
47
|
+
const basename = `${group}/${toProxyName(iconId)}${target === "vue" ? ".vue" : ""}`;
|
|
48
|
+
visit(`${basename}.js`);
|
|
49
|
+
visit(`${basename}.d.ts`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Object.fromEntries([...selected].sort().map((file) => [file, files[file]]));
|
|
53
|
+
}
|
|
3
54
|
/**
|
|
4
55
|
* Deterministic subtree hash over subtree-relative files. Files and directories
|
|
5
56
|
* are visited depth-first sorted by `localeCompare`, and each file contributes
|
|
@@ -65,8 +116,8 @@ function hasModernTargetContract(source) {
|
|
|
65
116
|
*/
|
|
66
117
|
export function selectTargetSubtree(archiveBytes, source, target) {
|
|
67
118
|
const extracted = extractTarGz(archiveBytes, {
|
|
68
|
-
maxEntries:
|
|
69
|
-
maxExpandedBytes:
|
|
119
|
+
maxEntries: ICON_ARCHIVE_MAX_ENTRIES,
|
|
120
|
+
maxExpandedBytes: ICON_ARCHIVE_MAX_EXPANDED_BYTES,
|
|
70
121
|
});
|
|
71
122
|
if (extracted.errors.length > 0) {
|
|
72
123
|
return { ok: false, reason: "validation", message: extracted.errors[0] ?? "invalid archive" };
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test seam (candidate acceptance): `MOEICONS_LIBRARY_VERSIONS_URL` overrides
|
|
3
|
+
* the public versions endpoint. Only an https URL or a loopback http URL is
|
|
4
|
+
* accepted, so production behavior is unchanged.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveVersionsEndpoint(env: Readonly<Record<string, string | undefined>>): string;
|
|
1
7
|
export interface PublicTierVersion {
|
|
2
8
|
readonly version: string;
|
|
3
9
|
readonly releasedAt: string;
|
|
4
10
|
readonly descriptorSha256: string;
|
|
11
|
+
/** A-1b: present only for an accepted local-test candidate. */
|
|
12
|
+
readonly channel?: "local-test";
|
|
13
|
+
readonly publishable?: boolean;
|
|
5
14
|
}
|
|
6
15
|
export interface PublicLibraryVersions {
|
|
7
16
|
readonly schemaVersion: 1;
|
|
@@ -12,6 +21,8 @@ export declare function fetchLibraryVersions(deps?: {
|
|
|
12
21
|
fetch?: typeof fetch;
|
|
13
22
|
signal?: AbortSignal;
|
|
14
23
|
timeoutMs?: number;
|
|
24
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
25
|
+
allowLocalTest?: boolean;
|
|
15
26
|
}): Promise<PublicLibraryVersions>;
|
|
16
27
|
export declare function fetchMoeiconsVersions(deps?: {
|
|
17
28
|
fetch?: typeof fetch;
|
|
@@ -3,19 +3,93 @@ import { parseVersion } from "./update-policy.js";
|
|
|
3
3
|
const LIBRARY_VERSIONS_URL = "https://api.moeicons.com/v1/icon-library/versions";
|
|
4
4
|
const NPM_PACKAGE_URL = "https://registry.npmjs.org/@moewolf%2fmoe-icons-cli";
|
|
5
5
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
6
|
+
/** A-1b: local-test candidate versions; only accepted with an explicit local context. */
|
|
7
|
+
const LOCAL_TEST_VERSION = /^\d+\.\d+\.\d+-test$/;
|
|
6
8
|
const CLI_VERSION_CACHE_TTL_MS = 5 * 60 * 1_000;
|
|
7
9
|
let cliVersionCache;
|
|
8
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Test seam (candidate acceptance): `MOEICONS_LIBRARY_VERSIONS_URL` overrides
|
|
12
|
+
* the public versions endpoint. Only an https URL or a loopback http URL is
|
|
13
|
+
* accepted, so production behavior is unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveVersionsEndpoint(env) {
|
|
16
|
+
const override = env.MOEICONS_LIBRARY_VERSIONS_URL;
|
|
17
|
+
if (!override)
|
|
18
|
+
return LIBRARY_VERSIONS_URL;
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = new URL(override);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new CliError("VALIDATION_ERROR", "MOEICONS_LIBRARY_VERSIONS_URL is not a valid URL");
|
|
25
|
+
}
|
|
26
|
+
const loopback = parsed.protocol === "http:" && ["127.0.0.1", "localhost", "::1"].includes(parsed.hostname);
|
|
27
|
+
if (parsed.protocol !== "https:" && !loopback) {
|
|
28
|
+
throw new CliError("VALIDATION_ERROR", "MOEICONS_LIBRARY_VERSIONS_URL must be https or loopback http");
|
|
29
|
+
}
|
|
30
|
+
return override;
|
|
31
|
+
}
|
|
32
|
+
async function fixedJson(url, fetchFn, maxBytes, signal, timeoutMs = 5_000) {
|
|
9
33
|
const controller = new AbortController();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
34
|
+
let reader;
|
|
35
|
+
let rejectAbort = () => { };
|
|
36
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
37
|
+
rejectAbort = reject;
|
|
38
|
+
});
|
|
39
|
+
const abort = () => {
|
|
13
40
|
controller.abort();
|
|
41
|
+
void reader?.cancel().catch(() => { });
|
|
42
|
+
rejectAbort(new CliError(signal?.aborted ? "CANCELLED" : "NETWORK_ERROR", signal?.aborted ? "version check cancelled" : "version check timed out"));
|
|
43
|
+
};
|
|
44
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
14
45
|
signal?.addEventListener("abort", abort, { once: true });
|
|
46
|
+
const operation = async () => {
|
|
47
|
+
if (signal?.aborted)
|
|
48
|
+
throw new CliError("CANCELLED", "version check cancelled");
|
|
49
|
+
const response = await fetchFn(url, {
|
|
50
|
+
method: "GET",
|
|
51
|
+
headers: { accept: "application/json" },
|
|
52
|
+
signal: controller.signal,
|
|
53
|
+
redirect: "error",
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
void response.body?.cancel().catch(() => { });
|
|
57
|
+
throw new CliError("NETWORK_ERROR", `version check failed with ${response.status}`);
|
|
58
|
+
}
|
|
59
|
+
const declared = response.headers.get("content-length");
|
|
60
|
+
if (declared && (!/^\d+$/.test(declared) || Number(declared) > maxBytes)) {
|
|
61
|
+
void response.body?.cancel().catch(() => { });
|
|
62
|
+
throw new CliError("VALIDATION_ERROR", "version response exceeds size limit");
|
|
63
|
+
}
|
|
64
|
+
if (!response.body)
|
|
65
|
+
throw new CliError("VALIDATION_ERROR", "empty version response");
|
|
66
|
+
reader = response.body.getReader();
|
|
67
|
+
const chunks = [];
|
|
68
|
+
let total = 0;
|
|
69
|
+
for (;;) {
|
|
70
|
+
const part = await reader.read();
|
|
71
|
+
if (part.done)
|
|
72
|
+
break;
|
|
73
|
+
total += part.value.byteLength;
|
|
74
|
+
if (total > maxBytes) {
|
|
75
|
+
void reader.cancel().catch(() => { });
|
|
76
|
+
throw new CliError("VALIDATION_ERROR", "version response exceeds size limit");
|
|
77
|
+
}
|
|
78
|
+
chunks.push(part.value);
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw new CliError("VALIDATION_ERROR", "invalid version response JSON");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
15
87
|
try {
|
|
16
|
-
return await
|
|
88
|
+
return await Promise.race([operation(), interrupted]);
|
|
17
89
|
}
|
|
18
|
-
catch {
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof CliError)
|
|
92
|
+
throw error;
|
|
19
93
|
throw new CliError(signal?.aborted ? "CANCELLED" : "NETWORK_ERROR", signal?.aborted ? "version check cancelled" : "version check failed");
|
|
20
94
|
}
|
|
21
95
|
finally {
|
|
@@ -23,34 +97,61 @@ async function fixedFetch(url, fetchFn, signal, timeoutMs = 5_000) {
|
|
|
23
97
|
signal?.removeEventListener("abort", abort);
|
|
24
98
|
}
|
|
25
99
|
}
|
|
26
|
-
function tier(value) {
|
|
100
|
+
function tier(value, allowLocalTest) {
|
|
27
101
|
if (value === null)
|
|
28
102
|
return null;
|
|
29
103
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
30
104
|
return undefined;
|
|
31
105
|
const item = value;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
106
|
+
const baseValid = typeof item.version === "string" &&
|
|
107
|
+
typeof item.releasedAt === "string" &&
|
|
108
|
+
/^\d{4}-\d{2}-\d{2}T.*Z$/.test(item.releasedAt) &&
|
|
109
|
+
typeof item.descriptorSha256 === "string" &&
|
|
110
|
+
SHA256.test(item.descriptorSha256);
|
|
111
|
+
if (!baseValid)
|
|
112
|
+
return undefined;
|
|
113
|
+
const version = item.version;
|
|
114
|
+
if (parseVersion(version)) {
|
|
115
|
+
// A formal (stable/alpha/beta) version must never carry the local-test marker.
|
|
116
|
+
if (item.channel !== undefined || item.publishable !== undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
return item;
|
|
119
|
+
}
|
|
120
|
+
// A-1b: an `X.Y.Z-test` version is only accepted when the caller proved a
|
|
121
|
+
// local-only context and the release explicitly declares the local-test model.
|
|
122
|
+
if (allowLocalTest &&
|
|
123
|
+
LOCAL_TEST_VERSION.test(version) &&
|
|
124
|
+
item.channel === "local-test" &&
|
|
125
|
+
item.publishable === false) {
|
|
126
|
+
return item;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
35
129
|
}
|
|
36
130
|
export async function fetchLibraryVersions(deps = {}) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
131
|
+
const url = resolveVersionsEndpoint(deps.env ?? {});
|
|
132
|
+
const allowLocalTest = deps.allowLocalTest === true;
|
|
133
|
+
const raw = await fixedJson(url, deps.fetch ?? fetch, 32 * 1024, deps.signal, deps.timeoutMs);
|
|
134
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
135
|
+
throw new CliError("VALIDATION_ERROR", "invalid icon library versions response");
|
|
136
|
+
const value = raw;
|
|
137
|
+
const free = tier(value.free, allowLocalTest);
|
|
138
|
+
const pro = tier(value.pro, allowLocalTest);
|
|
139
|
+
if (value.schemaVersion !== 1 ||
|
|
140
|
+
free === undefined ||
|
|
141
|
+
pro === undefined ||
|
|
142
|
+
Object.keys(value).some((key) => !["schemaVersion", "free", "pro"].includes(key))) {
|
|
44
143
|
throw new CliError("VALIDATION_ERROR", "invalid icon library versions response");
|
|
45
144
|
}
|
|
46
145
|
return { schemaVersion: 1, free, pro };
|
|
47
146
|
}
|
|
48
147
|
export async function fetchMoeiconsVersions(deps = {}) {
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
51
|
-
throw new CliError("
|
|
52
|
-
const value =
|
|
53
|
-
if (typeof value.versions !== "object" ||
|
|
148
|
+
const raw = await fixedJson(NPM_PACKAGE_URL, deps.fetch ?? fetch, 8 * 1024 * 1024, deps.signal, deps.timeoutMs);
|
|
149
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
150
|
+
throw new CliError("VALIDATION_ERROR", "invalid npm package metadata");
|
|
151
|
+
const value = raw;
|
|
152
|
+
if (typeof value.versions !== "object" ||
|
|
153
|
+
value.versions === null ||
|
|
154
|
+
Array.isArray(value.versions))
|
|
54
155
|
throw new CliError("VALIDATION_ERROR", "invalid npm package metadata");
|
|
55
156
|
return Object.keys(value.versions).filter((version) => parseVersion(version));
|
|
56
157
|
}
|
|
@@ -66,6 +167,10 @@ export function latestInChannel(current, versions) {
|
|
|
66
167
|
const parsed = parseVersion(current);
|
|
67
168
|
if (!parsed)
|
|
68
169
|
return undefined;
|
|
69
|
-
return versions
|
|
70
|
-
.
|
|
170
|
+
return versions
|
|
171
|
+
.map((version) => ({ version, parsed: parseVersion(version) }))
|
|
172
|
+
.filter((item) => item.parsed?.channel === parsed.channel)
|
|
173
|
+
.sort((a, b) => b.parsed.major - a.parsed.major ||
|
|
174
|
+
b.parsed.minor - a.parsed.minor ||
|
|
175
|
+
b.parsed.patch - a.parsed.patch)[0]?.version;
|
|
71
176
|
}
|
package/dist/core/wizard.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CommandContext } from "./context.js";
|
|
1
|
+
import type { CommandContext, UiChoice } from "./context.js";
|
|
2
2
|
export type WizardResult = {
|
|
3
3
|
readonly ok: true;
|
|
4
4
|
readonly action: "json-hint";
|
|
@@ -12,6 +12,9 @@ export type WizardResult = {
|
|
|
12
12
|
readonly ok: true;
|
|
13
13
|
readonly action: "pending";
|
|
14
14
|
readonly flow: "login";
|
|
15
|
+
} | {
|
|
16
|
+
readonly ok: true;
|
|
17
|
+
readonly action: "pro-resources";
|
|
15
18
|
} | {
|
|
16
19
|
readonly ok: true;
|
|
17
20
|
readonly action: "manage";
|
|
@@ -20,30 +23,30 @@ export type WizardResult = {
|
|
|
20
23
|
readonly ok: true;
|
|
21
24
|
readonly action: "settings";
|
|
22
25
|
readonly flow: "logout" | "cli-update";
|
|
26
|
+
} | {
|
|
27
|
+
readonly ok: true;
|
|
28
|
+
readonly action: "back";
|
|
29
|
+
} | {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly action: "exit";
|
|
32
|
+
readonly via: "menu" | "cancel";
|
|
23
33
|
} | {
|
|
24
34
|
readonly ok: false;
|
|
25
35
|
readonly reason: "cancelled";
|
|
26
36
|
};
|
|
27
37
|
export type WizardSessionState = "authenticated" | "signed-out" | "unknown";
|
|
28
|
-
export declare function homeChoices(session: WizardSessionState):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
readonly label: "Install moeicons free";
|
|
34
|
-
}, {
|
|
35
|
-
readonly value: "manage";
|
|
36
|
-
readonly label: "Manage project icons";
|
|
37
|
-
}, ...{
|
|
38
|
-
value: string;
|
|
39
|
-
label: string;
|
|
40
|
-
}[], {
|
|
41
|
-
readonly value: "settings";
|
|
42
|
-
readonly label: "Settings";
|
|
43
|
-
}];
|
|
38
|
+
export declare function homeChoices(session: WizardSessionState, proResourcesLabel?: string): UiChoice[];
|
|
39
|
+
export declare function settingsChoices(session: WizardSessionState): UiChoice[];
|
|
40
|
+
export declare function manageChoices(status?: string): UiChoice[];
|
|
41
|
+
export declare function targetChoices(): UiChoice[];
|
|
42
|
+
export declare function loginRecoveryChoices(): UiChoice[];
|
|
44
43
|
/** Wizard state machine. No Clack/Commander/process imports. */
|
|
44
|
+
export declare const HOME_MESSAGE = "Choose an option";
|
|
45
45
|
export declare function runWizardUseCase(context: CommandContext, options: {
|
|
46
46
|
readonly json: boolean;
|
|
47
47
|
readonly session?: WizardSessionState;
|
|
48
48
|
readonly getLibraryStatus?: () => Promise<string>;
|
|
49
|
+
readonly getProResourceLabel?: () => Promise<string | undefined>;
|
|
50
|
+
/** W3-E2: optional single-line recent-task status shown above the menu. */
|
|
51
|
+
readonly historyLine?: string;
|
|
49
52
|
}): Promise<WizardResult>;
|
package/dist/core/wizard.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { detectProject } from "../project/detect.js";
|
|
2
2
|
import { CliError } from "../errors/index.js";
|
|
3
3
|
const JSON_HINT = "interactive wizard unavailable in JSON mode; use install/login/account/groups/generate";
|
|
4
|
-
|
|
4
|
+
const BACK = { value: "back", label: "Back", separatorBefore: true };
|
|
5
|
+
const EXIT = { value: "exit", label: "Exit" };
|
|
6
|
+
export function homeChoices(session, proResourcesLabel) {
|
|
5
7
|
return [
|
|
6
8
|
{ value: "pro", label: "Install moeicons pro" },
|
|
7
9
|
{ value: "free", label: "Install moeicons free" },
|
|
10
|
+
...(session === "authenticated" && proResourcesLabel
|
|
11
|
+
? [{ value: "pro-resources", label: proResourcesLabel }]
|
|
12
|
+
: []),
|
|
8
13
|
{ value: "manage", label: "Manage project icons" },
|
|
9
14
|
...(session === "authenticated"
|
|
10
15
|
? []
|
|
@@ -15,56 +20,89 @@ export function homeChoices(session) {
|
|
|
15
20
|
},
|
|
16
21
|
]),
|
|
17
22
|
{ value: "settings", label: "Settings" },
|
|
23
|
+
EXIT,
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
export function settingsChoices(session) {
|
|
27
|
+
return [
|
|
28
|
+
...(session === "authenticated" ? [{ value: "logout", label: "Log out" }] : []),
|
|
29
|
+
{ value: "cli-update", label: "Check for CLI updates" },
|
|
30
|
+
BACK,
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function manageChoices(status) {
|
|
34
|
+
return [
|
|
35
|
+
{ value: "reload", label: "Update project resources" },
|
|
36
|
+
{
|
|
37
|
+
value: "library-update",
|
|
38
|
+
label: `Update icon library version${status ? ` — ${status}` : ""}`,
|
|
39
|
+
},
|
|
40
|
+
BACK,
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
export function targetChoices() {
|
|
44
|
+
return [
|
|
45
|
+
{ value: "react", label: "React" },
|
|
46
|
+
{ value: "vue", label: "Vue" },
|
|
47
|
+
{ value: "vanilla", label: "Vanilla" },
|
|
48
|
+
{ value: "assets", label: "Static assets" },
|
|
49
|
+
BACK,
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
export function loginRecoveryChoices() {
|
|
53
|
+
return [
|
|
54
|
+
{ value: "retry", label: "Retry login" },
|
|
55
|
+
BACK,
|
|
18
56
|
];
|
|
19
57
|
}
|
|
20
58
|
/** Wizard state machine. No Clack/Commander/process imports. */
|
|
59
|
+
export const HOME_MESSAGE = "Choose an option";
|
|
21
60
|
export async function runWizardUseCase(context, options) {
|
|
22
61
|
if (options.json) {
|
|
23
62
|
return { ok: true, action: "json-hint", message: JSON_HINT };
|
|
24
63
|
}
|
|
25
|
-
const
|
|
64
|
+
const session = options.session ?? "signed-out";
|
|
65
|
+
const proLabel = session === "authenticated" ? await options.getProResourceLabel?.().catch(() => undefined) : undefined;
|
|
66
|
+
const message = options.historyLine ? `${options.historyLine}\n\n${HOME_MESSAGE}` : HOME_MESSAGE;
|
|
67
|
+
const choice = await context.ui.select(message, homeChoices(session, proLabel), context.signal);
|
|
68
|
+
// Esc on home == Exit (cancel path); explicit Exit menu item uses via=menu.
|
|
26
69
|
if (choice === undefined)
|
|
27
|
-
return { ok:
|
|
70
|
+
return { ok: true, action: "exit", via: "cancel" };
|
|
71
|
+
if (choice === "exit")
|
|
72
|
+
return { ok: true, action: "exit", via: "menu" };
|
|
28
73
|
if (choice === "login")
|
|
29
74
|
return { ok: true, action: "pending", flow: "login" };
|
|
75
|
+
if (choice === "pro-resources")
|
|
76
|
+
return { ok: true, action: "pro-resources" };
|
|
30
77
|
if (choice === "settings") {
|
|
31
|
-
const setting = await context.ui.select("Settings",
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
return setting === undefined
|
|
36
|
-
? { ok: false, reason: "cancelled" }
|
|
37
|
-
: { ok: true, action: "settings", flow: setting };
|
|
78
|
+
const setting = await context.ui.select("Settings", settingsChoices(session), context.signal);
|
|
79
|
+
if (setting === undefined || setting === "back")
|
|
80
|
+
return { ok: true, action: "back" };
|
|
81
|
+
return { ok: true, action: "settings", flow: setting };
|
|
38
82
|
}
|
|
39
83
|
const project = detectProject(context.cwd);
|
|
40
84
|
if (!project)
|
|
41
85
|
throw new CliError("VALIDATION_ERROR", "no package.json found in the current directory or parents; run inside a project");
|
|
42
86
|
if (choice === "manage") {
|
|
43
87
|
const status = await options.getLibraryStatus?.();
|
|
44
|
-
const management = await context.ui.select("Manage project icons",
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
label: `Update icon library version${status ? ` — ${status}` : ""}`,
|
|
49
|
-
},
|
|
50
|
-
], context.signal);
|
|
51
|
-
return management === undefined
|
|
52
|
-
? { ok: false, reason: "cancelled" }
|
|
53
|
-
: { ok: true, action: "manage", flow: management };
|
|
88
|
+
const management = await context.ui.select("Manage project icons", manageChoices(status), context.signal);
|
|
89
|
+
if (management === undefined || management === "back")
|
|
90
|
+
return { ok: true, action: "back" };
|
|
91
|
+
return { ok: true, action: "manage", flow: management };
|
|
54
92
|
}
|
|
55
93
|
if (choice === "free" || choice === "pro") {
|
|
56
|
-
const target = await context.ui.select("Choose an output target",
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
{ value: "vanilla", label: "Vanilla" },
|
|
60
|
-
{ value: "assets", label: "Static assets" },
|
|
61
|
-
], context.signal);
|
|
94
|
+
const target = await context.ui.select("Choose an output target", targetChoices(), context.signal);
|
|
95
|
+
if (target === undefined || target === "back")
|
|
96
|
+
return { ok: true, action: "back" };
|
|
62
97
|
if (target !== "react" && target !== "vue" && target !== "vanilla" && target !== "assets") {
|
|
63
|
-
return { ok:
|
|
98
|
+
return { ok: true, action: "back" };
|
|
64
99
|
}
|
|
65
100
|
const confirmed = await context.ui.confirm(`Install ${choice} ${target} into ${project.root}?`, context.signal);
|
|
66
|
-
|
|
101
|
+
// Esc / Ctrl+C on confirm exits; No returns to home (back)
|
|
102
|
+
if (confirmed === undefined)
|
|
67
103
|
return { ok: false, reason: "cancelled" };
|
|
104
|
+
if (confirmed !== true)
|
|
105
|
+
return { ok: true, action: "back" };
|
|
68
106
|
return { ok: true, action: "install", group: choice, target };
|
|
69
107
|
}
|
|
70
108
|
throw new CliError("VALIDATION_ERROR", `unknown wizard choice: ${choice}`);
|
package/dist/errors/errors.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 0 success/cancel, 1 validation error, 2 auth error, 3 network error,
|
|
4
4
|
* 4 not found, 5 unexpected.
|
|
5
5
|
*/
|
|
6
|
-
export type CliErrorCode = "HELP" | "VERSION" | "CANCELLED" | "VALIDATION_ERROR" | "TAILWIND_VERSION_UNSUPPORTED" | "AUTH_ERROR" | "NETWORK_ERROR" | "NOT_FOUND" | "FORBIDDEN" | "NOT_TTY" | "NOT_IMPLEMENTED" | "UNEXPECTED";
|
|
6
|
+
export type CliErrorCode = "HELP" | "VERSION" | "CANCELLED" | "VALIDATION_ERROR" | "TAILWIND_VERSION_UNSUPPORTED" | "AUTH_ERROR" | "NETWORK_ERROR" | "NOT_FOUND" | "FORBIDDEN" | "NOT_TTY" | "NOT_IMPLEMENTED" | "DISK_FULL" | "UNEXPECTED";
|
|
7
7
|
/** Frozen CliErrorCode → process exit code. Register new codes here before use. */
|
|
8
8
|
export declare const CLI_ERROR_EXIT_MAP: {
|
|
9
9
|
readonly HELP: 0;
|
|
@@ -17,6 +17,7 @@ export declare const CLI_ERROR_EXIT_MAP: {
|
|
|
17
17
|
readonly FORBIDDEN: 2;
|
|
18
18
|
readonly NETWORK_ERROR: 3;
|
|
19
19
|
readonly NOT_FOUND: 4;
|
|
20
|
+
readonly DISK_FULL: 5;
|
|
20
21
|
readonly UNEXPECTED: 5;
|
|
21
22
|
};
|
|
22
23
|
export type JsonErrorBody = {
|
package/dist/errors/errors.js
CHANGED
|
@@ -6,7 +6,7 @@ export { toLibraryExportName, toProxyName };
|
|
|
6
6
|
export declare const toPascalCase: typeof toProxyName;
|
|
7
7
|
/**
|
|
8
8
|
* React/Vue proxy generation. Purely returns owned file paths/content for
|
|
9
|
-
* types,
|
|
9
|
+
* types, provider/plugin, per-icon proxies and barrel exports. Rejects
|
|
10
10
|
* duplicate PascalCase names and missing fallback. Bitmap themes use local
|
|
11
11
|
* img wrappers + static asset imports (G1/G4); SVG themes import moe-icons.
|
|
12
12
|
*/
|