@moewolf/moe-icons-cli 0.0.1 → 0.0.3
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 +249 -10
- 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 +12768 -3965
- 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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Four-anchor project diagnosis types (E2E-B1, contract:
|
|
3
|
+
* `docs/contracts/project-integration-v1.md`).
|
|
4
|
+
*
|
|
5
|
+
* These types are pure data: detectors only READ files and BUILD a plan. They
|
|
6
|
+
* never emit ANSI, never prompt, and never write to disk. Writers/commands that
|
|
7
|
+
* display or apply the plan live outside the detectors.
|
|
8
|
+
*/
|
|
9
|
+
export type AnchorKind = "manifest" | "config" | "application" | "style";
|
|
10
|
+
export type AnchorStatus = "ok" | "missing" | "invalid" | "ambiguous" | "unsupported" | "not-required";
|
|
11
|
+
/** A single proposed file change (relative POSIX path under the project root). */
|
|
12
|
+
export interface PlannedFileChange {
|
|
13
|
+
readonly kind: "create" | "replace";
|
|
14
|
+
readonly path: string;
|
|
15
|
+
/** Full previous content (for `replace`); undefined only for `create`. */
|
|
16
|
+
readonly before: string | undefined;
|
|
17
|
+
/** Full next content. */
|
|
18
|
+
readonly after: string;
|
|
19
|
+
}
|
|
20
|
+
export interface AnchorResult {
|
|
21
|
+
readonly kind: AnchorKind;
|
|
22
|
+
readonly status: AnchorStatus;
|
|
23
|
+
readonly path?: string;
|
|
24
|
+
readonly candidates: readonly string[];
|
|
25
|
+
readonly evidence: readonly string[];
|
|
26
|
+
readonly fixes: readonly PlannedFileChange[];
|
|
27
|
+
}
|
|
28
|
+
/** The four anchors in a fixed order, for human and `--json` output. */
|
|
29
|
+
export interface DiagnosticReport {
|
|
30
|
+
readonly projectRoot?: string;
|
|
31
|
+
readonly anchors: readonly AnchorResult[];
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Four-anchor project diagnosis types (E2E-B1, contract:
|
|
3
|
+
* `docs/contracts/project-integration-v1.md`).
|
|
4
|
+
*
|
|
5
|
+
* These types are pure data: detectors only READ files and BUILD a plan. They
|
|
6
|
+
* never emit ANSI, never prompt, and never write to disk. Writers/commands that
|
|
7
|
+
* display or apply the plan live outside the detectors.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/dist/project/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type IconCatalog } from "../catalog/catalog.js";
|
|
|
2
2
|
import type { Target } from "../commands/parser.js";
|
|
3
3
|
export interface MoeiconsThemeConfig {
|
|
4
4
|
readonly styleGroup: string;
|
|
5
|
+
readonly icons?: readonly string[];
|
|
5
6
|
readonly styles?: readonly string[];
|
|
6
7
|
readonly format?: "svg" | "webp" | "png";
|
|
7
8
|
readonly imageSize?: 64 | 128 | 256 | 512;
|
|
@@ -9,21 +10,30 @@ export interface MoeiconsThemeConfig {
|
|
|
9
10
|
readonly strokeWidth?: number;
|
|
10
11
|
readonly className?: string;
|
|
11
12
|
}
|
|
13
|
+
/** User-confirmed project integration anchors (config schema v3). */
|
|
14
|
+
export interface MoeiconsIntegration {
|
|
15
|
+
readonly adapter: "vite-react" | "next-app" | "next-pages" | "vite-vue" | "nuxt" | "vanilla" | "assets-only";
|
|
16
|
+
readonly entry?: string;
|
|
17
|
+
readonly style?: string;
|
|
18
|
+
}
|
|
12
19
|
/**
|
|
13
20
|
* Normalized config: schema version 2 with a REQUIRED `target`. Core code must
|
|
14
21
|
* never see an optional or missing target — v1 files are migrated by
|
|
15
22
|
* `readMoeiconsConfig` and invalid v2 files fail with a validation error
|
|
16
|
-
* instead of silently defaulting to React.
|
|
23
|
+
* instead of silently defaulting to React. Schema v3 files add an optional
|
|
24
|
+
* `integration` block that is preserved in memory.
|
|
17
25
|
*/
|
|
18
26
|
export interface MoeiconsConfigFile {
|
|
19
|
-
readonly schemaVersion: 2;
|
|
27
|
+
readonly schemaVersion: 2 | 3;
|
|
20
28
|
readonly tier: "free" | "pro";
|
|
21
29
|
readonly target: Target;
|
|
22
30
|
readonly outputDir: string;
|
|
23
31
|
readonly defaultTheme: string;
|
|
24
32
|
readonly themes: Readonly<Record<string, MoeiconsThemeConfig>>;
|
|
25
33
|
readonly icons: readonly string[];
|
|
34
|
+
readonly downloadMode?: "auto" | "icons" | "full";
|
|
26
35
|
readonly missingIconPolicy?: "fallback" | "error";
|
|
36
|
+
readonly integration?: MoeiconsIntegration;
|
|
27
37
|
}
|
|
28
38
|
/**
|
|
29
39
|
* Legacy v1 input shape. Reads normalize it into a `MoeiconsConfigFile` v2 by
|
|
@@ -37,6 +47,7 @@ export interface LegacyConfigInput {
|
|
|
37
47
|
readonly defaultTheme: string;
|
|
38
48
|
readonly themes: Readonly<Record<string, MoeiconsThemeConfig>>;
|
|
39
49
|
readonly icons: readonly string[];
|
|
50
|
+
readonly downloadMode?: "auto" | "icons" | "full";
|
|
40
51
|
readonly missingIconPolicy?: "fallback" | "error";
|
|
41
52
|
}
|
|
42
53
|
export type ConfigLoadResult = {
|
|
@@ -52,9 +63,45 @@ export type ConfigLoadResult = {
|
|
|
52
63
|
readonly config: MoeiconsConfigFile;
|
|
53
64
|
readonly warnings: readonly string[];
|
|
54
65
|
};
|
|
66
|
+
/** Adapter enum for schema v3 `integration`. */
|
|
67
|
+
export declare const INTEGRATION_ADAPTERS: readonly ["vite-react", "next-app", "next-pages", "vite-vue", "nuxt", "vanilla", "assets-only"];
|
|
68
|
+
export type IntegrationAdapter = (typeof INTEGRATION_ADAPTERS)[number];
|
|
55
69
|
export declare const SUPPORTED_FILENAMES: readonly ["moeicons.config.jsonc", "moeicons.config.json", "moeicons.config.ts", "moeicons.config.js"];
|
|
56
70
|
export declare function findConfigFile(root: string): string | undefined;
|
|
57
|
-
|
|
71
|
+
/**
|
|
72
|
+
* DEV-G10-R1: two-phase Pro validation. The bootstrap phase parses only the
|
|
73
|
+
* catalog-independent fields (tier/target/syntax) so a Pro bitmap config can be
|
|
74
|
+
* read BEFORE the release catalog is available; the strict phase re-validates
|
|
75
|
+
* the same config against the verified release catalog and fails closed on any
|
|
76
|
+
* style group / icon / variant the release does not actually ship.
|
|
77
|
+
*/
|
|
78
|
+
export interface ConfigReadOptions {
|
|
79
|
+
readonly lenientCatalog?: boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A single immutable read of the on-disk config. DEV-G10-R2: Pro install takes
|
|
83
|
+
* one document snapshot and validates it twice (lenient, then strict). The
|
|
84
|
+
* target subtree and the bitmap tuples therefore always come from the SAME
|
|
85
|
+
* config bytes; a rewrite during download can never mix an old target with new
|
|
86
|
+
* tuples.
|
|
87
|
+
*/
|
|
88
|
+
export type ConfigDocument = {
|
|
89
|
+
readonly kind: "missing";
|
|
90
|
+
} | {
|
|
91
|
+
readonly kind: "invalid";
|
|
92
|
+
readonly message: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "unsupported";
|
|
95
|
+
readonly version: number;
|
|
96
|
+
} | {
|
|
97
|
+
readonly kind: "ok";
|
|
98
|
+
readonly value: unknown;
|
|
99
|
+
readonly version: 1 | 2 | 3;
|
|
100
|
+
};
|
|
101
|
+
export declare function loadConfigDocument(root: string): ConfigDocument;
|
|
102
|
+
/** Validate a previously loaded document (never re-reads the filesystem). */
|
|
103
|
+
export declare function validateConfigDocument(document: ConfigDocument, sourceCatalog?: IconCatalog, options?: ConfigReadOptions): ConfigLoadResult;
|
|
104
|
+
export declare function readMoeiconsConfig(root: string, sourceCatalog?: IconCatalog, options?: ConfigReadOptions): ConfigLoadResult;
|
|
58
105
|
export declare function mergeMoeiconsConfig(current: MoeiconsConfigFile | LegacyConfigInput, patch: Partial<Omit<MoeiconsConfigFile, "schemaVersion">>): MoeiconsConfigFile;
|
|
59
106
|
export declare function createMoeiconsConfig(options: {
|
|
60
107
|
target?: Target;
|
|
@@ -65,10 +112,17 @@ export declare function createMoeiconsConfig(options: {
|
|
|
65
112
|
icons?: readonly string[];
|
|
66
113
|
themes?: Readonly<Record<string, MoeiconsThemeConfig>>;
|
|
67
114
|
}): MoeiconsConfigFile;
|
|
68
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Render the editable, grouped JSONC skeleton used by `init`.
|
|
117
|
+
*
|
|
118
|
+
* Delegates to the canonical renderer bundled from moe-icons-code-library
|
|
119
|
+
* `config-package` (see `src/config-package/generated` + SOURCE.json). The CLI
|
|
120
|
+
* no longer keeps a second full template; a schema v1/v2 default file is
|
|
121
|
+
* produced unless an integration result is supplied (v3 + `integration`).
|
|
122
|
+
*/
|
|
69
123
|
export declare function renderMoeiconsConfigJsonc(options: {
|
|
70
124
|
target?: Target;
|
|
71
|
-
/** Legacy v1 alias; still emits a
|
|
125
|
+
/** Legacy v1 alias; still emits a `target` field. */
|
|
72
126
|
framework?: "react" | "vue";
|
|
73
127
|
tier?: "free" | "pro";
|
|
74
128
|
}): string;
|
package/dist/project/config.js
CHANGED
|
@@ -2,6 +2,18 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parse } from "jsonc-parser";
|
|
4
4
|
import { catalog, findCatalogIcon, findCatalogStyleGroup, } from "../catalog/catalog.js";
|
|
5
|
+
import { loadGeneratedConfigPackage } from "../config-package/generated-config.js";
|
|
6
|
+
import { toProxyName } from "../core/icon-names.js";
|
|
7
|
+
/** Adapter enum for schema v3 `integration`. */
|
|
8
|
+
export const INTEGRATION_ADAPTERS = [
|
|
9
|
+
"vite-react",
|
|
10
|
+
"next-app",
|
|
11
|
+
"next-pages",
|
|
12
|
+
"vite-vue",
|
|
13
|
+
"nuxt",
|
|
14
|
+
"vanilla",
|
|
15
|
+
"assets-only",
|
|
16
|
+
];
|
|
5
17
|
export const SUPPORTED_FILENAMES = [
|
|
6
18
|
"moeicons.config.jsonc",
|
|
7
19
|
"moeicons.config.json",
|
|
@@ -43,14 +55,18 @@ function flattenIcons(value) {
|
|
|
43
55
|
const ALLOWED_COMMON_KEYS = new Set([
|
|
44
56
|
"schemaVersion",
|
|
45
57
|
"tier",
|
|
58
|
+
"target",
|
|
46
59
|
"outputDir",
|
|
47
60
|
"defaultTheme",
|
|
48
61
|
"themes",
|
|
49
62
|
"icons",
|
|
50
63
|
"missingIconPolicy",
|
|
64
|
+
"downloadMode",
|
|
65
|
+
"integration",
|
|
51
66
|
]);
|
|
52
67
|
const ALLOWED_THEME_KEYS = new Set([
|
|
53
68
|
"styleGroup",
|
|
69
|
+
"icons",
|
|
54
70
|
"styles",
|
|
55
71
|
"format",
|
|
56
72
|
"imageSize",
|
|
@@ -63,25 +79,51 @@ function asRecord(raw) {
|
|
|
63
79
|
throw new Error("config must be an object");
|
|
64
80
|
return raw;
|
|
65
81
|
}
|
|
66
|
-
function requireCommonConfigFields(obj, sourceCatalog) {
|
|
82
|
+
function requireCommonConfigFields(obj, sourceCatalog, lenientCatalog) {
|
|
67
83
|
const warnings = [];
|
|
68
84
|
if (obj.tier !== "free" && obj.tier !== "pro")
|
|
69
85
|
throw new Error("tier must be free or pro");
|
|
70
86
|
const tier = obj.tier;
|
|
71
87
|
if (typeof obj.outputDir !== "string" || obj.outputDir.length === 0)
|
|
72
88
|
throw new Error("outputDir is required");
|
|
89
|
+
const outputParts = obj.outputDir.split("/");
|
|
90
|
+
if (obj.outputDir.includes("\\") ||
|
|
91
|
+
obj.outputDir.includes("\0") ||
|
|
92
|
+
obj.outputDir.startsWith("/") ||
|
|
93
|
+
/^[A-Za-z]:/.test(obj.outputDir) ||
|
|
94
|
+
outputParts.some((part) => !part || part === "." || part === "..")) {
|
|
95
|
+
throw new Error("outputDir must be a POSIX relative path without '.' or '..' segments");
|
|
96
|
+
}
|
|
97
|
+
if ([".git", ".moeicons", "node_modules"].includes(outputParts[0] ?? "")) {
|
|
98
|
+
throw new Error("outputDir cannot use a project control directory");
|
|
99
|
+
}
|
|
100
|
+
if (!/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(obj.outputDir)) {
|
|
101
|
+
throw new Error("outputDir must use ASCII letters, digits, _, -, and safe dotted child segments");
|
|
102
|
+
}
|
|
73
103
|
if (typeof obj.defaultTheme !== "string" || obj.defaultTheme.length === 0)
|
|
74
104
|
throw new Error("defaultTheme is required");
|
|
75
105
|
if (typeof obj.themes !== "object" || obj.themes === null || Array.isArray(obj.themes)) {
|
|
76
106
|
throw new Error("themes must be an object");
|
|
77
107
|
}
|
|
108
|
+
if (obj.downloadMode !== undefined &&
|
|
109
|
+
!["auto", "icons", "full"].includes(String(obj.downloadMode)))
|
|
110
|
+
throw new Error('downloadMode must be "auto", "icons" or "full"');
|
|
78
111
|
if (obj.missingIconPolicy !== undefined &&
|
|
79
112
|
obj.missingIconPolicy !== "fallback" &&
|
|
80
113
|
obj.missingIconPolicy !== "error") {
|
|
81
114
|
throw new Error(`missingIconPolicy must be "fallback" or "error"`);
|
|
82
115
|
}
|
|
83
116
|
const themes = {};
|
|
117
|
+
const themeNames = new Map();
|
|
84
118
|
for (const [name, value] of Object.entries(obj.themes)) {
|
|
119
|
+
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(name)) {
|
|
120
|
+
throw new Error(`theme key "${name}" must be lowercase kebab-case`);
|
|
121
|
+
}
|
|
122
|
+
const internalName = toProxyName(name).toLowerCase();
|
|
123
|
+
const previous = themeNames.get(internalName);
|
|
124
|
+
if (previous)
|
|
125
|
+
throw new Error(`theme keys "${previous}" and "${name}" generate the same identifier`);
|
|
126
|
+
themeNames.set(internalName, name);
|
|
85
127
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
86
128
|
throw new Error(`theme ${name} is invalid`);
|
|
87
129
|
const theme = value;
|
|
@@ -93,15 +135,39 @@ function requireCommonConfigFields(obj, sourceCatalog) {
|
|
|
93
135
|
if (typeof theme.styleGroup !== "string")
|
|
94
136
|
throw new Error(`theme ${name}.styleGroup is required`);
|
|
95
137
|
const group = findCatalogStyleGroup(theme.styleGroup, sourceCatalog);
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
if (!lenientCatalog) {
|
|
139
|
+
if (!group)
|
|
140
|
+
throw new Error(`unknown style group "${theme.styleGroup}"`);
|
|
141
|
+
if (!group.tiers.includes(tier)) {
|
|
142
|
+
throw new Error(`style group "${group.id}" is not available in ${tier} tier`);
|
|
143
|
+
}
|
|
100
144
|
}
|
|
101
145
|
// styles[] is deprecated — still accepted for migration but warns.
|
|
102
146
|
if (Array.isArray(theme.styles) && theme.styles.length > 0) {
|
|
103
147
|
warnings.push(`theme "${name}": "styles" is deprecated and has no effect; remove it from your config`);
|
|
104
148
|
}
|
|
149
|
+
if (theme.format !== undefined && typeof theme.format !== "string")
|
|
150
|
+
throw new Error(`theme ${name}.format is invalid`);
|
|
151
|
+
if (theme.imageSize !== undefined && typeof theme.imageSize !== "number")
|
|
152
|
+
throw new Error(`theme ${name}.imageSize is invalid`);
|
|
153
|
+
if (theme.defaultSize !== undefined &&
|
|
154
|
+
(typeof theme.defaultSize !== "number" ||
|
|
155
|
+
!Number.isFinite(theme.defaultSize) ||
|
|
156
|
+
theme.defaultSize <= 0))
|
|
157
|
+
throw new Error(`theme ${name}.defaultSize must be a positive finite number`);
|
|
158
|
+
if (theme.strokeWidth !== undefined &&
|
|
159
|
+
(typeof theme.strokeWidth !== "number" ||
|
|
160
|
+
!Number.isFinite(theme.strokeWidth) ||
|
|
161
|
+
theme.strokeWidth < 0))
|
|
162
|
+
throw new Error(`theme ${name}.strokeWidth must be a nonnegative finite number`);
|
|
163
|
+
if (theme.className !== undefined && typeof theme.className !== "string")
|
|
164
|
+
throw new Error(`theme ${name}.className must be a string`);
|
|
165
|
+
if (theme.icons !== undefined &&
|
|
166
|
+
(!Array.isArray(theme.icons) ||
|
|
167
|
+
theme.icons.some((id) => typeof id !== "string") ||
|
|
168
|
+
new Set(theme.icons).size !== theme.icons.length)) {
|
|
169
|
+
throw new Error(`theme ${name}.icons must be an array of unique icon IDs`);
|
|
170
|
+
}
|
|
105
171
|
const format = typeof theme.format === "string" ? theme.format : undefined;
|
|
106
172
|
if (format !== undefined && format !== "svg" && format !== "webp" && format !== "png") {
|
|
107
173
|
throw new Error(`theme ${name}.format is invalid`);
|
|
@@ -110,20 +176,31 @@ function requireCommonConfigFields(obj, sourceCatalog) {
|
|
|
110
176
|
if (imageSize !== undefined && ![64, 128, 256, 512].includes(imageSize)) {
|
|
111
177
|
throw new Error(`theme ${name}.imageSize is invalid`);
|
|
112
178
|
}
|
|
113
|
-
if (
|
|
114
|
-
(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
179
|
+
if (!lenientCatalog && group) {
|
|
180
|
+
if (group.type !== "bitmap" &&
|
|
181
|
+
((format !== undefined && format !== "svg") || imageSize !== undefined)) {
|
|
182
|
+
throw new Error(`SVG theme ${name} cannot define bitmap options`);
|
|
183
|
+
}
|
|
184
|
+
if (group.type === "bitmap" && format !== undefined && !group.formats.includes(format)) {
|
|
185
|
+
throw new Error(`format ${format} is unavailable for ${group.id}`);
|
|
186
|
+
}
|
|
187
|
+
if (group.type === "bitmap" &&
|
|
188
|
+
imageSize !== undefined &&
|
|
189
|
+
!group.imageSizes.includes(imageSize)) {
|
|
190
|
+
throw new Error(`imageSize ${imageSize} is unavailable for ${group.id}`);
|
|
191
|
+
}
|
|
192
|
+
if (group.type === "bitmap" && group.variants && group.variants.length > 0) {
|
|
193
|
+
const effectiveFormat = format ?? "webp";
|
|
194
|
+
const effectiveSize = imageSize ?? 256;
|
|
195
|
+
const variantId = `${group.id}-${effectiveSize}-${effectiveFormat}`;
|
|
196
|
+
if (!group.variants.includes(variantId)) {
|
|
197
|
+
throw new Error(`variant ${variantId} is unavailable for ${group.id}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
124
200
|
}
|
|
125
201
|
themes[name] = {
|
|
126
202
|
styleGroup: theme.styleGroup,
|
|
203
|
+
...(theme.icons !== undefined ? { icons: theme.icons } : {}),
|
|
127
204
|
...(format !== undefined ? { format } : {}),
|
|
128
205
|
...(imageSize !== undefined ? { imageSize: imageSize } : {}),
|
|
129
206
|
...(typeof theme.defaultSize === "number" ? { defaultSize: theme.defaultSize } : {}),
|
|
@@ -134,9 +211,17 @@ function requireCommonConfigFields(obj, sourceCatalog) {
|
|
|
134
211
|
if (!(obj.defaultTheme in themes))
|
|
135
212
|
throw new Error(`defaultTheme "${obj.defaultTheme}" is not defined`);
|
|
136
213
|
const icons = flattenIcons(obj.icons);
|
|
137
|
-
for (const
|
|
138
|
-
|
|
139
|
-
|
|
214
|
+
for (const [name, theme] of Object.entries(themes)) {
|
|
215
|
+
for (const iconId of theme.icons ?? []) {
|
|
216
|
+
if (!icons.includes(iconId))
|
|
217
|
+
throw new Error(`theme ${name}.icons contains unregistered icon "${iconId}"; add it to project icons first`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!lenientCatalog) {
|
|
221
|
+
for (const iconId of icons)
|
|
222
|
+
if (!findCatalogIcon(iconId, sourceCatalog))
|
|
223
|
+
throw new Error(`unknown icon "${iconId}"`);
|
|
224
|
+
}
|
|
140
225
|
return {
|
|
141
226
|
warnings,
|
|
142
227
|
tier,
|
|
@@ -144,9 +229,10 @@ function requireCommonConfigFields(obj, sourceCatalog) {
|
|
|
144
229
|
defaultTheme: obj.defaultTheme,
|
|
145
230
|
themes,
|
|
146
231
|
icons,
|
|
147
|
-
...(obj.
|
|
148
|
-
? {
|
|
232
|
+
...(obj.downloadMode !== undefined
|
|
233
|
+
? { downloadMode: obj.downloadMode }
|
|
149
234
|
: {}),
|
|
235
|
+
...(obj.missingIconPolicy !== undefined ? { missingIconPolicy: obj.missingIconPolicy } : {}),
|
|
150
236
|
};
|
|
151
237
|
}
|
|
152
238
|
function normalizeTarget(value) {
|
|
@@ -155,49 +241,89 @@ function normalizeTarget(value) {
|
|
|
155
241
|
}
|
|
156
242
|
throw new Error("target must be react, vue, vanilla, or assets");
|
|
157
243
|
}
|
|
158
|
-
/** Validate a
|
|
159
|
-
function validateV2Config(raw, sourceCatalog) {
|
|
244
|
+
/** Validate a `target`-based config (schema v2 or v3). Framework is rejected. */
|
|
245
|
+
function validateV2Config(raw, sourceCatalog, schemaVersion = 2, lenientCatalog = false) {
|
|
160
246
|
const obj = asRecord(raw);
|
|
161
247
|
if (obj.framework !== undefined)
|
|
162
|
-
throw new Error(
|
|
248
|
+
throw new Error("framework is only supported in schemaVersion 1; use target");
|
|
163
249
|
for (const key of Object.keys(obj)) {
|
|
164
|
-
if (!ALLOWED_COMMON_KEYS.has(key)
|
|
250
|
+
if (!ALLOWED_COMMON_KEYS.has(key)) {
|
|
165
251
|
throw new Error(`unknown config field "${key}"`);
|
|
166
252
|
}
|
|
167
253
|
}
|
|
168
254
|
if (obj.target === undefined)
|
|
169
255
|
throw new Error("target is required");
|
|
170
256
|
const target = normalizeTarget(obj.target);
|
|
171
|
-
const common = requireCommonConfigFields(obj, sourceCatalog);
|
|
257
|
+
const common = requireCommonConfigFields(obj, sourceCatalog, lenientCatalog);
|
|
172
258
|
return {
|
|
173
259
|
config: {
|
|
174
|
-
schemaVersion
|
|
260
|
+
schemaVersion,
|
|
175
261
|
tier: common.tier,
|
|
176
262
|
target,
|
|
177
263
|
outputDir: common.outputDir,
|
|
178
264
|
defaultTheme: common.defaultTheme,
|
|
179
265
|
themes: common.themes,
|
|
180
266
|
icons: common.icons,
|
|
267
|
+
...(common.downloadMode !== undefined ? { downloadMode: common.downloadMode } : {}),
|
|
181
268
|
...(common.missingIconPolicy !== undefined
|
|
182
269
|
? { missingIconPolicy: common.missingIconPolicy }
|
|
183
270
|
: {}),
|
|
271
|
+
...(obj.integration !== undefined
|
|
272
|
+
? { integration: validateIntegration(obj.integration) }
|
|
273
|
+
: {}),
|
|
184
274
|
},
|
|
185
275
|
warnings: common.warnings,
|
|
186
276
|
};
|
|
187
277
|
}
|
|
278
|
+
function validateIntegration(value) {
|
|
279
|
+
const record = asRecord(value);
|
|
280
|
+
for (const key of Object.keys(record)) {
|
|
281
|
+
if (!["adapter", "entry", "style"].includes(key)) {
|
|
282
|
+
throw new Error(`unknown integration field "${key}"`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!INTEGRATION_ADAPTERS.includes(record.adapter)) {
|
|
286
|
+
throw new Error(`integration.adapter is not a supported adapter`);
|
|
287
|
+
}
|
|
288
|
+
const adapter = record.adapter;
|
|
289
|
+
const checkRel = (field, name) => {
|
|
290
|
+
if (field === undefined)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (typeof field !== "string" ||
|
|
293
|
+
!field ||
|
|
294
|
+
field.startsWith("/") ||
|
|
295
|
+
/^[A-Za-z]:/.test(field) ||
|
|
296
|
+
field.includes("\\") ||
|
|
297
|
+
field.includes("\0") ||
|
|
298
|
+
field.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
299
|
+
throw new Error(`${name} must be a POSIX-relative path without .. or escapes`);
|
|
300
|
+
}
|
|
301
|
+
return field;
|
|
302
|
+
};
|
|
303
|
+
const entry = checkRel(record.entry, "integration.entry");
|
|
304
|
+
const style = checkRel(record.style, "integration.style");
|
|
305
|
+
if (adapter !== "assets-only" && entry === undefined) {
|
|
306
|
+
throw new Error(`integration.entry is required for adapter ${adapter}`);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
adapter,
|
|
310
|
+
...(entry !== undefined ? { entry } : {}),
|
|
311
|
+
...(style !== undefined ? { style } : {}),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
188
314
|
/** Validate a legacy schema version 1 config: framework is required, target is rejected. */
|
|
189
|
-
function validateV1Config(raw, sourceCatalog) {
|
|
315
|
+
function validateV1Config(raw, sourceCatalog, lenientCatalog = false) {
|
|
190
316
|
const obj = asRecord(raw);
|
|
191
317
|
for (const key of Object.keys(obj)) {
|
|
192
318
|
if (!ALLOWED_COMMON_KEYS.has(key) && key !== "framework") {
|
|
193
319
|
throw new Error(`unknown config field "${key}"`);
|
|
194
320
|
}
|
|
195
321
|
}
|
|
196
|
-
if (obj.target !== undefined)
|
|
197
|
-
throw new Error("v1 config cannot set target; migrate to
|
|
322
|
+
if (obj.target !== undefined || obj.integration !== undefined)
|
|
323
|
+
throw new Error("v1 config cannot set target or integration; migrate to schema v2/v3");
|
|
198
324
|
if (obj.framework !== "react" && obj.framework !== "vue")
|
|
199
325
|
throw new Error("framework must be react or vue");
|
|
200
|
-
const common = requireCommonConfigFields(obj, sourceCatalog);
|
|
326
|
+
const common = requireCommonConfigFields(obj, sourceCatalog, lenientCatalog);
|
|
201
327
|
return {
|
|
202
328
|
config: {
|
|
203
329
|
schemaVersion: 2,
|
|
@@ -207,6 +333,7 @@ function validateV1Config(raw, sourceCatalog) {
|
|
|
207
333
|
defaultTheme: common.defaultTheme,
|
|
208
334
|
themes: common.themes,
|
|
209
335
|
icons: common.icons,
|
|
336
|
+
...(common.downloadMode !== undefined ? { downloadMode: common.downloadMode } : {}),
|
|
210
337
|
...(common.missingIconPolicy !== undefined
|
|
211
338
|
? { missingIconPolicy: common.missingIconPolicy }
|
|
212
339
|
: {}),
|
|
@@ -214,7 +341,7 @@ function validateV1Config(raw, sourceCatalog) {
|
|
|
214
341
|
warnings: common.warnings,
|
|
215
342
|
};
|
|
216
343
|
}
|
|
217
|
-
export function
|
|
344
|
+
export function loadConfigDocument(root) {
|
|
218
345
|
const configPath = findConfigFile(root);
|
|
219
346
|
if (!configPath)
|
|
220
347
|
return { kind: "missing" };
|
|
@@ -231,23 +358,75 @@ export function readMoeiconsConfig(root, sourceCatalog = catalog) {
|
|
|
231
358
|
? parsed.value.schemaVersion
|
|
232
359
|
: undefined;
|
|
233
360
|
if (typeof version !== "number") {
|
|
234
|
-
return { kind: "invalid", message: "schemaVersion must be 1 or
|
|
361
|
+
return { kind: "invalid", message: "schemaVersion must be 1, 2, or 3" };
|
|
235
362
|
}
|
|
236
|
-
if (version !== 1 && version !== 2)
|
|
363
|
+
if (version !== 1 && version !== 2 && version !== 3)
|
|
237
364
|
return { kind: "unsupported", version };
|
|
365
|
+
return { kind: "ok", value: parsed.value, version };
|
|
366
|
+
}
|
|
367
|
+
/** Validate a previously loaded document (never re-reads the filesystem). */
|
|
368
|
+
export function validateConfigDocument(document, sourceCatalog = catalog, options = {}) {
|
|
369
|
+
if (document.kind !== "ok")
|
|
370
|
+
return document;
|
|
238
371
|
try {
|
|
239
|
-
if (
|
|
240
|
-
const
|
|
372
|
+
if (options.lenientCatalog !== true) {
|
|
373
|
+
const raw = asRecord(document.value);
|
|
374
|
+
if (document.version === 1 && (raw.target !== undefined || raw.integration !== undefined)) {
|
|
375
|
+
return {
|
|
376
|
+
kind: "invalid",
|
|
377
|
+
message: "v1 config cannot set target or integration; migrate to schema v2/v3",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const canonical = loadGeneratedConfigPackage().validateConfig(document.value, sourceCatalog);
|
|
381
|
+
if (!canonical.ok) {
|
|
382
|
+
if (canonical.kind === "unsupported" && typeof canonical.version === "number") {
|
|
383
|
+
return { kind: "unsupported", version: canonical.version };
|
|
384
|
+
}
|
|
385
|
+
return { kind: "invalid", message: canonical.message ?? "invalid configuration" };
|
|
386
|
+
}
|
|
387
|
+
// The vendored contract owns normalization. Only the release-specific
|
|
388
|
+
// bitmap variant check and legacy in-memory version remain CLI concerns.
|
|
389
|
+
const normalized = canonical.config;
|
|
390
|
+
for (const theme of Object.values(normalized.themes)) {
|
|
391
|
+
const group = findCatalogStyleGroup(theme.styleGroup, sourceCatalog);
|
|
392
|
+
if (group?.type !== "bitmap" || !group.variants?.length)
|
|
393
|
+
continue;
|
|
394
|
+
const variantId = `${group.id}-${theme.imageSize ?? 256}-${theme.format ?? "webp"}`;
|
|
395
|
+
if (!group.variants.includes(variantId)) {
|
|
396
|
+
return {
|
|
397
|
+
kind: "invalid",
|
|
398
|
+
message: `variant ${variantId} is unavailable for ${group.id}`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const warnings = [...(canonical.warnings ?? [])];
|
|
403
|
+
for (const [name, value] of Object.entries(raw.themes)) {
|
|
404
|
+
const styles = value.styles;
|
|
405
|
+
if (Array.isArray(styles) && styles.length > 0) {
|
|
406
|
+
warnings.push(`theme "${name}": "styles" is deprecated and has no effect; remove it from your config`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
kind: "ok",
|
|
411
|
+
config: { ...normalized, schemaVersion: document.version === 3 ? 3 : 2 },
|
|
412
|
+
warnings,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (document.version === 1) {
|
|
416
|
+
const validated = validateV1Config(document.value, sourceCatalog, options.lenientCatalog === true);
|
|
241
417
|
validated.warnings.unshift('config schema v1 migrated "framework" to "target"');
|
|
242
418
|
return { kind: "ok", config: validated.config, warnings: validated.warnings };
|
|
243
419
|
}
|
|
244
|
-
const validated = validateV2Config(
|
|
420
|
+
const validated = validateV2Config(document.value, sourceCatalog, document.version, options.lenientCatalog === true);
|
|
245
421
|
return { kind: "ok", config: validated.config, warnings: validated.warnings };
|
|
246
422
|
}
|
|
247
423
|
catch (error) {
|
|
248
424
|
return { kind: "invalid", message: error instanceof Error ? error.message : String(error) };
|
|
249
425
|
}
|
|
250
426
|
}
|
|
427
|
+
export function readMoeiconsConfig(root, sourceCatalog = catalog, options = {}) {
|
|
428
|
+
return validateConfigDocument(loadConfigDocument(root), sourceCatalog, options);
|
|
429
|
+
}
|
|
251
430
|
/** Resolve the canonical v2 target from either a normalized v2 or legacy v1 input. */
|
|
252
431
|
function targetFromInput(current) {
|
|
253
432
|
if ("target" in current)
|
|
@@ -255,14 +434,21 @@ function targetFromInput(current) {
|
|
|
255
434
|
return current.framework;
|
|
256
435
|
}
|
|
257
436
|
export function mergeMoeiconsConfig(current, patch) {
|
|
437
|
+
const integration = patch.integration ?? ("integration" in current ? current.integration : undefined);
|
|
258
438
|
return {
|
|
259
|
-
schemaVersion: 2,
|
|
439
|
+
schemaVersion: integration || current.schemaVersion === 3 ? 3 : 2,
|
|
260
440
|
tier: patch.tier ?? current.tier,
|
|
261
441
|
target: patch.target ?? targetFromInput(current),
|
|
262
442
|
outputDir: patch.outputDir ?? current.outputDir,
|
|
263
443
|
defaultTheme: patch.defaultTheme ?? current.defaultTheme,
|
|
264
444
|
themes: patch.themes ?? current.themes,
|
|
265
445
|
icons: patch.icons ?? current.icons,
|
|
446
|
+
...(patch.downloadMode !== undefined
|
|
447
|
+
? { downloadMode: patch.downloadMode }
|
|
448
|
+
: current.downloadMode !== undefined
|
|
449
|
+
? { downloadMode: current.downloadMode }
|
|
450
|
+
: {}),
|
|
451
|
+
...(integration ? { integration } : {}),
|
|
266
452
|
...(patch.missingIconPolicy !== undefined
|
|
267
453
|
? { missingIconPolicy: patch.missingIconPolicy }
|
|
268
454
|
: current.missingIconPolicy !== undefined
|
|
@@ -285,69 +471,15 @@ export function createMoeiconsConfig(options) {
|
|
|
285
471
|
missingIconPolicy: "fallback",
|
|
286
472
|
};
|
|
287
473
|
}
|
|
288
|
-
/**
|
|
474
|
+
/**
|
|
475
|
+
* Render the editable, grouped JSONC skeleton used by `init`.
|
|
476
|
+
*
|
|
477
|
+
* Delegates to the canonical renderer bundled from moe-icons-code-library
|
|
478
|
+
* `config-package` (see `src/config-package/generated` + SOURCE.json). The CLI
|
|
479
|
+
* no longer keeps a second full template; a schema v1/v2 default file is
|
|
480
|
+
* produced unless an integration result is supplied (v3 + `integration`).
|
|
481
|
+
*/
|
|
289
482
|
export function renderMoeiconsConfigJsonc(options) {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
// Collect style groups available for this tier, ordered by id.
|
|
293
|
-
const availableGroups = catalog.styleGroups
|
|
294
|
-
.filter((g) => g.tiers.includes(tier))
|
|
295
|
-
.sort((a, b) => a.id.localeCompare(b.id));
|
|
296
|
-
const defaultThemeName = "outline";
|
|
297
|
-
// Build theme entries: one per available SVG group (bitmap groups get their
|
|
298
|
-
// own entry with format/imageSize annotations).
|
|
299
|
-
const themeLines = [];
|
|
300
|
-
for (const group of availableGroups) {
|
|
301
|
-
const themeName = group.id.replace(/^moe-/, "");
|
|
302
|
-
if (group.type === "bitmap") {
|
|
303
|
-
// List available format/imageSize combinations as inline comments.
|
|
304
|
-
const formatsComment = `// format options: ${group.formats.join(", ")}`;
|
|
305
|
-
const sizesComment = `// imageSize options: ${group.imageSizes.join(", ")}`;
|
|
306
|
-
themeLines.push(` // ${themeName} — bitmap style (${group.id})`, ` // ${formatsComment}`, ` // ${sizesComment}`, ` // ${JSON.stringify(themeName)}: {`, ` // "styleGroup": ${JSON.stringify(group.id)},`, ` // "format": "webp",`, ` // "imageSize": 256`, ` // },`);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
themeLines.push(` ${JSON.stringify(themeName)}: {`, ` "styleGroup": ${JSON.stringify(group.id)}`, ` },`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
// Collect icons: only those available in at least one style group of this tier,
|
|
313
|
-
// grouped by prefix and default-selected (all selected).
|
|
314
|
-
const tierGroupIds = new Set(availableGroups.map((g) => g.id));
|
|
315
|
-
const groups = new Map();
|
|
316
|
-
for (const icon of catalog.icons) {
|
|
317
|
-
if (!icon.availableIn.some((sg) => tierGroupIds.has(sg)))
|
|
318
|
-
continue;
|
|
319
|
-
const ids = groups.get(icon.prefix) ?? [];
|
|
320
|
-
ids.push(icon.id);
|
|
321
|
-
groups.set(icon.prefix, ids);
|
|
322
|
-
}
|
|
323
|
-
const iconLines = [...groups.entries()]
|
|
324
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
325
|
-
.flatMap(([prefix, ids]) => [
|
|
326
|
-
` // ${prefix} icons`,
|
|
327
|
-
` ${JSON.stringify(prefix)}: [`,
|
|
328
|
-
...ids.map((id) => ` ${JSON.stringify(id)},`),
|
|
329
|
-
" ],",
|
|
330
|
-
]);
|
|
331
|
-
return [
|
|
332
|
-
"{",
|
|
333
|
-
` "schemaVersion": 2,`,
|
|
334
|
-
` "tier": ${JSON.stringify(tier)},`,
|
|
335
|
-
` "target": ${JSON.stringify(target)},`,
|
|
336
|
-
` "outputDir": "src/moeicons",`,
|
|
337
|
-
` "defaultTheme": ${JSON.stringify(defaultThemeName)},`,
|
|
338
|
-
' "themes": {',
|
|
339
|
-
...themeLines,
|
|
340
|
-
` // Set defaultTheme above to one of: ${availableGroups
|
|
341
|
-
.filter((g) => g.type !== "bitmap")
|
|
342
|
-
.map((g) => JSON.stringify(g.id.replace(/^moe-/, "")))
|
|
343
|
-
.join(", ")}`,
|
|
344
|
-
" },",
|
|
345
|
-
" // Comment out individual IDs or a complete prefix group to exclude it.",
|
|
346
|
-
' "icons": {',
|
|
347
|
-
...iconLines,
|
|
348
|
-
" },",
|
|
349
|
-
' "missingIconPolicy": "fallback"',
|
|
350
|
-
"}",
|
|
351
|
-
"",
|
|
352
|
-
].join("\n");
|
|
483
|
+
const generated = loadGeneratedConfigPackage();
|
|
484
|
+
return generated.renderMoeiconsConfigJsonc({ ...options, catalog });
|
|
353
485
|
}
|