wensity 0.1.6 → 0.1.7

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.
Files changed (2) hide show
  1. package/dist/index.js +1186 -265
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,10 +4,160 @@
4
4
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
5
5
  import { dirname as dirname6, join as join6 } from "path";
6
6
 
7
+ // src/constants.ts
8
+ var CLI_NAME = "wensity";
9
+ var CLI_VERSION = "0.1.7";
10
+ var CONFIG_FILE_NAME = "wensity.json";
11
+ var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
12
+ var DEFAULT_REGISTRY_PATH = "/api/registry";
13
+ var DEFAULT_COMPONENTS_DIR = "src/components/wensity";
14
+ var DEFAULT_UTILS_PATH = "src/lib/utils";
15
+ var DEFAULT_COMPONENTS_ALIAS = "@/components/wensity";
16
+ var DEFAULT_UTILS_ALIAS = "@/lib/utils";
17
+ var INIT_PRIMITIVE_CSS_SLUG = "button";
18
+ var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
19
+
20
+ Usage:
21
+ wensity init [--preset <code>] [--preset-file <path>] [options]
22
+ wensity login [options]
23
+ wensity logout
24
+ wensity whoami [--json]
25
+ wensity list [--type component|block] [options]
26
+ wensity add <slug> [--type component|block] [options]
27
+ wensity update <slug> [--type component|block] [options]
28
+ wensity version
29
+
30
+ Commands:
31
+ init Create wensity.json and local helper files (optional --preset applies style + CSS)
32
+ login Save a Wensity API token for premium component access
33
+ logout Remove saved CLI credentials
34
+ whoami Verify the saved token and print the authenticated account
35
+ list List registry components or blocks
36
+ add Download a component or block source file into this project
37
+ update Re-download a component or block and overwrite the local copy
38
+ version Print the CLI version
39
+
40
+ Options:
41
+ --api-url <url> Override the Wensity app origin, defaults to ${DEFAULT_API_ORIGIN}
42
+ --registry <url> Override the registry endpoint
43
+ --type <type> Select component or block, defaults to component
44
+ --token <token> Provide a token non-interactively for login
45
+ --preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
46
+ --preset-file <path> Read a preset code or JSON payload from a file
47
+ --force Overwrite generated files when adding or initializing
48
+ --no-install Write files without running the package manager
49
+ --dry-run Print planned writes and installs without changing files
50
+ --json Print machine-readable output where supported
51
+ --help Show help
52
+ --version Print the CLI version
53
+
54
+ Preset notes:
55
+ Generate codes at ${DEFAULT_API_ORIGIN}/docs/primitives/setup
56
+ Plain init refuses an existing wensity.json unless --force
57
+ init --preset merges style + primitive CSS; does not rewrite installed source
58
+ add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
59
+ Invalid/expired/unsupported codes exit non-zero with an actionable message
60
+ `;
61
+
62
+ // src/errors.ts
63
+ var CliError = class extends Error {
64
+ exitCode;
65
+ constructor(message, exitCode = 1) {
66
+ super(message);
67
+ this.name = "CliError";
68
+ this.exitCode = exitCode;
69
+ }
70
+ };
71
+ function isNodeError(error) {
72
+ return error instanceof Error && "code" in error;
73
+ }
74
+
75
+ // src/args.ts
76
+ function parseArgs(argv) {
77
+ const positionals = [];
78
+ const options = /* @__PURE__ */ new Map();
79
+ for (let index = 0; index < argv.length; index += 1) {
80
+ const token = argv[index];
81
+ if (!token.startsWith("--")) {
82
+ positionals.push(token);
83
+ continue;
84
+ }
85
+ if (token === "--") {
86
+ positionals.push(...argv.slice(index + 1));
87
+ break;
88
+ }
89
+ const inlineValueIndex = token.indexOf("=");
90
+ const rawName = inlineValueIndex > -1 ? token.slice(2, inlineValueIndex) : token.slice(2);
91
+ const name = normalizeOptionName(rawName);
92
+ if (!name) {
93
+ throw new CliError(`Invalid option: ${token}`);
94
+ }
95
+ if (inlineValueIndex > -1) {
96
+ options.set(name, token.slice(inlineValueIndex + 1));
97
+ continue;
98
+ }
99
+ const nextToken = argv[index + 1];
100
+ if (nextToken && !nextToken.startsWith("--") && expectsValue(name)) {
101
+ options.set(name, nextToken);
102
+ index += 1;
103
+ continue;
104
+ }
105
+ options.set(name, true);
106
+ }
107
+ return { positionals, options };
108
+ }
109
+ function getStringOption(args, name) {
110
+ const value = args.options.get(name);
111
+ if (value === void 0) return void 0;
112
+ if (typeof value === "boolean") {
113
+ throw new CliError(`--${name} expects a value.`);
114
+ }
115
+ return value;
116
+ }
117
+ function hasFlag(args, name) {
118
+ return args.options.get(name) === true;
119
+ }
120
+ function expectsValue(name) {
121
+ return ["api-url", "registry", "token", "type", "preset", "preset-file"].includes(name);
122
+ }
123
+ function normalizeOptionName(name) {
124
+ if (name === "dry") return "dry-run";
125
+ return name;
126
+ }
127
+
128
+ // src/config.ts
129
+ import { existsSync } from "fs";
130
+ import { access, mkdir, readFile, writeFile } from "fs/promises";
131
+ import { dirname as dirname2, join as join2, relative as relative2 } from "path";
132
+
7
133
  // ../wensity-preset/src/constants.ts
8
134
  var ICON_LIBRARIES = ["tabler", "lucide"];
9
135
  var RADIUS_PRESETS = ["none", "small", "medium", "large"];
10
136
  var BUILTIN_FONT_IDS = ["site", "system-sans", "system-mono"];
137
+ var BASE_COLOR_IDS = [
138
+ "inherit",
139
+ "neutral",
140
+ "zinc",
141
+ "slate",
142
+ "stone",
143
+ "taupe"
144
+ ];
145
+ var THEME_COLOR_IDS = [
146
+ "ink",
147
+ "red",
148
+ "blue",
149
+ "violet",
150
+ "green",
151
+ "orange"
152
+ ];
153
+ var CHART_PALETTE_IDS = [
154
+ "theme",
155
+ "warm",
156
+ "cool",
157
+ "mixed"
158
+ ];
159
+ var HEADING_FONT_IDS = ["system", "playfair"];
160
+ var BODY_FONT_IDS = ["system", "inter"];
11
161
  var RADIUS_PRESET_CSS = {
12
162
  none: "0px",
13
163
  small: "0.5rem",
@@ -27,12 +177,6 @@ var CONTROL_SOLID_COLOR_KEYS = [
27
177
  "controlSolidActive",
28
178
  "controlSolidForeground"
29
179
  ];
30
- var CONTROL_SOLID_CSS_VARS = {
31
- controlSolid: "--primitive-control-solid",
32
- controlSolidHover: "--primitive-control-solid-hover",
33
- controlSolidActive: "--primitive-control-solid-active",
34
- controlSolidForeground: "--primitive-control-solid-foreground"
35
- };
36
180
  var STYLE_OBJECT_KEYS = [
37
181
  "iconLibrary",
38
182
  "radius",
@@ -40,6 +184,16 @@ var STYLE_OBJECT_KEYS = [
40
184
  "colors",
41
185
  "presetCode"
42
186
  ];
187
+ var STYLE_OBJECT_KEYS_CANONICAL = [
188
+ "baseColor",
189
+ "themeColor",
190
+ "chartPalette",
191
+ "headingFont",
192
+ "bodyFont",
193
+ "iconLibrary",
194
+ "radius",
195
+ "presetCode"
196
+ ];
43
197
  var FONT_ROLE_KEYS = ["sans", "display", "mono"];
44
198
  var COLOR_MODE_KEYS = ["light", "dark"];
45
199
  var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
@@ -62,6 +216,21 @@ function isFontRoleValue(value) {
62
216
  if (isBuiltinFontId(value)) return true;
63
217
  return value.startsWith(CUSTOM_FONT_PREFIX) && value.length > CUSTOM_FONT_PREFIX.length;
64
218
  }
219
+ function isBaseColorId(value) {
220
+ return BASE_COLOR_IDS.includes(value);
221
+ }
222
+ function isThemeColorId(value) {
223
+ return THEME_COLOR_IDS.includes(value);
224
+ }
225
+ function isChartPaletteId(value) {
226
+ return CHART_PALETTE_IDS.includes(value);
227
+ }
228
+ function isHeadingFontId(value) {
229
+ return HEADING_FONT_IDS.includes(value);
230
+ }
231
+ function isBodyFontId(value) {
232
+ return BODY_FONT_IDS.includes(value);
233
+ }
65
234
 
66
235
  // ../wensity-preset/src/defaults.ts
67
236
  var DEFAULT_STYLE_FONTS = {
@@ -101,6 +270,26 @@ function createDefaultStyleConfig() {
101
270
  }
102
271
  };
103
272
  }
273
+ var DEFAULT_STYLE_CONFIG_CANONICAL = {
274
+ baseColor: "inherit",
275
+ themeColor: "ink",
276
+ chartPalette: "theme",
277
+ headingFont: "system",
278
+ bodyFont: "system",
279
+ iconLibrary: "tabler",
280
+ radius: "medium"
281
+ };
282
+ function createDefaultStyleConfigCanonical() {
283
+ return {
284
+ baseColor: DEFAULT_STYLE_CONFIG_CANONICAL.baseColor,
285
+ themeColor: DEFAULT_STYLE_CONFIG_CANONICAL.themeColor,
286
+ chartPalette: DEFAULT_STYLE_CONFIG_CANONICAL.chartPalette,
287
+ headingFont: DEFAULT_STYLE_CONFIG_CANONICAL.headingFont,
288
+ bodyFont: DEFAULT_STYLE_CONFIG_CANONICAL.bodyFont,
289
+ iconLibrary: DEFAULT_STYLE_CONFIG_CANONICAL.iconLibrary,
290
+ radius: DEFAULT_STYLE_CONFIG_CANONICAL.radius
291
+ };
292
+ }
104
293
 
105
294
  // ../wensity-preset/src/schema.ts
106
295
  var fontRoleSchema = {
@@ -123,66 +312,53 @@ var controlSolidColorsSchema = {
123
312
  ])
124
313
  )
125
314
  };
126
- var WENSITY_CONFIG_SCHEMA = {
127
- $schema: "https://json-schema.org/draft/2020-12/schema",
128
- $id: "https://ui.wensity.com/schema/wensity.json",
129
- title: "Wensity CLI configuration",
315
+ var legacyStyleSchema = {
130
316
  type: "object",
131
317
  additionalProperties: false,
132
318
  properties: {
133
- $schema: { type: "string" },
134
- version: { type: "integer", const: 1 },
135
- registry: { type: "string", format: "uri" },
136
- components: { type: "string", minLength: 1 },
137
- utils: { type: "string", minLength: 1 },
138
- tailwind: {
319
+ iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
320
+ radius: { type: "string", enum: [...RADIUS_PRESETS] },
321
+ fonts: {
139
322
  type: "object",
140
323
  additionalProperties: false,
141
324
  properties: {
142
- config: { type: "string" },
143
- css: { type: "string" }
325
+ sans: fontRoleSchema,
326
+ display: fontRoleSchema,
327
+ mono: fontRoleSchema
144
328
  }
145
329
  },
146
- aliases: {
330
+ colors: {
147
331
  type: "object",
148
332
  additionalProperties: false,
149
333
  properties: {
150
- components: { type: "string", minLength: 1 },
151
- utils: { type: "string", minLength: 1 }
334
+ light: controlSolidColorsSchema,
335
+ dark: controlSolidColorsSchema
152
336
  }
153
337
  },
154
- style: {
155
- type: "object",
156
- additionalProperties: false,
157
- properties: {
158
- iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
159
- radius: { type: "string", enum: [...RADIUS_PRESETS] },
160
- fonts: {
161
- type: "object",
162
- additionalProperties: false,
163
- properties: {
164
- sans: fontRoleSchema,
165
- display: fontRoleSchema,
166
- mono: fontRoleSchema
167
- }
168
- },
169
- colors: {
170
- type: "object",
171
- additionalProperties: false,
172
- properties: {
173
- light: controlSolidColorsSchema,
174
- dark: controlSolidColorsSchema
175
- }
176
- },
177
- presetCode: {
178
- type: "string",
179
- minLength: 1,
180
- maxLength: PRESET_CODEC_MAX_CODE_LENGTH
181
- }
182
- }
338
+ presetCode: {
339
+ type: "string",
340
+ minLength: 1,
341
+ maxLength: PRESET_CODEC_MAX_CODE_LENGTH
183
342
  }
184
- },
185
- required: ["registry", "components", "utils", "aliases"]
343
+ }
344
+ };
345
+ var canonicalStyleSchema = {
346
+ type: "object",
347
+ additionalProperties: false,
348
+ properties: {
349
+ baseColor: { type: "string", enum: [...BASE_COLOR_IDS] },
350
+ themeColor: { type: "string", enum: [...THEME_COLOR_IDS] },
351
+ chartPalette: { type: "string", enum: [...CHART_PALETTE_IDS] },
352
+ headingFont: { type: "string", enum: [...HEADING_FONT_IDS] },
353
+ bodyFont: { type: "string", enum: [...BODY_FONT_IDS] },
354
+ iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
355
+ radius: { type: "string", enum: [...RADIUS_PRESETS] },
356
+ presetCode: {
357
+ type: "string",
358
+ minLength: 1,
359
+ maxLength: PRESET_CODEC_MAX_CODE_LENGTH
360
+ }
361
+ }
186
362
  };
187
363
 
188
364
  // ../wensity-preset/src/fonts.ts
@@ -420,6 +596,91 @@ function validateStyleConfigPartial(value) {
420
596
  }
421
597
  return result;
422
598
  }
599
+ function validateStyleConfigCanonicalPartial(value) {
600
+ if (value === void 0 || value === null) return {};
601
+ assertPlainObject(value, "style");
602
+ assertAllowedKeys(value, STYLE_OBJECT_KEYS_CANONICAL, "style");
603
+ const result = {};
604
+ if ("baseColor" in value) {
605
+ if (typeof value.baseColor !== "string" || !isBaseColorId(value.baseColor)) {
606
+ throw new PresetValidationError(
607
+ "Invalid style.baseColor. Expected inherit | neutral | zinc | slate | stone | taupe.",
608
+ "style.baseColor"
609
+ );
610
+ }
611
+ result.baseColor = value.baseColor;
612
+ }
613
+ if ("themeColor" in value) {
614
+ if (typeof value.themeColor !== "string" || !isThemeColorId(value.themeColor)) {
615
+ throw new PresetValidationError(
616
+ "Invalid style.themeColor. Expected ink | red | blue | violet | green | orange.",
617
+ "style.themeColor"
618
+ );
619
+ }
620
+ result.themeColor = value.themeColor;
621
+ }
622
+ if ("chartPalette" in value) {
623
+ if (typeof value.chartPalette !== "string" || !isChartPaletteId(value.chartPalette)) {
624
+ throw new PresetValidationError(
625
+ "Invalid style.chartPalette. Expected theme | warm | cool | mixed.",
626
+ "style.chartPalette"
627
+ );
628
+ }
629
+ result.chartPalette = value.chartPalette;
630
+ }
631
+ if ("headingFont" in value) {
632
+ if (typeof value.headingFont !== "string" || !isHeadingFontId(value.headingFont)) {
633
+ throw new PresetValidationError(
634
+ "Invalid style.headingFont. Expected system | playfair.",
635
+ "style.headingFont"
636
+ );
637
+ }
638
+ result.headingFont = value.headingFont;
639
+ }
640
+ if ("bodyFont" in value) {
641
+ if (typeof value.bodyFont !== "string" || !isBodyFontId(value.bodyFont)) {
642
+ throw new PresetValidationError(
643
+ "Invalid style.bodyFont. Expected system | inter.",
644
+ "style.bodyFont"
645
+ );
646
+ }
647
+ result.bodyFont = value.bodyFont;
648
+ }
649
+ if ("iconLibrary" in value) {
650
+ if (typeof value.iconLibrary !== "string" || !isIconLibrary(value.iconLibrary)) {
651
+ throw new PresetValidationError(
652
+ "Invalid style.iconLibrary. Expected tabler | lucide.",
653
+ "style.iconLibrary"
654
+ );
655
+ }
656
+ result.iconLibrary = value.iconLibrary;
657
+ }
658
+ if ("radius" in value) {
659
+ if (typeof value.radius !== "string" || !isRadiusPreset(value.radius)) {
660
+ throw new PresetValidationError(
661
+ "Invalid style.radius. Expected none | small | medium | large.",
662
+ "style.radius"
663
+ );
664
+ }
665
+ result.radius = value.radius;
666
+ }
667
+ if ("presetCode" in value) {
668
+ if (typeof value.presetCode !== "string" || value.presetCode.length === 0) {
669
+ throw new PresetValidationError(
670
+ "Invalid style.presetCode. Expected a non-empty string.",
671
+ "style.presetCode"
672
+ );
673
+ }
674
+ if (value.presetCode.length > PRESET_CODEC_MAX_CODE_LENGTH) {
675
+ throw new PresetValidationError(
676
+ `Invalid style.presetCode. Exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
677
+ "style.presetCode"
678
+ );
679
+ }
680
+ result.presetCode = value.presetCode;
681
+ }
682
+ return result;
683
+ }
423
684
 
424
685
  // ../wensity-preset/src/normalize.ts
425
686
  function mergeControlSolid(base, partial) {
@@ -460,9 +721,36 @@ function normalizeStyleConfig(partial) {
460
721
  }
461
722
  return normalized;
462
723
  }
724
+ function normalizeStyleConfigCanonical(partial) {
725
+ const validated = validateStyleConfigCanonicalPartial(partial ?? void 0);
726
+ const defaults = createDefaultStyleConfigCanonical();
727
+ const normalized = {
728
+ baseColor: validated.baseColor ?? defaults.baseColor,
729
+ themeColor: validated.themeColor ?? defaults.themeColor,
730
+ chartPalette: validated.chartPalette ?? defaults.chartPalette,
731
+ headingFont: validated.headingFont ?? defaults.headingFont,
732
+ bodyFont: validated.bodyFont ?? defaults.bodyFont,
733
+ iconLibrary: validated.iconLibrary ?? defaults.iconLibrary,
734
+ radius: validated.radius ?? defaults.radius
735
+ };
736
+ if (validated.presetCode !== void 0) {
737
+ normalized.presetCode = validated.presetCode;
738
+ }
739
+ return normalized;
740
+ }
463
741
 
464
742
  // ../wensity-preset/src/codec.ts
465
743
  var CODEC_PAYLOAD_KEYS = ["v", "iconLibrary", "radius", "fonts", "colors"];
744
+ var CODEC_PAYLOAD_KEYS_CANONICAL = [
745
+ "v",
746
+ "baseColor",
747
+ "themeColor",
748
+ "chartPalette",
749
+ "headingFont",
750
+ "bodyFont",
751
+ "iconLibrary",
752
+ "radius"
753
+ ];
466
754
  function assertPlainObject2(value, path) {
467
755
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
468
756
  throw new PresetValidationError(`Expected an object at ${path}.`, path);
@@ -675,7 +963,124 @@ function pickDefinedColors(partial) {
675
963
  }
676
964
  return Object.keys(out).length > 0 ? out : void 0;
677
965
  }
678
- function decodePreset(code) {
966
+ var CODEC_CANONICAL_REQUIRED_FIELDS = [
967
+ "baseColor",
968
+ "themeColor",
969
+ "chartPalette",
970
+ "headingFont",
971
+ "bodyFont",
972
+ "iconLibrary",
973
+ "radius"
974
+ ];
975
+ function validateCodecPayloadCanonical(value) {
976
+ assertPlainObject2(value, "preset");
977
+ assertAllowedKeys2(value, CODEC_PAYLOAD_KEYS_CANONICAL, "preset");
978
+ if (value.v !== 2) {
979
+ throw new PresetValidationError(
980
+ typeof value.v === "number" ? `Unsupported preset codec version ${value.v}. Expected v: 2.` : "Preset payload missing v: 2.",
981
+ "preset.v"
982
+ );
983
+ }
984
+ for (const key of CODEC_CANONICAL_REQUIRED_FIELDS) {
985
+ if (!(key in value)) {
986
+ throw new PresetValidationError(`Preset payload missing ${key}.`, `preset.${key}`);
987
+ }
988
+ }
989
+ if (typeof value.baseColor !== "string" || !isBaseColorId(value.baseColor)) {
990
+ throw new PresetValidationError(
991
+ "Invalid preset.baseColor. Expected inherit | neutral | zinc | slate | stone | taupe.",
992
+ "preset.baseColor"
993
+ );
994
+ }
995
+ if (typeof value.themeColor !== "string" || !isThemeColorId(value.themeColor)) {
996
+ throw new PresetValidationError(
997
+ "Invalid preset.themeColor. Expected ink | red | blue | violet | green | orange.",
998
+ "preset.themeColor"
999
+ );
1000
+ }
1001
+ if (typeof value.chartPalette !== "string" || !isChartPaletteId(value.chartPalette)) {
1002
+ throw new PresetValidationError(
1003
+ "Invalid preset.chartPalette. Expected theme | warm | cool | mixed.",
1004
+ "preset.chartPalette"
1005
+ );
1006
+ }
1007
+ if (typeof value.headingFont !== "string" || !isHeadingFontId(value.headingFont)) {
1008
+ throw new PresetValidationError(
1009
+ "Invalid preset.headingFont. Expected system | playfair.",
1010
+ "preset.headingFont"
1011
+ );
1012
+ }
1013
+ if (typeof value.bodyFont !== "string" || !isBodyFontId(value.bodyFont)) {
1014
+ throw new PresetValidationError(
1015
+ "Invalid preset.bodyFont. Expected system | inter.",
1016
+ "preset.bodyFont"
1017
+ );
1018
+ }
1019
+ if (typeof value.iconLibrary !== "string" || !isIconLibrary(value.iconLibrary)) {
1020
+ throw new PresetValidationError(
1021
+ "Invalid preset.iconLibrary. Expected tabler | lucide.",
1022
+ "preset.iconLibrary"
1023
+ );
1024
+ }
1025
+ if (typeof value.radius !== "string" || !isRadiusPreset(value.radius)) {
1026
+ throw new PresetValidationError(
1027
+ "Invalid preset.radius. Expected none | small | medium | large.",
1028
+ "preset.radius"
1029
+ );
1030
+ }
1031
+ return {
1032
+ v: 2,
1033
+ baseColor: value.baseColor,
1034
+ themeColor: value.themeColor,
1035
+ chartPalette: value.chartPalette,
1036
+ headingFont: value.headingFont,
1037
+ bodyFont: value.bodyFont,
1038
+ iconLibrary: value.iconLibrary,
1039
+ radius: value.radius
1040
+ };
1041
+ }
1042
+ function stableStringifyCodecPayloadCanonical(payload) {
1043
+ return `{${[
1044
+ `"v":${payload.v}`,
1045
+ `"baseColor":${JSON.stringify(payload.baseColor)}`,
1046
+ `"themeColor":${JSON.stringify(payload.themeColor)}`,
1047
+ `"chartPalette":${JSON.stringify(payload.chartPalette)}`,
1048
+ `"headingFont":${JSON.stringify(payload.headingFont)}`,
1049
+ `"bodyFont":${JSON.stringify(payload.bodyFont)}`,
1050
+ `"iconLibrary":${JSON.stringify(payload.iconLibrary)}`,
1051
+ `"radius":${JSON.stringify(payload.radius)}`
1052
+ ].join(",")}}`;
1053
+ }
1054
+ function encodePresetCanonical(style) {
1055
+ const normalized = normalizeStyleConfigCanonical(style);
1056
+ const payload = {
1057
+ v: 2,
1058
+ baseColor: normalized.baseColor,
1059
+ themeColor: normalized.themeColor,
1060
+ chartPalette: normalized.chartPalette,
1061
+ headingFont: normalized.headingFont,
1062
+ bodyFont: normalized.bodyFont,
1063
+ iconLibrary: normalized.iconLibrary,
1064
+ radius: normalized.radius
1065
+ };
1066
+ const json = stableStringifyCodecPayloadCanonical(payload);
1067
+ const bytes = new TextEncoder().encode(json);
1068
+ if (bytes.byteLength > PRESET_CODEC_MAX_DECODED_JSON_BYTES) {
1069
+ throw new PresetValidationError(
1070
+ `Preset JSON exceeds ${PRESET_CODEC_MAX_DECODED_JSON_BYTES} bytes.`,
1071
+ "preset"
1072
+ );
1073
+ }
1074
+ const code = `${PRESET_CODEC_PREFIX}.${toBase64Url(bytes)}`;
1075
+ if (code.length > PRESET_CODEC_MAX_CODE_LENGTH) {
1076
+ throw new PresetValidationError(
1077
+ `Preset code exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
1078
+ "preset"
1079
+ );
1080
+ }
1081
+ return code;
1082
+ }
1083
+ function decodePresetEnvelope(code) {
679
1084
  if (typeof code !== "string" || code.length === 0) {
680
1085
  throw new PresetValidationError("Preset code is required.", "preset");
681
1086
  }
@@ -727,7 +1132,26 @@ function decodePreset(code) {
727
1132
  } catch {
728
1133
  throw new PresetValidationError("Preset payload is not valid JSON.", "preset");
729
1134
  }
730
- return validateCodecPayload(parsed);
1135
+ return parsed;
1136
+ }
1137
+ function formatUnsupportedPayloadVersion(v) {
1138
+ const label = v === void 0 ? "undefined" : String(v);
1139
+ return `Unsupported preset payload version ${label}. This preset may require a newer version of the Wensity CLI.`;
1140
+ }
1141
+ function decodeAnyPreset(code) {
1142
+ const parsed = decodePresetEnvelope(code);
1143
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1144
+ throw new PresetValidationError("Expected an object at preset.", "preset");
1145
+ }
1146
+ assertNoForbiddenObjectKeys(parsed, "preset");
1147
+ const v = parsed.v;
1148
+ if (v === 1) {
1149
+ return validateCodecPayload(parsed);
1150
+ }
1151
+ if (v === 2) {
1152
+ return validateCodecPayloadCanonical(parsed);
1153
+ }
1154
+ throw new PresetValidationError(formatUnsupportedPayloadVersion(v), "preset.v");
731
1155
  }
732
1156
 
733
1157
  // ../wensity-preset/src/icon-map.ts
@@ -747,185 +1171,473 @@ var TABLER_TO_LUCIDE = {
747
1171
  IconArrowUpRight: "ArrowUpRight",
748
1172
  IconCalendar: "Calendar",
749
1173
  IconCamera: "Camera",
750
- IconCheck: "Check",
751
- IconChevronDown: "ChevronDown",
752
- IconChevronLeft: "ChevronLeft",
753
- IconChevronRight: "ChevronRight",
754
- IconChevronUp: "ChevronUp",
755
- IconChevronsLeft: "ChevronsLeft",
756
- IconChevronsRight: "ChevronsRight",
757
- IconCircleX: "CircleX",
758
- IconClock: "Clock",
759
- IconCornerDownLeft: "CornerDownLeft",
760
- IconDots: "Ellipsis",
761
- IconDownload: "Download",
762
- IconFile: "File",
763
- IconFileMusic: "FileAudio",
764
- IconFileText: "FileText",
765
- IconFileZip: "FileArchive",
766
- IconInfoCircle: "Info",
767
- IconLayoutSidebarLeftCollapse: "PanelLeftClose",
768
- IconLayoutSidebarLeftExpand: "PanelLeftOpen",
769
- IconMinus: "Minus",
770
- IconPhoto: "Image",
771
- IconPlus: "Plus",
772
- IconRefresh: "RefreshCw",
773
- IconSearch: "Search",
774
- IconSelector: "ChevronsUpDown",
775
- IconSlash: "Slash",
776
- IconUser: "User",
777
- IconVideo: "Video",
778
- IconX: "X"
779
- };
780
- var EXCLUSION_SET = new Set(ICON_TRANSFORM_EXCLUSIONS);
781
- function isIconTransformExclusion(name) {
782
- return EXCLUSION_SET.has(name);
783
- }
784
- function isMappedTablerIcon(name) {
785
- return Object.prototype.hasOwnProperty.call(TABLER_TO_LUCIDE, name);
786
- }
787
- function resolveLucideExport(tablerName) {
788
- if (isIconTransformExclusion(tablerName)) {
789
- throw new Error(
790
- `Icon "${tablerName}" is a Phase 0 exclusion and must not be transformed. Use internal SVG/CSS geometry instead.`
791
- );
792
- }
793
- if (!isMappedTablerIcon(tablerName)) {
794
- throw new Error(
795
- `No approved Lucide mapping for Tabler icon "${tablerName}". Add an audited map entry or convert it to internal geometry.`
796
- );
797
- }
798
- return TABLER_TO_LUCIDE[tablerName];
799
- }
800
- var LUCIDE_TO_TABLER = Object.fromEntries(
801
- Object.entries(TABLER_TO_LUCIDE).map(([tabler, lucide]) => [lucide, tabler])
802
- );
803
-
804
- // src/constants.ts
805
- var CLI_NAME = "wensity";
806
- var CLI_VERSION = "0.1.6";
807
- var CONFIG_FILE_NAME = "wensity.json";
808
- var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
809
- var DEFAULT_REGISTRY_PATH = "/api/registry";
810
- var DEFAULT_COMPONENTS_DIR = "src/components/wensity";
811
- var DEFAULT_UTILS_PATH = "src/lib/utils";
812
- var DEFAULT_COMPONENTS_ALIAS = "@/components/wensity";
813
- var DEFAULT_UTILS_ALIAS = "@/lib/utils";
814
- var INIT_PRIMITIVE_CSS_SLUG = "button";
815
- var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
816
-
817
- Usage:
818
- wensity init [--preset <code>] [--preset-file <path>] [options]
819
- wensity login [options]
820
- wensity logout
821
- wensity whoami [--json]
822
- wensity list [--type component|block] [options]
823
- wensity add <slug> [--type component|block] [options]
824
- wensity update <slug> [--type component|block] [options]
825
- wensity version
826
-
827
- Commands:
828
- init Create wensity.json and local helper files (optional --preset applies style + CSS)
829
- login Save a Wensity API token for premium component access
830
- logout Remove saved CLI credentials
831
- whoami Verify the saved token and print the authenticated account
832
- list List registry components or blocks
833
- add Download a component or block source file into this project
834
- update Re-download a component or block and overwrite the local copy
835
- version Print the CLI version
836
-
837
- Options:
838
- --api-url <url> Override the Wensity app origin, defaults to ${DEFAULT_API_ORIGIN}
839
- --registry <url> Override the registry endpoint
840
- --type <type> Select component or block, defaults to component
841
- --token <token> Provide a token non-interactively for login
842
- --preset <code> Apply a shareable Wensity style preset (wsty1.\u2026); merges style when config exists
843
- --preset-file <path> Read a preset code or JSON payload from a file
844
- --force Overwrite generated files when adding or initializing
845
- --no-install Write files without running the package manager
846
- --dry-run Print planned writes and installs without changing files
847
- --json Print machine-readable output where supported
848
- --help Show help
849
- --version Print the CLI version
850
-
851
- Preset notes:
852
- Generate codes at ${DEFAULT_API_ORIGIN}/docs/primitives/setup
853
- Plain init refuses an existing wensity.json unless --force
854
- init --preset merges style + primitive CSS; does not rewrite installed source
855
- add/update read style.iconLibrary for UI primitives only (Tabler|Lucide)
856
- Invalid/expired/unsupported codes exit non-zero with an actionable message
857
- `;
858
-
859
- // src/errors.ts
860
- var CliError = class extends Error {
861
- exitCode;
862
- constructor(message, exitCode = 1) {
863
- super(message);
864
- this.name = "CliError";
865
- this.exitCode = exitCode;
866
- }
1174
+ IconCheck: "Check",
1175
+ IconChevronDown: "ChevronDown",
1176
+ IconChevronLeft: "ChevronLeft",
1177
+ IconChevronRight: "ChevronRight",
1178
+ IconChevronUp: "ChevronUp",
1179
+ IconChevronsLeft: "ChevronsLeft",
1180
+ IconChevronsRight: "ChevronsRight",
1181
+ IconCircleX: "CircleX",
1182
+ IconClock: "Clock",
1183
+ IconCornerDownLeft: "CornerDownLeft",
1184
+ IconDots: "Ellipsis",
1185
+ IconDownload: "Download",
1186
+ IconFile: "File",
1187
+ IconFileMusic: "FileAudio",
1188
+ IconFileText: "FileText",
1189
+ IconFileZip: "FileArchive",
1190
+ IconInfoCircle: "Info",
1191
+ IconLayoutSidebarLeftCollapse: "PanelLeftClose",
1192
+ IconLayoutSidebarLeftExpand: "PanelLeftOpen",
1193
+ IconMinus: "Minus",
1194
+ IconPhoto: "Image",
1195
+ IconPlus: "Plus",
1196
+ IconRefresh: "RefreshCw",
1197
+ IconSearch: "Search",
1198
+ IconSelector: "ChevronsUpDown",
1199
+ IconSlash: "Slash",
1200
+ IconUser: "User",
1201
+ IconVideo: "Video",
1202
+ IconX: "X"
867
1203
  };
868
- function isNodeError(error) {
869
- return error instanceof Error && "code" in error;
1204
+ var EXCLUSION_SET = new Set(ICON_TRANSFORM_EXCLUSIONS);
1205
+ function isIconTransformExclusion(name) {
1206
+ return EXCLUSION_SET.has(name);
1207
+ }
1208
+ function isMappedTablerIcon(name) {
1209
+ return Object.prototype.hasOwnProperty.call(TABLER_TO_LUCIDE, name);
1210
+ }
1211
+ function resolveLucideExport(tablerName) {
1212
+ if (isIconTransformExclusion(tablerName)) {
1213
+ throw new Error(
1214
+ `Icon "${tablerName}" is a Phase 0 exclusion and must not be transformed. Use internal SVG/CSS geometry instead.`
1215
+ );
1216
+ }
1217
+ if (!isMappedTablerIcon(tablerName)) {
1218
+ throw new Error(
1219
+ `No approved Lucide mapping for Tabler icon "${tablerName}". Add an audited map entry or convert it to internal geometry.`
1220
+ );
1221
+ }
1222
+ return TABLER_TO_LUCIDE[tablerName];
870
1223
  }
1224
+ var LUCIDE_TO_TABLER = Object.fromEntries(
1225
+ Object.entries(TABLER_TO_LUCIDE).map(([tabler, lucide]) => [lucide, tabler])
1226
+ );
871
1227
 
872
- // src/args.ts
873
- function parseArgs(argv) {
874
- const positionals = [];
875
- const options = /* @__PURE__ */ new Map();
876
- for (let index = 0; index < argv.length; index += 1) {
877
- const token = argv[index];
878
- if (!token.startsWith("--")) {
879
- positionals.push(token);
880
- continue;
1228
+ // ../wensity-preset/src/palettes.ts
1229
+ var BASE_COLOR_PALETTE = {
1230
+ zinc: {
1231
+ light: {
1232
+ surfaceElevated: "#ffffff",
1233
+ surfaceOverlay: "#ffffff",
1234
+ foreground: "#09090b",
1235
+ border: "rgba(9, 9, 11, 0.08)",
1236
+ mutedForeground: "#71717a"
1237
+ },
1238
+ dark: {
1239
+ surfaceElevated: "#18181b",
1240
+ surfaceOverlay: "#27272a",
1241
+ foreground: "#fafafa",
1242
+ border: "rgba(250, 250, 250, 0.10)",
1243
+ mutedForeground: "#a1a1aa"
881
1244
  }
882
- if (token === "--") {
883
- positionals.push(...argv.slice(index + 1));
884
- break;
1245
+ },
1246
+ neutral: {
1247
+ light: {
1248
+ surfaceElevated: "#ffffff",
1249
+ surfaceOverlay: "#ffffff",
1250
+ foreground: "#171717",
1251
+ border: "rgba(23, 23, 23, 0.08)",
1252
+ mutedForeground: "#737373"
1253
+ },
1254
+ dark: {
1255
+ surfaceElevated: "#171717",
1256
+ surfaceOverlay: "#262626",
1257
+ foreground: "#fafafa",
1258
+ border: "rgba(250, 250, 250, 0.10)",
1259
+ mutedForeground: "#a3a3a3"
885
1260
  }
886
- const inlineValueIndex = token.indexOf("=");
887
- const rawName = inlineValueIndex > -1 ? token.slice(2, inlineValueIndex) : token.slice(2);
888
- const name = normalizeOptionName(rawName);
889
- if (!name) {
890
- throw new CliError(`Invalid option: ${token}`);
1261
+ },
1262
+ slate: {
1263
+ light: {
1264
+ surfaceElevated: "#ffffff",
1265
+ surfaceOverlay: "#ffffff",
1266
+ foreground: "#0f172a",
1267
+ border: "rgba(15, 23, 42, 0.08)",
1268
+ mutedForeground: "#64748b"
1269
+ },
1270
+ dark: {
1271
+ surfaceElevated: "#0f172a",
1272
+ surfaceOverlay: "#1e293b",
1273
+ foreground: "#f8fafc",
1274
+ border: "rgba(248, 250, 252, 0.10)",
1275
+ mutedForeground: "#94a3b8"
891
1276
  }
892
- if (inlineValueIndex > -1) {
893
- options.set(name, token.slice(inlineValueIndex + 1));
894
- continue;
1277
+ },
1278
+ stone: {
1279
+ light: {
1280
+ surfaceElevated: "#ffffff",
1281
+ surfaceOverlay: "#ffffff",
1282
+ foreground: "#1c1917",
1283
+ border: "rgba(28, 25, 23, 0.08)",
1284
+ mutedForeground: "#78716c"
1285
+ },
1286
+ dark: {
1287
+ surfaceElevated: "#1c1917",
1288
+ surfaceOverlay: "#292524",
1289
+ foreground: "#fafaf9",
1290
+ border: "rgba(250, 250, 249, 0.10)",
1291
+ mutedForeground: "#a8a29e"
895
1292
  }
896
- const nextToken = argv[index + 1];
897
- if (nextToken && !nextToken.startsWith("--") && expectsValue(name)) {
898
- options.set(name, nextToken);
899
- index += 1;
900
- continue;
1293
+ },
1294
+ taupe: {
1295
+ light: {
1296
+ surfaceElevated: "#faf8f5",
1297
+ surfaceOverlay: "#ffffff",
1298
+ foreground: "#3b342e",
1299
+ border: "rgba(59, 52, 46, 0.10)",
1300
+ mutedForeground: "#8a7f74"
1301
+ },
1302
+ dark: {
1303
+ surfaceElevated: "#1c1916",
1304
+ surfaceOverlay: "#2a2521",
1305
+ foreground: "#f5f0ea",
1306
+ border: "rgba(245, 240, 234, 0.10)",
1307
+ mutedForeground: "#a89f94"
901
1308
  }
902
- options.set(name, true);
903
1309
  }
904
- return { positionals, options };
1310
+ };
1311
+ var THEME_COLOR_PALETTE = {
1312
+ ink: {
1313
+ light: { ...DEFAULT_CONTROL_SOLID_LIGHT },
1314
+ dark: { ...DEFAULT_CONTROL_SOLID_DARK }
1315
+ },
1316
+ red: {
1317
+ light: {
1318
+ controlSolid: "#b91c1c",
1319
+ controlSolidHover: "#dc2626",
1320
+ controlSolidActive: "#991b1b",
1321
+ controlSolidForeground: "#ffffff"
1322
+ },
1323
+ dark: {
1324
+ controlSolid: "#f87171",
1325
+ controlSolidHover: "#fca5a5",
1326
+ controlSolidActive: "#ef4444",
1327
+ controlSolidForeground: "#450a0a"
1328
+ }
1329
+ },
1330
+ blue: {
1331
+ light: {
1332
+ controlSolid: "#1d4ed8",
1333
+ controlSolidHover: "#2563eb",
1334
+ controlSolidActive: "#1e40af",
1335
+ controlSolidForeground: "#ffffff"
1336
+ },
1337
+ dark: {
1338
+ controlSolid: "#60a5fa",
1339
+ controlSolidHover: "#93c5fd",
1340
+ controlSolidActive: "#3b82f6",
1341
+ controlSolidForeground: "#172554"
1342
+ }
1343
+ },
1344
+ violet: {
1345
+ light: {
1346
+ controlSolid: "#6d28d9",
1347
+ controlSolidHover: "#7c3aed",
1348
+ controlSolidActive: "#5b21b6",
1349
+ controlSolidForeground: "#ffffff"
1350
+ },
1351
+ dark: {
1352
+ controlSolid: "#a78bfa",
1353
+ controlSolidHover: "#c4b5fd",
1354
+ controlSolidActive: "#8b5cf6",
1355
+ controlSolidForeground: "#2e1065"
1356
+ }
1357
+ },
1358
+ green: {
1359
+ light: {
1360
+ controlSolid: "#047857",
1361
+ controlSolidHover: "#059669",
1362
+ controlSolidActive: "#065f46",
1363
+ controlSolidForeground: "#ffffff"
1364
+ },
1365
+ dark: {
1366
+ controlSolid: "#34d399",
1367
+ controlSolidHover: "#6ee7b7",
1368
+ controlSolidActive: "#10b981",
1369
+ controlSolidForeground: "#022c22"
1370
+ }
1371
+ },
1372
+ orange: {
1373
+ light: {
1374
+ controlSolid: "#c2410c",
1375
+ controlSolidHover: "#ea580c",
1376
+ controlSolidActive: "#9a3412",
1377
+ controlSolidForeground: "#ffffff"
1378
+ },
1379
+ dark: {
1380
+ controlSolid: "#fb923c",
1381
+ controlSolidHover: "#fdba74",
1382
+ controlSolidActive: "#f97316",
1383
+ controlSolidForeground: "#431407"
1384
+ }
1385
+ }
1386
+ };
1387
+ var DEFAULT_CHART_STOPS = {
1388
+ light: [
1389
+ "#2a78d6",
1390
+ "#1baf7a",
1391
+ "#eda100",
1392
+ "#008300",
1393
+ "#4a3aa7",
1394
+ "#e34948"
1395
+ ],
1396
+ dark: [
1397
+ "#3987e5",
1398
+ "#199e70",
1399
+ "#c98500",
1400
+ "#008300",
1401
+ "#9085e9",
1402
+ "#e66767"
1403
+ ]
1404
+ };
1405
+ var CHART_PALETTES = {
1406
+ theme: {
1407
+ ink: DEFAULT_CHART_STOPS,
1408
+ red: {
1409
+ light: ["#e34948", "#f97316", "#eda100", "#db2777", "#b91c1c", "#9f1239"],
1410
+ dark: ["#e66767", "#fb923c", "#c98500", "#f472b6", "#f87171", "#fb7185"]
1411
+ },
1412
+ blue: {
1413
+ light: ["#2a78d6", "#0284c7", "#4a3aa7", "#0e7490", "#1d4ed8", "#0369a1"],
1414
+ dark: ["#3987e5", "#38bdf8", "#9085e9", "#22d3ee", "#60a5fa", "#7dd3fc"]
1415
+ },
1416
+ violet: {
1417
+ light: ["#4a3aa7", "#7c3aed", "#2a78d6", "#a21caf", "#6d28d9", "#db2777"],
1418
+ dark: ["#9085e9", "#a78bfa", "#3987e5", "#e879f9", "#c4b5fd", "#f472b6"]
1419
+ },
1420
+ green: {
1421
+ light: ["#1baf7a", "#008300", "#0d9488", "#2a78d6", "#047857", "#65a30d"],
1422
+ dark: ["#199e70", "#34d399", "#2dd4bf", "#3987e5", "#4ade80", "#a3e635"]
1423
+ },
1424
+ orange: {
1425
+ light: ["#eda100", "#f97316", "#e34948", "#ca8a04", "#c2410c", "#db2777"],
1426
+ dark: ["#c98500", "#fb923c", "#e66767", "#facc15", "#fdba74", "#f472b6"]
1427
+ }
1428
+ },
1429
+ warm: {
1430
+ light: ["#ea580c", "#d97706", "#e34948", "#db2777", "#ca8a04", "#c2410c"],
1431
+ dark: ["#fb923c", "#fbbf24", "#e66767", "#f472b6", "#facc15", "#fdba74"]
1432
+ },
1433
+ cool: {
1434
+ light: ["#2a78d6", "#0e7490", "#4a3aa7", "#0284c7", "#1d4ed8", "#0f766e"],
1435
+ dark: ["#3987e5", "#22d3ee", "#9085e9", "#38bdf8", "#60a5fa", "#2dd4bf"]
1436
+ },
1437
+ mixed: {
1438
+ light: ["#2a78d6", "#e34948", "#1baf7a", "#eda100", "#4a3aa7", "#db2777"],
1439
+ dark: ["#3987e5", "#e66767", "#199e70", "#c98500", "#9085e9", "#f472b6"]
1440
+ }
1441
+ };
1442
+
1443
+ // ../wensity-preset/src/curated-fonts.ts
1444
+ var HEADING_FONT_PACKAGES = {
1445
+ playfair: {
1446
+ packageName: "@fontsource/playfair-display",
1447
+ importSpecifier: "@fontsource/playfair-display",
1448
+ familyName: "Playfair Display"
1449
+ }
1450
+ };
1451
+ var BODY_FONT_PACKAGES = {
1452
+ inter: {
1453
+ packageName: "@fontsource/inter",
1454
+ importSpecifier: "@fontsource/inter",
1455
+ familyName: "Inter"
1456
+ }
1457
+ };
1458
+ var HEADING_FONT_CSS = {
1459
+ system: "var(--font-display)",
1460
+ playfair: "'Playfair Display', Georgia, 'Times New Roman', serif"
1461
+ };
1462
+ var BODY_FONT_CSS = {
1463
+ system: "var(--font-sans)",
1464
+ inter: "'Inter', ui-sans-serif, system-ui, sans-serif"
1465
+ };
1466
+
1467
+ // ../wensity-preset/src/resolve.ts
1468
+ var RESOLVED_TOKEN_CSS_VARS = {
1469
+ surfaceElevated: "--primitive-surface-elevated",
1470
+ surfaceOverlay: "--primitive-surface-overlay",
1471
+ surfaceHover: "--primitive-surface-hover",
1472
+ surfaceActive: "--primitive-surface-active",
1473
+ surfaceSelected: "--primitive-surface-selected",
1474
+ borderSubtle: "--primitive-border-subtle",
1475
+ ring: "--primitive-ring",
1476
+ textSecondary: "--primitive-text-secondary",
1477
+ textPlaceholder: "--primitive-text-placeholder",
1478
+ controlSolid: "--primitive-control-solid",
1479
+ controlSolidHover: "--primitive-control-solid-hover",
1480
+ controlSolidActive: "--primitive-control-solid-active",
1481
+ controlSolidForeground: "--primitive-control-solid-foreground",
1482
+ chart1: "--primitive-chart-1",
1483
+ chart2: "--primitive-chart-2",
1484
+ chart3: "--primitive-chart-3",
1485
+ chart4: "--primitive-chart-4",
1486
+ chart5: "--primitive-chart-5",
1487
+ chart6: "--primitive-chart-6",
1488
+ radius: "--primitive-radius",
1489
+ fontSans: "--primitive-font-sans",
1490
+ fontDisplay: "--primitive-font-display",
1491
+ fontMono: "--primitive-font-mono"
1492
+ };
1493
+ var INHERIT_BASE_EXPRESSIONS = {
1494
+ light: {
1495
+ surfaceElevated: "#ffffff",
1496
+ surfaceOverlay: "#ffffff",
1497
+ surfaceHover: "color-mix(in srgb, var(--foreground) 4%, transparent)",
1498
+ surfaceActive: "color-mix(in srgb, var(--foreground) 7%, transparent)",
1499
+ borderSubtle: "color-mix(in srgb, var(--border) 60%, transparent)",
1500
+ textSecondary: "color-mix(in srgb, var(--foreground) 72%, transparent)",
1501
+ textPlaceholder: "color-mix(in srgb, var(--muted-foreground) 85%, transparent)"
1502
+ },
1503
+ dark: {
1504
+ surfaceElevated: "#0f0f10",
1505
+ surfaceOverlay: "#141415",
1506
+ surfaceHover: "color-mix(in srgb, var(--foreground) 5%, transparent)",
1507
+ surfaceActive: "color-mix(in srgb, var(--foreground) 10%, transparent)",
1508
+ borderSubtle: "color-mix(in srgb, var(--border) 60%, transparent)",
1509
+ textSecondary: "color-mix(in srgb, var(--foreground) 72%, transparent)",
1510
+ textPlaceholder: "color-mix(in srgb, var(--muted-foreground) 85%, transparent)"
1511
+ }
1512
+ };
1513
+ var FIXED_RING = "color-mix(in srgb, var(--foreground) 45%, transparent)";
1514
+ var FIXED_SURFACE_SELECTED = {
1515
+ light: "color-mix(in srgb, var(--foreground) 6%, transparent)",
1516
+ dark: "color-mix(in srgb, var(--foreground) 8%, transparent)"
1517
+ };
1518
+ var COLORED_THEME_IDS = /* @__PURE__ */ new Set([
1519
+ "red",
1520
+ "blue",
1521
+ "violet",
1522
+ "green",
1523
+ "orange"
1524
+ ]);
1525
+ function colorMix(source, percent) {
1526
+ return `color-mix(in srgb, ${source} ${percent}%, transparent)`;
905
1527
  }
906
- function getStringOption(args, name) {
907
- const value = args.options.get(name);
908
- if (value === void 0) return void 0;
909
- if (typeof value === "boolean") {
910
- throw new CliError(`--${name} expects a value.`);
1528
+ function resolveControlSolid(partial, fallback) {
1529
+ const out = { ...fallback };
1530
+ for (const key of CONTROL_SOLID_COLOR_KEYS) {
1531
+ const value = partial?.[key];
1532
+ if (value !== void 0) out[key] = value;
911
1533
  }
912
- return value;
1534
+ return out;
913
1535
  }
914
- function hasFlag(args, name) {
915
- return args.options.get(name) === true;
1536
+ function chartStopsToModeTokens(stops) {
1537
+ return {
1538
+ chart1: stops[0],
1539
+ chart2: stops[1],
1540
+ chart3: stops[2],
1541
+ chart4: stops[3],
1542
+ chart5: stops[4],
1543
+ chart6: stops[5]
1544
+ };
916
1545
  }
917
- function expectsValue(name) {
918
- return ["api-url", "registry", "token", "type", "preset", "preset-file"].includes(name);
1546
+ function baseFromInherit(mode) {
1547
+ return { ...INHERIT_BASE_EXPRESSIONS[mode] };
919
1548
  }
920
- function normalizeOptionName(name) {
921
- if (name === "dry") return "dry-run";
922
- return name;
1549
+ function baseFromSeeds(seeds, mode) {
1550
+ const hoverPercent = mode === "light" ? 4 : 5;
1551
+ const activePercent = mode === "light" ? 7 : 10;
1552
+ return {
1553
+ surfaceElevated: seeds.surfaceElevated,
1554
+ surfaceOverlay: seeds.surfaceOverlay,
1555
+ surfaceHover: colorMix(seeds.foreground, hoverPercent),
1556
+ surfaceActive: colorMix(seeds.foreground, activePercent),
1557
+ borderSubtle: colorMix(seeds.border, 60),
1558
+ textSecondary: colorMix(seeds.foreground, 72),
1559
+ textPlaceholder: colorMix(seeds.mutedForeground, 85)
1560
+ };
1561
+ }
1562
+ function ringAndSelectedForInk(mode) {
1563
+ return {
1564
+ ring: FIXED_RING,
1565
+ surfaceSelected: FIXED_SURFACE_SELECTED[mode]
1566
+ };
1567
+ }
1568
+ function ringAndSelectedFromAccent(accent, mode) {
1569
+ return {
1570
+ ring: colorMix(accent, 45),
1571
+ surfaceSelected: colorMix(accent, mode === "light" ? 6 : 8)
1572
+ };
1573
+ }
1574
+ function buildModeTokens(args) {
1575
+ return {
1576
+ ...args.base,
1577
+ ...args.ringSelected,
1578
+ controlSolid: args.control.controlSolid,
1579
+ controlSolidHover: args.control.controlSolidHover,
1580
+ controlSolidActive: args.control.controlSolidActive,
1581
+ controlSolidForeground: args.control.controlSolidForeground,
1582
+ ...chartStopsToModeTokens(args.charts)
1583
+ };
1584
+ }
1585
+ function resolveStyleConfig(style) {
1586
+ const lightControl = resolveControlSolid(
1587
+ style.colors?.light,
1588
+ DEFAULT_CONTROL_SOLID_LIGHT
1589
+ );
1590
+ const darkControl = resolveControlSolid(
1591
+ style.colors?.dark,
1592
+ DEFAULT_CONTROL_SOLID_DARK
1593
+ );
1594
+ return {
1595
+ iconLibrary: style.iconLibrary,
1596
+ radius: RADIUS_PRESET_CSS[style.radius],
1597
+ fontSans: expandFontRole(style.fonts.sans, "sans"),
1598
+ fontDisplay: expandFontRole(style.fonts.display, "display"),
1599
+ fontMono: expandFontRole(style.fonts.mono, "mono"),
1600
+ light: buildModeTokens({
1601
+ base: baseFromInherit("light"),
1602
+ control: lightControl,
1603
+ ringSelected: ringAndSelectedForInk("light"),
1604
+ charts: DEFAULT_CHART_STOPS.light
1605
+ }),
1606
+ dark: buildModeTokens({
1607
+ base: baseFromInherit("dark"),
1608
+ control: darkControl,
1609
+ ringSelected: ringAndSelectedForInk("dark"),
1610
+ charts: DEFAULT_CHART_STOPS.dark
1611
+ })
1612
+ };
1613
+ }
1614
+ function resolveChartStops(chartPalette, themeColor, mode) {
1615
+ if (chartPalette === "theme") {
1616
+ return CHART_PALETTES.theme[themeColor][mode];
1617
+ }
1618
+ return CHART_PALETTES[chartPalette][mode];
1619
+ }
1620
+ function resolveStyleConfigCanonical(style) {
1621
+ const theme = THEME_COLOR_PALETTE[style.themeColor];
1622
+ const useAccentRing = COLORED_THEME_IDS.has(style.themeColor);
1623
+ const resolveMode = (mode) => {
1624
+ const base = style.baseColor === "inherit" ? baseFromInherit(mode) : baseFromSeeds(BASE_COLOR_PALETTE[style.baseColor][mode], mode);
1625
+ const control = theme[mode];
1626
+ const ringSelected = useAccentRing ? ringAndSelectedFromAccent(control.controlSolid, mode) : ringAndSelectedForInk(mode);
1627
+ const charts = resolveChartStops(style.chartPalette, style.themeColor, mode);
1628
+ return buildModeTokens({ base, control, ringSelected, charts });
1629
+ };
1630
+ return {
1631
+ iconLibrary: style.iconLibrary,
1632
+ radius: RADIUS_PRESET_CSS[style.radius],
1633
+ fontSans: BODY_FONT_CSS[style.bodyFont],
1634
+ fontDisplay: HEADING_FONT_CSS[style.headingFont],
1635
+ // Canonical model has no mono field — lock to today's system-mono expansion.
1636
+ fontMono: FONT_ID_EXPANSIONS["system-mono"],
1637
+ light: resolveMode("light"),
1638
+ dark: resolveMode("dark")
1639
+ };
923
1640
  }
924
-
925
- // src/config.ts
926
- import { existsSync } from "fs";
927
- import { access, mkdir, readFile, writeFile } from "fs/promises";
928
- import { dirname as dirname2, join as join2, relative as relative2 } from "path";
929
1641
 
930
1642
  // src/paths.ts
931
1643
  import { dirname, isAbsolute, relative, resolve, sep } from "path";
@@ -995,6 +1707,35 @@ function extensionlessPath(path) {
995
1707
  return path.replace(/\.(tsx|ts|jsx|js)$/, "");
996
1708
  }
997
1709
 
1710
+ // src/style-shape.ts
1711
+ function isCanonicalStyleShape(value) {
1712
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1713
+ const record = value;
1714
+ return "baseColor" in record || "themeColor" in record || "chartPalette" in record || "headingFont" in record || "bodyFont" in record;
1715
+ }
1716
+ function normalizeProjectStyle(style) {
1717
+ if (isCanonicalStyleShape(style)) {
1718
+ return normalizeStyleConfigCanonical(style);
1719
+ }
1720
+ return normalizeStyleConfig(style);
1721
+ }
1722
+ function resolveTokensForStyle(style) {
1723
+ if (isCanonicalStyleShape(style)) {
1724
+ return resolveStyleConfigCanonical(normalizeStyleConfigCanonical(style));
1725
+ }
1726
+ return resolveStyleConfig(normalizeStyleConfig(style));
1727
+ }
1728
+ function curatedFontsFromStyle(style) {
1729
+ if (isCanonicalStyleShape(style)) {
1730
+ const normalized = normalizeStyleConfigCanonical(style);
1731
+ return {
1732
+ headingFont: normalized.headingFont,
1733
+ bodyFont: normalized.bodyFont
1734
+ };
1735
+ }
1736
+ return { headingFont: "system", bodyFont: "system" };
1737
+ }
1738
+
998
1739
  // src/config.ts
999
1740
  async function loadConfig(cwd, overrides = {}) {
1000
1741
  const foundPath = await findUp(cwd, CONFIG_FILE_NAME);
@@ -1128,7 +1869,8 @@ function normalizeConfig(config, overrides) {
1128
1869
  utils: config.aliases?.utils || DEFAULT_UTILS_ALIAS
1129
1870
  },
1130
1871
  // In-memory only: missing style fields resolve to defaults without rewriting disk.
1131
- style: normalizeStyleConfig(config.style)
1872
+ // Accepts either legacy or canonical style shapes.
1873
+ style: normalizeProjectStyle(config.style)
1132
1874
  };
1133
1875
  } catch (error) {
1134
1876
  if (error instanceof PresetValidationError) {
@@ -1257,6 +1999,122 @@ function requiredHome(env) {
1257
1999
  throw new CliError("HOME is not set, so the CLI cannot locate ~/.config/wensity.");
1258
2000
  }
1259
2001
 
2002
+ // src/font-install.ts
2003
+ var FONT_CSS_START_MARKER = "/* \u2500\u2500 Wensity Fonts";
2004
+ var FONT_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Fonts \u2500\u2500 */";
2005
+ function planFontInstall(headingFont, bodyFont) {
2006
+ const packages = [];
2007
+ const importLines = [];
2008
+ const seenPackages = /* @__PURE__ */ new Set();
2009
+ const seenImports = /* @__PURE__ */ new Set();
2010
+ const pushFont = (meta) => {
2011
+ if (!meta) return;
2012
+ if (!seenPackages.has(meta.packageName)) {
2013
+ seenPackages.add(meta.packageName);
2014
+ packages.push(meta.packageName);
2015
+ }
2016
+ const line = `@import "${meta.importSpecifier}";`;
2017
+ if (!seenImports.has(line)) {
2018
+ seenImports.add(line);
2019
+ importLines.push(line);
2020
+ }
2021
+ };
2022
+ if (headingFont !== "system") {
2023
+ pushFont(HEADING_FONT_PACKAGES[headingFont]);
2024
+ }
2025
+ if (bodyFont !== "system") {
2026
+ pushFont(BODY_FONT_PACKAGES[bodyFont]);
2027
+ }
2028
+ packages.sort();
2029
+ if (importLines.length === 0) {
2030
+ return { packages: [], importLines: [], block: null };
2031
+ }
2032
+ const block = [
2033
+ `${FONT_CSS_START_MARKER} \u2500\u2500 */`,
2034
+ ...importLines,
2035
+ FONT_CSS_END_MARKER
2036
+ ].join("\n");
2037
+ return { packages, importLines, block };
2038
+ }
2039
+ function upsertFontImportBlock(existingCss, block) {
2040
+ const start = existingCss.indexOf(FONT_CSS_START_MARKER);
2041
+ const end = existingCss.indexOf(FONT_CSS_END_MARKER);
2042
+ if (start !== -1 || end !== -1) {
2043
+ if (start === -1 || end === -1 || end <= start) {
2044
+ throw new CliError(
2045
+ "Configured Tailwind CSS has a partial Wensity Fonts block. Remove the broken markers and re-run install."
2046
+ );
2047
+ }
2048
+ const before2 = existingCss.slice(0, start).replace(/\s*$/, "");
2049
+ const after2 = existingCss.slice(end + FONT_CSS_END_MARKER.length).replace(/^\s*/, "");
2050
+ if (!block) {
2051
+ const joined = [before2, after2].filter((part) => part.length > 0).join("\n\n");
2052
+ return joined ? `${joined.replace(/\n{3,}/g, "\n\n")}
2053
+ ` : "";
2054
+ }
2055
+ const normalizedBlock2 = block.trim();
2056
+ const prefix2 = before2 ? `${before2}
2057
+
2058
+ ` : "";
2059
+ const suffix2 = after2 ? `
2060
+
2061
+ ${after2}` : "\n";
2062
+ return `${prefix2}${normalizedBlock2}${suffix2}`.replace(/\n{3,}/g, "\n\n");
2063
+ }
2064
+ if (!block) return existingCss;
2065
+ const normalizedBlock = block.trim();
2066
+ const insertAt = findFontBlockInsertIndex(existingCss);
2067
+ const before = existingCss.slice(0, insertAt).replace(/\s*$/, "");
2068
+ const after = existingCss.slice(insertAt).replace(/^\s*/, "");
2069
+ const prefix = before ? `${before}
2070
+
2071
+ ` : "";
2072
+ const suffix = after ? `
2073
+
2074
+ ${after}` : "\n";
2075
+ return `${prefix}${normalizedBlock}${suffix}`.replace(/\n{3,}/g, "\n\n");
2076
+ }
2077
+ function warnUnusedFontPackageMessage(headingFont, bodyFont, installedPackages) {
2078
+ const needed = new Set(planFontInstall(headingFont, bodyFont).packages);
2079
+ const curated = [
2080
+ ...Object.values(HEADING_FONT_PACKAGES).map((entry) => entry.packageName),
2081
+ ...Object.values(BODY_FONT_PACKAGES).map((entry) => entry.packageName)
2082
+ ];
2083
+ const leftover = curated.filter(
2084
+ (pkg) => installedPackages.has(pkg) && !needed.has(pkg)
2085
+ );
2086
+ if (leftover.length === 0) return void 0;
2087
+ return `Project package.json still lists ${leftover.join(", ")} while curated fonts are set to heading="${headingFont}" body="${bodyFont}". Wensity removed the @import for unused fonts but will not uninstall ${leftover.join(", ")} (it may be used outside Wensity).`;
2088
+ }
2089
+ function findFontBlockInsertIndex(css) {
2090
+ let index = 0;
2091
+ const length = css.length;
2092
+ while (index < length) {
2093
+ while (index < length && /\s/.test(css[index])) index += 1;
2094
+ if (index >= length) break;
2095
+ if (css.startsWith("/*", index)) {
2096
+ const end = css.indexOf("*/", index + 2);
2097
+ if (end === -1) break;
2098
+ index = end + 2;
2099
+ continue;
2100
+ }
2101
+ if (css.startsWith("//", index)) {
2102
+ const end = css.indexOf("\n", index);
2103
+ if (end === -1) return length;
2104
+ index = end + 1;
2105
+ continue;
2106
+ }
2107
+ if (css.startsWith("@charset", index) || css.startsWith("@import", index)) {
2108
+ const end = css.indexOf(";", index);
2109
+ if (end === -1) break;
2110
+ index = end + 1;
2111
+ continue;
2112
+ }
2113
+ break;
2114
+ }
2115
+ return index;
2116
+ }
2117
+
1260
2118
  // src/output.ts
1261
2119
  import boxen from "boxen";
1262
2120
  import Table from "cli-table3";
@@ -1807,19 +2665,13 @@ async function resolvePresetStyle(input) {
1807
2665
  const trimmed = raw.trim();
1808
2666
  if (trimmed.startsWith("{")) {
1809
2667
  const parsed = JSON.parse(trimmed);
1810
- if (parsed && typeof parsed === "object" && "v" in parsed) {
1811
- const payload = validateCodecPayload(parsed);
1812
- const json = stableStringifyCodecPayload(payload);
1813
- code = `${PRESET_CODEC_PREFIX}.${Buffer.from(json, "utf8").toString("base64url")}`;
1814
- } else {
1815
- code = encodePreset(parsed);
1816
- }
2668
+ code = encodePresetFileJson(parsed);
1817
2669
  } else {
1818
2670
  code = trimmed;
1819
2671
  }
1820
2672
  } catch (error) {
1821
2673
  if (error instanceof PresetValidationError) {
1822
- throw new CliError(`Invalid preset file: ${error.message}`);
2674
+ throw toCliPresetError(error);
1823
2675
  }
1824
2676
  if (error instanceof SyntaxError) {
1825
2677
  throw new CliError(`Preset file contains invalid JSON: ${error.message}`);
@@ -1834,19 +2686,69 @@ async function resolvePresetStyle(input) {
1834
2686
  }
1835
2687
  try {
1836
2688
  const trimmed = code.trim();
1837
- const payload = decodePreset(trimmed);
1838
- const style = normalizeStyleConfig({
1839
- ...codecPayloadToStyleInput(payload),
1840
- presetCode: trimmed
1841
- });
2689
+ const payload = decodeAnyPreset(trimmed);
2690
+ const style = styleFromDecodedPayload(payload, trimmed);
1842
2691
  return { style, presetCode: trimmed };
1843
2692
  } catch (error) {
1844
2693
  if (error instanceof PresetValidationError) {
1845
- throw new CliError(`Invalid preset: ${error.message}`);
2694
+ throw toCliPresetError(error);
1846
2695
  }
1847
2696
  throw error;
1848
2697
  }
1849
2698
  }
2699
+ function encodePresetFileJson(parsed) {
2700
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2701
+ throw new PresetValidationError("Expected an object at preset.", "preset");
2702
+ }
2703
+ if ("v" in parsed) {
2704
+ const v = parsed.v;
2705
+ if (v === 1) {
2706
+ const payload = validateCodecPayload(parsed);
2707
+ const json = stableStringifyCodecPayload(payload);
2708
+ return `${PRESET_CODEC_PREFIX}.${Buffer.from(json, "utf8").toString("base64url")}`;
2709
+ }
2710
+ if (v === 2) {
2711
+ const payload = validateCodecPayloadCanonical(parsed);
2712
+ const json = stableStringifyCodecPayloadCanonical(payload);
2713
+ return `${PRESET_CODEC_PREFIX}.${Buffer.from(json, "utf8").toString("base64url")}`;
2714
+ }
2715
+ throw new PresetValidationError(
2716
+ `Unsupported preset payload version ${String(v)}. This preset may require a newer version of the Wensity CLI.`,
2717
+ "preset.v"
2718
+ );
2719
+ }
2720
+ if ("baseColor" in parsed || "themeColor" in parsed || "chartPalette" in parsed || "headingFont" in parsed || "bodyFont" in parsed) {
2721
+ return encodePresetCanonical(parsed);
2722
+ }
2723
+ return encodePreset(parsed);
2724
+ }
2725
+ function styleFromDecodedPayload(payload, presetCode) {
2726
+ if (payload.v === 1) {
2727
+ return normalizeStyleConfig({
2728
+ ...codecPayloadToStyleInput(payload),
2729
+ presetCode
2730
+ });
2731
+ }
2732
+ return normalizeStyleConfigCanonical({
2733
+ baseColor: payload.baseColor,
2734
+ themeColor: payload.themeColor,
2735
+ chartPalette: payload.chartPalette,
2736
+ headingFont: payload.headingFont,
2737
+ bodyFont: payload.bodyFont,
2738
+ iconLibrary: payload.iconLibrary,
2739
+ radius: payload.radius,
2740
+ presetCode
2741
+ });
2742
+ }
2743
+ function toCliPresetError(error) {
2744
+ const message = error.message;
2745
+ if (/Unsupported preset payload version/i.test(message)) {
2746
+ return new CliError(
2747
+ `Invalid preset: ${message} Upgrade the Wensity CLI (wensity@0.1.7 or newer) and try again.`
2748
+ );
2749
+ }
2750
+ return new CliError(`Invalid preset: ${message}`);
2751
+ }
1850
2752
 
1851
2753
  // src/preset-init-writes.ts
1852
2754
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
@@ -1878,34 +2780,29 @@ async function commitPresetInitWrites(input) {
1878
2780
  // src/style-css.ts
1879
2781
  var ROOT_SELECTOR = ":root";
1880
2782
  var DARK_SELECTOR = ".dark";
1881
- function applyStyleToPrimitiveCss(block, styleInput) {
1882
- const style = normalizeStyleConfig(styleInput);
2783
+ var SHARED_TOKEN_KEYS = ["radius", "fontSans", "fontDisplay", "fontMono"];
2784
+ var MODE_TOKEN_KEYS = Object.keys(RESOLVED_TOKEN_CSS_VARS).filter(
2785
+ (key) => !SHARED_TOKEN_KEYS.includes(key)
2786
+ );
2787
+ function applyResolvedTokensToPrimitiveCss(block, tokens) {
1883
2788
  let next = block;
1884
- const radiusValue = RADIUS_PRESET_CSS[style.radius];
1885
- next = replaceDeclInSelector(next, ROOT_SELECTOR, "--primitive-radius", radiusValue);
1886
- next = replaceDeclInSelector(next, DARK_SELECTOR, "--primitive-radius", radiusValue);
1887
- for (const role of FONT_ROLE_KEYS) {
1888
- const cssVar = `--primitive-font-${role}`;
1889
- const cssValue = expandFontRole(style.fonts[role], role);
1890
- next = replaceDeclInSelector(next, ROOT_SELECTOR, cssVar, cssValue);
1891
- next = replaceDeclInSelector(next, DARK_SELECTOR, cssVar, cssValue);
1892
- }
1893
- if (style.colors?.light) {
1894
- for (const key of CONTROL_SOLID_COLOR_KEYS) {
1895
- const value = style.colors.light[key];
1896
- if (value === void 0) continue;
1897
- next = replaceDeclInSelector(next, ROOT_SELECTOR, CONTROL_SOLID_CSS_VARS[key], value);
1898
- }
2789
+ for (const key of SHARED_TOKEN_KEYS) {
2790
+ const cssVar = RESOLVED_TOKEN_CSS_VARS[key];
2791
+ const value = tokens[key];
2792
+ next = replaceDeclInSelector(next, ROOT_SELECTOR, cssVar, value);
2793
+ next = replaceDeclInSelector(next, DARK_SELECTOR, cssVar, value);
1899
2794
  }
1900
- if (style.colors?.dark) {
1901
- for (const key of CONTROL_SOLID_COLOR_KEYS) {
1902
- const value = style.colors.dark[key];
1903
- if (value === void 0) continue;
1904
- next = replaceDeclInSelector(next, DARK_SELECTOR, CONTROL_SOLID_CSS_VARS[key], value);
1905
- }
2795
+ for (const key of MODE_TOKEN_KEYS) {
2796
+ const cssVar = RESOLVED_TOKEN_CSS_VARS[key];
2797
+ next = replaceDeclInSelector(next, ROOT_SELECTOR, cssVar, tokens.light[key]);
2798
+ next = replaceDeclInSelector(next, DARK_SELECTOR, cssVar, tokens.dark[key]);
1906
2799
  }
1907
2800
  return next;
1908
2801
  }
2802
+ function applyStyleToPrimitiveCss(block, styleInput) {
2803
+ const style = normalizeProjectStyle(styleInput);
2804
+ return applyResolvedTokensToPrimitiveCss(block, resolveTokensForStyle(style));
2805
+ }
1909
2806
  function replaceDeclInSelector(css, selector, property, value) {
1910
2807
  const block = findTopLevelRule(css, selector);
1911
2808
  if (!block) return css;
@@ -2131,7 +3028,7 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
2131
3028
  const files = [];
2132
3029
  const componentContents = [];
2133
3030
  const iconSwaps = [];
2134
- const style = normalizeStyleConfig(config.style);
3031
+ const style = normalizeProjectStyle(config.style);
2135
3032
  const isPrimitiveCategory = isPrimitivePayloadCategory(payload.category);
2136
3033
  const payloadHelperNeeds = normalizePayloadHelperNeeds(
2137
3034
  "helperNeeds" in payload ? payload.helperNeeds : void 0
@@ -2188,8 +3085,10 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
2188
3085
  }
2189
3086
  const iconDependencySwap = aggregateIconDependencySwaps(iconSwaps);
2190
3087
  const declaredAfterSwap = applyIconDependencySwap(payload.dependencies ?? [], iconDependencySwap);
3088
+ const fonts = curatedFontsFromStyle(style);
3089
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
2191
3090
  const dependencies = collectDependencies({
2192
- declared: declaredAfterSwap,
3091
+ declared: [...declaredAfterSwap, ...fontPlan.packages],
2193
3092
  source: componentContents.join("\n"),
2194
3093
  needsUtility: helperNeeds.utils
2195
3094
  });
@@ -2198,7 +3097,8 @@ async function prepareInstall(projectRoot, config, payload, options = {}) {
2198
3097
  files,
2199
3098
  dependencies,
2200
3099
  ...cssFile ? { cssFile } : {},
2201
- ...isPrimitiveCategory ? { iconDependencySwap } : {}
3100
+ ...isPrimitiveCategory ? { iconDependencySwap } : {},
3101
+ ...fontPlan.packages.length > 0 ? { fontPackages: fontPlan.packages } : {}
2202
3102
  };
2203
3103
  }
2204
3104
  async function planPrimitiveCssFile(projectRoot, config, payload) {
@@ -2207,13 +3107,16 @@ async function planPrimitiveCssFile(projectRoot, config, payload) {
2207
3107
  }
2208
3108
  let primitiveCss = payload.primitiveCss.trim();
2209
3109
  if (!primitiveCss) return void 0;
2210
- const style = normalizeStyleConfig(config.style);
3110
+ const style = normalizeProjectStyle(config.style);
2211
3111
  primitiveCss = applyStyleToPrimitiveCss(primitiveCss, style).trim();
2212
3112
  const cssRelative = config.tailwind?.css;
2213
3113
  if (!cssRelative) return void 0;
2214
3114
  const absolutePath = resolveInside(projectRoot, cssRelative);
2215
3115
  const existing = await fileExists(absolutePath) ? await readFile4(absolutePath, "utf8") : "";
2216
- const next = upsertPrimitiveCssBlock(existing, primitiveCss);
3116
+ const withTokens = upsertPrimitiveCssBlock(existing, primitiveCss);
3117
+ const fonts = curatedFontsFromStyle(style);
3118
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
3119
+ const next = upsertFontImportBlock(withTokens, fontPlan.block);
2217
3120
  if (next === existing) return void 0;
2218
3121
  return {
2219
3122
  absolutePath,
@@ -2489,6 +3392,9 @@ async function initWithPreset(context, options) {
2489
3392
  presetCode: options.presetCode,
2490
3393
  presetFile: options.presetFile
2491
3394
  });
3395
+ const fonts = curatedFontsFromStyle(style);
3396
+ const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
3397
+ const installDependencies = [...options.dependencies, ...fontPlan.packages];
2492
3398
  const configPath = join6(context.cwd, CONFIG_FILE_NAME);
2493
3399
  const configExists = await fileExists(configPath);
2494
3400
  let config;
@@ -2531,8 +3437,11 @@ async function initWithPreset(context, options) {
2531
3437
  }
2532
3438
  const cssAbsolute = resolveInside(context.cwd, cssRelative);
2533
3439
  const existingCss = await fileExists(cssAbsolute) ? await readFile5(cssAbsolute, "utf8") : "";
2534
- const nextCss = upsertPrimitiveCssBlock(existingCss, primitiveCss);
3440
+ const withTokens = upsertPrimitiveCssBlock(existingCss, primitiveCss);
3441
+ const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
2535
3442
  const cssChanged = nextCss !== existingCss;
3443
+ const installedPackages = readInstalledPackageNames(context.cwd);
3444
+ const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
2536
3445
  if (options.dryRun) {
2537
3446
  printTitle("Project initialization preview (preset)");
2538
3447
  print(action("Would write:"));
@@ -2541,7 +3450,11 @@ async function initWithPreset(context, options) {
2541
3450
  print(formatList(planned));
2542
3451
  print(action(`Preset: ${presetCode.slice(0, 24)}\u2026`));
2543
3452
  print(action(`Style: icon=${style.iconLibrary} radius=${style.radius}`));
2544
- if (options.install) print(action(`Would install: ${options.dependencies.join(" ")}`));
3453
+ if (fontPlan.packages.length > 0) {
3454
+ print(action(`Fonts: ${fontPlan.packages.join(" ")}`));
3455
+ }
3456
+ if (options.install) print(action(`Would install: ${installDependencies.join(" ")}`));
3457
+ if (unusedFontWarning) warn(unusedFontWarning);
2545
3458
  return;
2546
3459
  }
2547
3460
  const shouldWriteUtility = options.force || !await fileExists(utilityPath);
@@ -2562,7 +3475,7 @@ async function initWithPreset(context, options) {
2562
3475
  }
2563
3476
  });
2564
3477
  if (options.install) {
2565
- runInstall(context.cwd, missingDependencies(context.cwd, options.dependencies));
3478
+ runInstall(context.cwd, missingDependencies(context.cwd, installDependencies));
2566
3479
  }
2567
3480
  printTitle("Project initialized");
2568
3481
  const created = [
@@ -2576,7 +3489,11 @@ async function initWithPreset(context, options) {
2576
3489
  } else {
2577
3490
  created.push(action(`CSS tokens unchanged: ${toPosixPath(cssRelative)}`));
2578
3491
  }
3492
+ if (fontPlan.packages.length > 0) {
3493
+ created.push(success(`fonts: ${fontPlan.packages.join(", ")}`));
3494
+ }
2579
3495
  print(panel("Created", created));
3496
+ if (unusedFontWarning) warn(unusedFontWarning);
2580
3497
  print(nextSteps([
2581
3498
  "Browse available components: npx wensity list",
2582
3499
  "Install a primitive: npx wensity add button"
@@ -2721,9 +3638,11 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
2721
3638
  await assertNoConflicts(plannedInstallFiles, force || dryRun);
2722
3639
  const plannedFiles = summarizeFiles(loaded.projectRoot, plannedInstallFiles);
2723
3640
  const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
2724
- const style = normalizeStyleConfig(loaded.config.style);
3641
+ const style = normalizeProjectStyle(loaded.config.style);
3642
+ const fonts = curatedFontsFromStyle(style);
2725
3643
  const installedPackages = readInstalledPackageNames(loaded.projectRoot);
2726
3644
  const oppositeIconWarning = installedPackages && prepared.iconDependencySwap ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
3645
+ const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
2727
3646
  if (dryRun) {
2728
3647
  printTitle(updateMode ? "Update preview" : "Install preview");
2729
3648
  print(action("Would write:"));
@@ -2733,6 +3652,7 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
2733
3652
  }
2734
3653
  if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
2735
3654
  if (oppositeIconWarning) warn(oppositeIconWarning);
3655
+ if (unusedFontWarning) warn(unusedFontWarning);
2736
3656
  return;
2737
3657
  }
2738
3658
  await writePlannedFiles(plannedInstallFiles);
@@ -2745,6 +3665,7 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
2745
3665
  ...plannedFiles.map((file) => success(file))
2746
3666
  ]));
2747
3667
  if (oppositeIconWarning) warn(oppositeIconWarning);
3668
+ if (unusedFontWarning) warn(unusedFontWarning);
2748
3669
  if (!install && missing.length > 0) {
2749
3670
  warn(`Dependencies were not installed: ${missing.join(" ")}`);
2750
3671
  print(nextSteps([