@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
|
@@ -1,72 +1,95 @@
|
|
|
1
|
-
import { createLoginState, createPkcePair } from
|
|
2
|
-
import {
|
|
1
|
+
import { createLoginState, createPkcePair } from "./pkce.js";
|
|
2
|
+
import { parseCreateLoginResponse, parseExchangeLoginResponse, parsePollLoginResponse, } from "./login-schemas.js";
|
|
3
|
+
import { CliError } from "../errors/index.js";
|
|
3
4
|
function assertBrowserUrl(value, expectedOrigin) {
|
|
4
5
|
const url = new URL(value);
|
|
5
|
-
if (url.origin !== new URL(expectedOrigin).origin || url.pathname !==
|
|
6
|
-
throw new CliError(
|
|
6
|
+
if (url.origin !== new URL(expectedOrigin).origin || url.pathname !== "/cli-login") {
|
|
7
|
+
throw new CliError("AUTH_ERROR", "backend returned an untrusted login URL");
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
export async function loginWithDeviceSession(config, deps, signal) {
|
|
10
11
|
const pkce = createPkcePair();
|
|
11
12
|
const state = createLoginState();
|
|
12
|
-
const
|
|
13
|
-
method:
|
|
13
|
+
const createdRaw = await deps.request("/v1/cli-login-sessions", {
|
|
14
|
+
method: "POST",
|
|
15
|
+
body: { codeChallenge: pkce.challenge, clientNonce: state.nonce, state: state.state },
|
|
16
|
+
stage: "create",
|
|
17
|
+
...(signal ? { signal } : {}),
|
|
14
18
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const auth = `Bearer ${created.data.pollingToken}`;
|
|
21
|
-
const sessionPath = `/v1/cli-login-sessions/${encodeURIComponent(created.data.loginId)}`;
|
|
19
|
+
const created = parseCreateLoginResponse(createdRaw.data);
|
|
20
|
+
assertBrowserUrl(created.browserUrl, config.websiteOrigin);
|
|
21
|
+
await deps.openBrowser(created.browserUrl);
|
|
22
|
+
const auth = `Bearer ${created.pollingToken}`;
|
|
23
|
+
const sessionPath = `/v1/cli-login-sessions/${encodeURIComponent(created.loginId)}`;
|
|
22
24
|
try {
|
|
23
|
-
while (deps.now() < Date.parse(created.
|
|
25
|
+
while (deps.now() < Date.parse(created.expiresAt)) {
|
|
24
26
|
if (signal?.aborted)
|
|
25
|
-
throw new CliError(
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
throw new CliError("CANCELLED", "login cancelled");
|
|
28
|
+
const polledRaw = await deps.request(sessionPath, {
|
|
29
|
+
method: "GET",
|
|
30
|
+
auth,
|
|
31
|
+
stage: "poll",
|
|
32
|
+
...(signal ? { signal } : {}),
|
|
33
|
+
});
|
|
34
|
+
const polled = parsePollLoginResponse(polledRaw.data);
|
|
35
|
+
if (polledRaw.status === 200 && polled.status === "complete" && polled.exchangeCode) {
|
|
36
|
+
const exchangedRaw = await deps.request(`${sessionPath}/exchange`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
auth,
|
|
39
|
+
stage: "exchange",
|
|
40
|
+
body: {
|
|
41
|
+
exchangeCode: polled.exchangeCode,
|
|
42
|
+
clientNonce: state.nonce,
|
|
43
|
+
codeVerifier: pkce.verifier,
|
|
44
|
+
},
|
|
31
45
|
...(signal ? { signal } : {}),
|
|
32
46
|
});
|
|
47
|
+
const exchanged = parseExchangeLoginResponse(exchangedRaw.data);
|
|
33
48
|
const stored = {
|
|
34
|
-
accountId: exchanged.
|
|
35
|
-
accessToken: exchanged.
|
|
36
|
-
refreshToken: exchanged.
|
|
37
|
-
expiresAt: deps.now() + exchanged.
|
|
38
|
-
scope:
|
|
49
|
+
accountId: exchanged.accountId,
|
|
50
|
+
accessToken: exchanged.accessToken,
|
|
51
|
+
refreshToken: exchanged.refreshToken,
|
|
52
|
+
expiresAt: deps.now() + exchanged.expiresIn * 1000,
|
|
53
|
+
scope: "openid profile email offline_access",
|
|
39
54
|
storedAt: deps.now(),
|
|
40
55
|
};
|
|
41
56
|
deps.tokenStore.set(stored);
|
|
42
57
|
return stored;
|
|
43
58
|
}
|
|
44
|
-
await deps.sleep(created.
|
|
59
|
+
await deps.sleep(created.intervalSeconds * 1000, signal);
|
|
45
60
|
}
|
|
46
|
-
throw new CliError(
|
|
61
|
+
throw new CliError("AUTH_ERROR", "login session expired");
|
|
47
62
|
}
|
|
48
63
|
catch (error) {
|
|
49
64
|
try {
|
|
50
|
-
await deps.request(sessionPath, { method:
|
|
65
|
+
await deps.request(sessionPath, { method: "DELETE", auth, stage: "cleanup" });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* best effort */
|
|
51
69
|
}
|
|
52
|
-
catch { /* best effort */ }
|
|
53
70
|
throw error;
|
|
54
71
|
}
|
|
55
72
|
}
|
|
56
73
|
export async function refreshAuth0Session(config, session, deps) {
|
|
57
|
-
const response = await deps.fetch(`${config.auth0Issuer.replace(/\/$/,
|
|
58
|
-
method:
|
|
59
|
-
|
|
74
|
+
const response = await deps.fetch(`${config.auth0Issuer.replace(/\/$/, "")}/oauth/token`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "content-type": "application/json" },
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
grant_type: "refresh_token",
|
|
79
|
+
client_id: config.auth0ClientId,
|
|
80
|
+
refresh_token: session.refreshToken,
|
|
81
|
+
}),
|
|
60
82
|
...(deps.signal ? { signal: deps.signal } : {}),
|
|
61
83
|
});
|
|
62
|
-
const body = await response.json();
|
|
63
|
-
if (!response.ok || typeof body.access_token !==
|
|
64
|
-
throw new CliError(
|
|
84
|
+
const body = (await response.json());
|
|
85
|
+
if (!response.ok || typeof body.access_token !== "string") {
|
|
86
|
+
throw new CliError("AUTH_ERROR", "Auth0 refresh failed");
|
|
87
|
+
}
|
|
65
88
|
const updated = {
|
|
66
89
|
...session,
|
|
67
90
|
accessToken: body.access_token,
|
|
68
|
-
refreshToken: typeof body.refresh_token ===
|
|
69
|
-
expiresAt: deps.now() + (typeof body.expires_in ===
|
|
91
|
+
refreshToken: typeof body.refresh_token === "string" ? body.refresh_token : session.refreshToken,
|
|
92
|
+
expiresAt: deps.now() + (typeof body.expires_in === "number" ? body.expires_in : 3600) * 1000,
|
|
70
93
|
storedAt: deps.now(),
|
|
71
94
|
};
|
|
72
95
|
deps.tokenStore.set(updated);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface CreateLoginResponse {
|
|
2
|
+
readonly loginId: string;
|
|
3
|
+
readonly pollingToken: string;
|
|
4
|
+
readonly browserUrl: string;
|
|
5
|
+
readonly intervalSeconds: number;
|
|
6
|
+
readonly expiresAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PollLoginResponse {
|
|
9
|
+
readonly status: "pending" | "complete";
|
|
10
|
+
readonly exchangeCode?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ExchangeLoginResponse {
|
|
13
|
+
readonly accountId: string;
|
|
14
|
+
readonly accessToken: string;
|
|
15
|
+
readonly refreshToken: string;
|
|
16
|
+
readonly expiresIn: number;
|
|
17
|
+
readonly tokenType: "Bearer";
|
|
18
|
+
}
|
|
19
|
+
/** Validate create-session JSON before opening a browser or storing anything. */
|
|
20
|
+
export declare function parseCreateLoginResponse(data: unknown): CreateLoginResponse;
|
|
21
|
+
/** Validate poll JSON; complete requires a non-empty exchangeCode. */
|
|
22
|
+
export declare function parsePollLoginResponse(data: unknown): PollLoginResponse;
|
|
23
|
+
/** Validate exchange JSON before writing the token store. */
|
|
24
|
+
export declare function parseExchangeLoginResponse(data: unknown): ExchangeLoginResponse;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CliError } from "../errors/index.js";
|
|
2
|
+
function asRecord(data, stage) {
|
|
3
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
4
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: response schema is not an object`);
|
|
5
|
+
}
|
|
6
|
+
return data;
|
|
7
|
+
}
|
|
8
|
+
function requireNonEmptyString(record, field, stage) {
|
|
9
|
+
const value = record[field];
|
|
10
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
11
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: missing or invalid ${field}`);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
/** Validate create-session JSON before opening a browser or storing anything. */
|
|
16
|
+
export function parseCreateLoginResponse(data) {
|
|
17
|
+
const stage = "login create";
|
|
18
|
+
const record = asRecord(data, stage);
|
|
19
|
+
const loginId = requireNonEmptyString(record, "loginId", stage);
|
|
20
|
+
const pollingToken = requireNonEmptyString(record, "pollingToken", stage);
|
|
21
|
+
const browserUrl = requireNonEmptyString(record, "browserUrl", stage);
|
|
22
|
+
const expiresAt = requireNonEmptyString(record, "expiresAt", stage);
|
|
23
|
+
if (Number.isNaN(Date.parse(expiresAt))) {
|
|
24
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: expiresAt is not a valid date`);
|
|
25
|
+
}
|
|
26
|
+
const intervalSeconds = record.intervalSeconds;
|
|
27
|
+
if (typeof intervalSeconds !== "number" ||
|
|
28
|
+
!Number.isSafeInteger(intervalSeconds) ||
|
|
29
|
+
intervalSeconds < 1 ||
|
|
30
|
+
intervalSeconds > 60) {
|
|
31
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: invalid polling interval`);
|
|
32
|
+
}
|
|
33
|
+
return { loginId, pollingToken, browserUrl, intervalSeconds, expiresAt };
|
|
34
|
+
}
|
|
35
|
+
/** Validate poll JSON; complete requires a non-empty exchangeCode. */
|
|
36
|
+
export function parsePollLoginResponse(data) {
|
|
37
|
+
const stage = "login poll";
|
|
38
|
+
const record = asRecord(data, stage);
|
|
39
|
+
const status = record.status;
|
|
40
|
+
if (status !== "pending" && status !== "complete") {
|
|
41
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: unknown status`);
|
|
42
|
+
}
|
|
43
|
+
if (status === "pending") {
|
|
44
|
+
return { status: "pending" };
|
|
45
|
+
}
|
|
46
|
+
const exchangeCode = requireNonEmptyString(record, "exchangeCode", stage);
|
|
47
|
+
return { status: "complete", exchangeCode };
|
|
48
|
+
}
|
|
49
|
+
/** Validate exchange JSON before writing the token store. */
|
|
50
|
+
export function parseExchangeLoginResponse(data) {
|
|
51
|
+
const stage = "login exchange";
|
|
52
|
+
const record = asRecord(data, stage);
|
|
53
|
+
const accountId = requireNonEmptyString(record, "accountId", stage);
|
|
54
|
+
const accessToken = requireNonEmptyString(record, "accessToken", stage);
|
|
55
|
+
const refreshToken = requireNonEmptyString(record, "refreshToken", stage);
|
|
56
|
+
const expiresIn = record.expiresIn;
|
|
57
|
+
if (typeof expiresIn !== "number" ||
|
|
58
|
+
!Number.isSafeInteger(expiresIn) ||
|
|
59
|
+
expiresIn <= 0 ||
|
|
60
|
+
Number.isNaN(expiresIn)) {
|
|
61
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: invalid expiresIn`);
|
|
62
|
+
}
|
|
63
|
+
if (record.tokenType !== "Bearer") {
|
|
64
|
+
throw new CliError("VALIDATION_ERROR", `${stage}: unsupported tokenType`);
|
|
65
|
+
}
|
|
66
|
+
return { accountId, accessToken, refreshToken, expiresIn, tokenType: "Bearer" };
|
|
67
|
+
}
|
|
@@ -7,11 +7,22 @@ export interface CatalogStyleGroup {
|
|
|
7
7
|
readonly tiers: readonly CatalogTier[];
|
|
8
8
|
readonly formats: readonly CatalogFormat[];
|
|
9
9
|
readonly imageSizes: readonly number[];
|
|
10
|
+
/**
|
|
11
|
+
* MEDIA-FORMAT-V2: canonical bitmap resourceVariantIds declared by the
|
|
12
|
+
* catalog. This is the exact availability authority; a format/size pair that
|
|
13
|
+
* is not in this list must fail even when it is inside `formats`/`imageSizes`.
|
|
14
|
+
*/
|
|
15
|
+
readonly variants?: readonly string[];
|
|
10
16
|
}
|
|
11
17
|
export interface CatalogIcon {
|
|
12
18
|
readonly id: string;
|
|
19
|
+
readonly name?: string;
|
|
13
20
|
readonly prefix: string;
|
|
14
21
|
readonly label: string;
|
|
22
|
+
readonly keywords?: readonly string[];
|
|
23
|
+
readonly categories?: readonly string[];
|
|
24
|
+
readonly variants?: readonly string[];
|
|
25
|
+
readonly targets?: readonly string[];
|
|
15
26
|
readonly aliases: readonly string[];
|
|
16
27
|
readonly deprecatedAt?: string;
|
|
17
28
|
readonly replacedBy?: string;
|
package/dist/catalog/catalog.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import bundledCatalog from "./catalog.json" with { type: "json" };
|
|
2
|
+
import { parseResourceVariantId } from "../core/resource-variant.js";
|
|
2
3
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
4
|
function isStringArray(value) {
|
|
4
5
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -35,12 +36,38 @@ export function parseCatalog(value) {
|
|
|
35
36
|
if (!["outline", "solid", "mixed", "bitmap"].includes(group.type)) {
|
|
36
37
|
throw new Error(`catalog style group ${group.id} has invalid type`);
|
|
37
38
|
}
|
|
39
|
+
if (group.variants !== undefined && !isStringArray(group.variants)) {
|
|
40
|
+
throw new Error(`catalog style group ${group.id} has invalid variants`);
|
|
41
|
+
}
|
|
42
|
+
const groupVariants = group.variants;
|
|
43
|
+
if (groupVariants && (groupVariants.length === 0 || new Set(groupVariants).size !== groupVariants.length)) {
|
|
44
|
+
throw new Error(`catalog style group ${group.id} has empty or duplicate variants`);
|
|
45
|
+
}
|
|
46
|
+
if (group.type !== "bitmap" && groupVariants !== undefined) {
|
|
47
|
+
throw new Error(`catalog SVG style group ${group.id} must not declare bitmap variants`);
|
|
48
|
+
}
|
|
49
|
+
if (group.type === "bitmap" && groupVariants) {
|
|
50
|
+
for (const variantId of groupVariants) {
|
|
51
|
+
let variant;
|
|
52
|
+
try {
|
|
53
|
+
variant = parseResourceVariantId(variantId);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new Error(`catalog style group ${group.id} has inconsistent variant ${variantId}`);
|
|
57
|
+
}
|
|
58
|
+
if (variant.styleGroupId !== group.id || !group.formats.includes(variant.format)
|
|
59
|
+
|| !group.imageSizes.includes(variant.imageSize)) {
|
|
60
|
+
throw new Error(`catalog style group ${group.id} has inconsistent variant ${variantId}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
38
64
|
return {
|
|
39
65
|
id: group.id,
|
|
40
66
|
type: group.type,
|
|
41
67
|
tiers: group.tiers,
|
|
42
68
|
formats: group.formats,
|
|
43
69
|
imageSizes: group.imageSizes,
|
|
70
|
+
...(groupVariants ? { variants: groupVariants } : {}),
|
|
44
71
|
};
|
|
45
72
|
});
|
|
46
73
|
const groupIds = new Set(groups.map((group) => group.id));
|
|
@@ -51,10 +78,26 @@ export function parseCatalog(value) {
|
|
|
51
78
|
if (!isStringArray(icon.aliases) || !isStringArray(icon.availableIn) || !icon.availableIn.every((id) => groupIds.has(id))) {
|
|
52
79
|
throw new Error(`catalog icon ${icon.id} has invalid availability`);
|
|
53
80
|
}
|
|
81
|
+
const stringArray = (field, name) => {
|
|
82
|
+
if (field === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (!isStringArray(field))
|
|
85
|
+
throw new Error(`catalog icon ${String(icon.id)} has invalid ${name}`);
|
|
86
|
+
return field;
|
|
87
|
+
};
|
|
88
|
+
const keywords = stringArray(icon.keywords, "keywords");
|
|
89
|
+
const categories = stringArray(icon.categories, "categories");
|
|
90
|
+
const variants = stringArray(icon.variants, "variants");
|
|
91
|
+
const targets = stringArray(icon.targets, "targets");
|
|
54
92
|
return {
|
|
55
93
|
id: icon.id,
|
|
94
|
+
...(typeof icon.name === "string" ? { name: icon.name } : {}),
|
|
56
95
|
prefix: icon.prefix,
|
|
57
96
|
label: icon.label,
|
|
97
|
+
...(keywords ? { keywords } : {}),
|
|
98
|
+
...(categories ? { categories } : {}),
|
|
99
|
+
...(variants ? { variants } : {}),
|
|
100
|
+
...(targets ? { targets } : {}),
|
|
58
101
|
aliases: icon.aliases,
|
|
59
102
|
...(typeof icon.deprecatedAt === "string" ? { deprecatedAt: icon.deprecatedAt } : {}),
|
|
60
103
|
...(typeof icon.replacedBy === "string" ? { replacedBy: icon.replacedBy } : {}),
|