wensity 0.1.5 → 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.
- package/LICENSE +21 -0
- package/README.md +49 -11
- package/dist/index.js +2547 -72
- package/package.json +12 -8
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands.ts
|
|
4
|
-
import { mkdir as
|
|
5
|
-
import { dirname as
|
|
4
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
5
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
var CLI_NAME = "wensity";
|
|
9
|
-
var CLI_VERSION = "0.1.
|
|
9
|
+
var CLI_VERSION = "0.1.7";
|
|
10
10
|
var CONFIG_FILE_NAME = "wensity.json";
|
|
11
11
|
var DEFAULT_API_ORIGIN = "https://ui.wensity.com";
|
|
12
12
|
var DEFAULT_REGISTRY_PATH = "/api/registry";
|
|
@@ -14,10 +14,11 @@ var DEFAULT_COMPONENTS_DIR = "src/components/wensity";
|
|
|
14
14
|
var DEFAULT_UTILS_PATH = "src/lib/utils";
|
|
15
15
|
var DEFAULT_COMPONENTS_ALIAS = "@/components/wensity";
|
|
16
16
|
var DEFAULT_UTILS_ALIAS = "@/lib/utils";
|
|
17
|
+
var INIT_PRIMITIVE_CSS_SLUG = "button";
|
|
17
18
|
var HELP_TEXT = `Wensity CLI ${CLI_VERSION}
|
|
18
19
|
|
|
19
20
|
Usage:
|
|
20
|
-
wensity init [options]
|
|
21
|
+
wensity init [--preset <code>] [--preset-file <path>] [options]
|
|
21
22
|
wensity login [options]
|
|
22
23
|
wensity logout
|
|
23
24
|
wensity whoami [--json]
|
|
@@ -27,7 +28,7 @@ Usage:
|
|
|
27
28
|
wensity version
|
|
28
29
|
|
|
29
30
|
Commands:
|
|
30
|
-
init Create wensity.json and local helper files
|
|
31
|
+
init Create wensity.json and local helper files (optional --preset applies style + CSS)
|
|
31
32
|
login Save a Wensity API token for premium component access
|
|
32
33
|
logout Remove saved CLI credentials
|
|
33
34
|
whoami Verify the saved token and print the authenticated account
|
|
@@ -41,12 +42,21 @@ Options:
|
|
|
41
42
|
--registry <url> Override the registry endpoint
|
|
42
43
|
--type <type> Select component or block, defaults to component
|
|
43
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
|
|
44
47
|
--force Overwrite generated files when adding or initializing
|
|
45
48
|
--no-install Write files without running the package manager
|
|
46
49
|
--dry-run Print planned writes and installs without changing files
|
|
47
50
|
--json Print machine-readable output where supported
|
|
48
51
|
--help Show help
|
|
49
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
|
|
50
60
|
`;
|
|
51
61
|
|
|
52
62
|
// src/errors.ts
|
|
@@ -108,18 +118,1527 @@ function hasFlag(args, name) {
|
|
|
108
118
|
return args.options.get(name) === true;
|
|
109
119
|
}
|
|
110
120
|
function expectsValue(name) {
|
|
111
|
-
return ["api-url", "registry", "token", "type"].includes(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
|
+
|
|
133
|
+
// ../wensity-preset/src/constants.ts
|
|
134
|
+
var ICON_LIBRARIES = ["tabler", "lucide"];
|
|
135
|
+
var RADIUS_PRESETS = ["none", "small", "medium", "large"];
|
|
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"];
|
|
161
|
+
var RADIUS_PRESET_CSS = {
|
|
162
|
+
none: "0px",
|
|
163
|
+
small: "0.5rem",
|
|
164
|
+
medium: "0.875rem",
|
|
165
|
+
large: "1.25rem"
|
|
166
|
+
};
|
|
167
|
+
var FONT_ID_EXPANSIONS = {
|
|
168
|
+
"site:sans": "var(--font-sans)",
|
|
169
|
+
"site:display": "var(--font-display)",
|
|
170
|
+
"site:mono": "var(--font-mono)",
|
|
171
|
+
"system-sans": "ui-sans-serif, system-ui, sans-serif",
|
|
172
|
+
"system-mono": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"
|
|
173
|
+
};
|
|
174
|
+
var CONTROL_SOLID_COLOR_KEYS = [
|
|
175
|
+
"controlSolid",
|
|
176
|
+
"controlSolidHover",
|
|
177
|
+
"controlSolidActive",
|
|
178
|
+
"controlSolidForeground"
|
|
179
|
+
];
|
|
180
|
+
var STYLE_OBJECT_KEYS = [
|
|
181
|
+
"iconLibrary",
|
|
182
|
+
"radius",
|
|
183
|
+
"fonts",
|
|
184
|
+
"colors",
|
|
185
|
+
"presetCode"
|
|
186
|
+
];
|
|
187
|
+
var STYLE_OBJECT_KEYS_CANONICAL = [
|
|
188
|
+
"baseColor",
|
|
189
|
+
"themeColor",
|
|
190
|
+
"chartPalette",
|
|
191
|
+
"headingFont",
|
|
192
|
+
"bodyFont",
|
|
193
|
+
"iconLibrary",
|
|
194
|
+
"radius",
|
|
195
|
+
"presetCode"
|
|
196
|
+
];
|
|
197
|
+
var FONT_ROLE_KEYS = ["sans", "display", "mono"];
|
|
198
|
+
var COLOR_MODE_KEYS = ["light", "dark"];
|
|
199
|
+
var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
200
|
+
var CUSTOM_FONT_PREFIX = "custom:";
|
|
201
|
+
var CUSTOM_FONT_STACK_MAX_LENGTH = 120;
|
|
202
|
+
var PRESET_CODEC_PREFIX = "wsty1";
|
|
203
|
+
var PRESET_CODEC_MAX_DECODED_JSON_BYTES = 4 * 1024;
|
|
204
|
+
var PRESET_CODEC_MAX_CODE_LENGTH = 6 * 1024;
|
|
205
|
+
var CONFIG_SCHEMA_VERSION = 1;
|
|
206
|
+
function isBuiltinFontId(value) {
|
|
207
|
+
return BUILTIN_FONT_IDS.includes(value);
|
|
208
|
+
}
|
|
209
|
+
function isIconLibrary(value) {
|
|
210
|
+
return ICON_LIBRARIES.includes(value);
|
|
211
|
+
}
|
|
212
|
+
function isRadiusPreset(value) {
|
|
213
|
+
return RADIUS_PRESETS.includes(value);
|
|
214
|
+
}
|
|
215
|
+
function isFontRoleValue(value) {
|
|
216
|
+
if (isBuiltinFontId(value)) return true;
|
|
217
|
+
return value.startsWith(CUSTOM_FONT_PREFIX) && value.length > CUSTOM_FONT_PREFIX.length;
|
|
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
|
+
}
|
|
234
|
+
|
|
235
|
+
// ../wensity-preset/src/defaults.ts
|
|
236
|
+
var DEFAULT_STYLE_FONTS = {
|
|
237
|
+
sans: "site",
|
|
238
|
+
display: "site",
|
|
239
|
+
mono: "system-mono"
|
|
240
|
+
};
|
|
241
|
+
var DEFAULT_CONTROL_SOLID_LIGHT = {
|
|
242
|
+
controlSolid: "#24292d",
|
|
243
|
+
controlSolidHover: "#2c3237",
|
|
244
|
+
controlSolidActive: "#1e2326",
|
|
245
|
+
controlSolidForeground: "#ffffff"
|
|
246
|
+
};
|
|
247
|
+
var DEFAULT_CONTROL_SOLID_DARK = {
|
|
248
|
+
controlSolid: "#f5f3ee",
|
|
249
|
+
controlSolidHover: "#ffffff",
|
|
250
|
+
controlSolidActive: "#e8e4da",
|
|
251
|
+
controlSolidForeground: "#151719"
|
|
252
|
+
};
|
|
253
|
+
var DEFAULT_STYLE_CONFIG = {
|
|
254
|
+
iconLibrary: "tabler",
|
|
255
|
+
radius: "medium",
|
|
256
|
+
fonts: { ...DEFAULT_STYLE_FONTS },
|
|
257
|
+
colors: {
|
|
258
|
+
light: { ...DEFAULT_CONTROL_SOLID_LIGHT },
|
|
259
|
+
dark: { ...DEFAULT_CONTROL_SOLID_DARK }
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
function createDefaultStyleConfig() {
|
|
263
|
+
return {
|
|
264
|
+
iconLibrary: DEFAULT_STYLE_CONFIG.iconLibrary,
|
|
265
|
+
radius: DEFAULT_STYLE_CONFIG.radius,
|
|
266
|
+
fonts: { ...DEFAULT_STYLE_CONFIG.fonts },
|
|
267
|
+
colors: {
|
|
268
|
+
light: { ...DEFAULT_CONTROL_SOLID_LIGHT },
|
|
269
|
+
dark: { ...DEFAULT_CONTROL_SOLID_DARK }
|
|
270
|
+
}
|
|
271
|
+
};
|
|
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
|
+
}
|
|
293
|
+
|
|
294
|
+
// ../wensity-preset/src/schema.ts
|
|
295
|
+
var fontRoleSchema = {
|
|
296
|
+
oneOf: [
|
|
297
|
+
{ type: "string", enum: [...BUILTIN_FONT_IDS] },
|
|
298
|
+
{
|
|
299
|
+
type: "string",
|
|
300
|
+
pattern: `^${CUSTOM_FONT_PREFIX}.+`,
|
|
301
|
+
minLength: CUSTOM_FONT_PREFIX.length + 1
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
};
|
|
305
|
+
var controlSolidColorsSchema = {
|
|
306
|
+
type: "object",
|
|
307
|
+
additionalProperties: false,
|
|
308
|
+
properties: Object.fromEntries(
|
|
309
|
+
CONTROL_SOLID_COLOR_KEYS.map((key) => [
|
|
310
|
+
key,
|
|
311
|
+
{ type: "string", pattern: HEX_COLOR_PATTERN.source }
|
|
312
|
+
])
|
|
313
|
+
)
|
|
314
|
+
};
|
|
315
|
+
var legacyStyleSchema = {
|
|
316
|
+
type: "object",
|
|
317
|
+
additionalProperties: false,
|
|
318
|
+
properties: {
|
|
319
|
+
iconLibrary: { type: "string", enum: [...ICON_LIBRARIES] },
|
|
320
|
+
radius: { type: "string", enum: [...RADIUS_PRESETS] },
|
|
321
|
+
fonts: {
|
|
322
|
+
type: "object",
|
|
323
|
+
additionalProperties: false,
|
|
324
|
+
properties: {
|
|
325
|
+
sans: fontRoleSchema,
|
|
326
|
+
display: fontRoleSchema,
|
|
327
|
+
mono: fontRoleSchema
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
colors: {
|
|
331
|
+
type: "object",
|
|
332
|
+
additionalProperties: false,
|
|
333
|
+
properties: {
|
|
334
|
+
light: controlSolidColorsSchema,
|
|
335
|
+
dark: controlSolidColorsSchema
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
presetCode: {
|
|
339
|
+
type: "string",
|
|
340
|
+
minLength: 1,
|
|
341
|
+
maxLength: PRESET_CODEC_MAX_CODE_LENGTH
|
|
342
|
+
}
|
|
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
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// ../wensity-preset/src/fonts.ts
|
|
365
|
+
var ALLOWED_UNQUOTED_FONT_TOKENS = [
|
|
366
|
+
"ui-sans-serif",
|
|
367
|
+
"system-ui",
|
|
368
|
+
"sans-serif",
|
|
369
|
+
"monospace",
|
|
370
|
+
"ui-monospace",
|
|
371
|
+
"SFMono-Regular",
|
|
372
|
+
"Menlo",
|
|
373
|
+
"Monaco",
|
|
374
|
+
"Consolas",
|
|
375
|
+
"var(--font-sans)",
|
|
376
|
+
"var(--font-display)",
|
|
377
|
+
"var(--font-mono)"
|
|
378
|
+
];
|
|
379
|
+
var ALLOWED_UNQUOTED_SET = new Set(ALLOWED_UNQUOTED_FONT_TOKENS);
|
|
380
|
+
var QUOTED_FAMILY_PATTERN = /^(["'])([A-Za-z0-9 -]+)\1$/;
|
|
381
|
+
var CONTROL_OR_BREAKOUT_PATTERN = /[\u0000-\u001f\u007f;{}\\\n\r<>]|url\s*\(|@import|expression\s*\(|<\//i;
|
|
382
|
+
function validateCustomFontStack(value, path) {
|
|
383
|
+
if (!value.startsWith(CUSTOM_FONT_PREFIX)) {
|
|
384
|
+
throw new PresetValidationError(`Expected custom font stack at ${path}.`, path);
|
|
385
|
+
}
|
|
386
|
+
const stack = value.slice(CUSTOM_FONT_PREFIX.length);
|
|
387
|
+
if (stack.length === 0) {
|
|
388
|
+
throw new PresetValidationError(`Empty custom font stack at ${path}.`, path);
|
|
389
|
+
}
|
|
390
|
+
if (stack.length > CUSTOM_FONT_STACK_MAX_LENGTH) {
|
|
391
|
+
throw new PresetValidationError(
|
|
392
|
+
`Custom font stack at ${path} exceeds ${CUSTOM_FONT_STACK_MAX_LENGTH} characters.`,
|
|
393
|
+
path
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (CONTROL_OR_BREAKOUT_PATTERN.test(stack)) {
|
|
397
|
+
throw new PresetValidationError(
|
|
398
|
+
`Custom font stack at ${path} contains forbidden characters or CSS breakout syntax.`,
|
|
399
|
+
path
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const tokens = splitFontStackTokens(stack);
|
|
403
|
+
if (tokens.length === 0) {
|
|
404
|
+
throw new PresetValidationError(`Custom font stack at ${path} has no tokens.`, path);
|
|
405
|
+
}
|
|
406
|
+
for (const token of tokens) {
|
|
407
|
+
validateFontStackToken(token, path);
|
|
408
|
+
}
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
function splitFontStackTokens(stack) {
|
|
412
|
+
const tokens = [];
|
|
413
|
+
let current = "";
|
|
414
|
+
let quote = null;
|
|
415
|
+
for (let i = 0; i < stack.length; i += 1) {
|
|
416
|
+
const ch = stack[i];
|
|
417
|
+
if (quote) {
|
|
418
|
+
current += ch;
|
|
419
|
+
if (ch === quote) quote = null;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (ch === '"' || ch === "'") {
|
|
423
|
+
quote = ch;
|
|
424
|
+
current += ch;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (ch === ",") {
|
|
428
|
+
const trimmed2 = current.trim();
|
|
429
|
+
if (trimmed2) tokens.push(trimmed2);
|
|
430
|
+
current = "";
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
current += ch;
|
|
434
|
+
}
|
|
435
|
+
if (quote) {
|
|
436
|
+
throw new PresetValidationError("Unterminated quoted font family in custom stack.", "fonts");
|
|
437
|
+
}
|
|
438
|
+
const trimmed = current.trim();
|
|
439
|
+
if (trimmed) tokens.push(trimmed);
|
|
440
|
+
return tokens;
|
|
441
|
+
}
|
|
442
|
+
function validateFontStackToken(token, path) {
|
|
443
|
+
if (QUOTED_FAMILY_PATTERN.test(token)) return;
|
|
444
|
+
if (ALLOWED_UNQUOTED_SET.has(token)) return;
|
|
445
|
+
throw new PresetValidationError(
|
|
446
|
+
`Invalid font token "${token}" at ${path}. Use a quoted family or an allowlisted keyword.`,
|
|
447
|
+
path
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
function expandFontRole(value, role) {
|
|
451
|
+
if (value.startsWith(CUSTOM_FONT_PREFIX)) {
|
|
452
|
+
validateCustomFontStack(value, `fonts.${role}`);
|
|
453
|
+
return value.slice(CUSTOM_FONT_PREFIX.length);
|
|
454
|
+
}
|
|
455
|
+
if (value === "site") {
|
|
456
|
+
if (role === "sans") return FONT_ID_EXPANSIONS["site:sans"];
|
|
457
|
+
if (role === "display") return FONT_ID_EXPANSIONS["site:display"];
|
|
458
|
+
return FONT_ID_EXPANSIONS["site:mono"];
|
|
459
|
+
}
|
|
460
|
+
if (value === "system-sans") return FONT_ID_EXPANSIONS["system-sans"];
|
|
461
|
+
if (value === "system-mono") return FONT_ID_EXPANSIONS["system-mono"];
|
|
462
|
+
if (isBuiltinFontId(value)) {
|
|
463
|
+
throw new PresetValidationError(`Unhandled built-in font id "${value}".`, `fonts.${role}`);
|
|
464
|
+
}
|
|
465
|
+
throw new PresetValidationError(`Invalid font role value at fonts.${role}.`, `fonts.${role}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ../wensity-preset/src/validate.ts
|
|
469
|
+
var FORBIDDEN_OBJECT_KEYS = ["__proto__", "constructor", "prototype"];
|
|
470
|
+
var PresetValidationError = class extends Error {
|
|
471
|
+
path;
|
|
472
|
+
constructor(message, path = "style") {
|
|
473
|
+
super(message);
|
|
474
|
+
this.name = "PresetValidationError";
|
|
475
|
+
this.path = path;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
function assertNoForbiddenObjectKeys(value, path) {
|
|
479
|
+
for (const key of FORBIDDEN_OBJECT_KEYS) {
|
|
480
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
481
|
+
throw new PresetValidationError(`Forbidden key at ${path}.`, path);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function assertPlainObject(value, path) {
|
|
486
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
487
|
+
throw new PresetValidationError(`Expected an object at ${path}.`, path);
|
|
488
|
+
}
|
|
489
|
+
assertNoForbiddenObjectKeys(value, path);
|
|
490
|
+
}
|
|
491
|
+
function assertAllowedKeys(value, allowed, path) {
|
|
492
|
+
const allowedSet = new Set(allowed);
|
|
493
|
+
for (const key of Object.keys(value)) {
|
|
494
|
+
if (!allowedSet.has(key)) {
|
|
495
|
+
throw new PresetValidationError(`Unknown key "${key}" at ${path}.`, `${path}.${key}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function validateHexColor(value, path) {
|
|
500
|
+
if (typeof value !== "string" || !HEX_COLOR_PATTERN.test(value)) {
|
|
501
|
+
throw new PresetValidationError(
|
|
502
|
+
`Invalid color at ${path}. Expected #RGB, #RRGGBB, or #RRGGBBAA.`,
|
|
503
|
+
path
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
function validateFontRole(value, path) {
|
|
509
|
+
if (typeof value !== "string" || !isFontRoleValue(value)) {
|
|
510
|
+
throw new PresetValidationError(
|
|
511
|
+
`Invalid font at ${path}. Expected site | system-sans | system-mono | custom:<stack>.`,
|
|
512
|
+
path
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (value.startsWith(CUSTOM_FONT_PREFIX)) {
|
|
516
|
+
validateCustomFontStack(value, path);
|
|
517
|
+
}
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
function validateControlSolidPartial(value, path) {
|
|
521
|
+
assertPlainObject(value, path);
|
|
522
|
+
assertAllowedKeys(value, CONTROL_SOLID_COLOR_KEYS, path);
|
|
523
|
+
const result = {};
|
|
524
|
+
for (const key of CONTROL_SOLID_COLOR_KEYS) {
|
|
525
|
+
if (key in value) {
|
|
526
|
+
result[key] = validateHexColor(value[key], `${path}.${key}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
function validateFontsPartial(value, path) {
|
|
532
|
+
assertPlainObject(value, path);
|
|
533
|
+
assertAllowedKeys(value, FONT_ROLE_KEYS, path);
|
|
534
|
+
const result = {};
|
|
535
|
+
for (const key of FONT_ROLE_KEYS) {
|
|
536
|
+
if (key in value) {
|
|
537
|
+
result[key] = validateFontRole(value[key], `${path}.${key}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
function validateColorsPartial(value, path) {
|
|
543
|
+
assertPlainObject(value, path);
|
|
544
|
+
assertAllowedKeys(value, COLOR_MODE_KEYS, path);
|
|
545
|
+
const result = {};
|
|
546
|
+
for (const mode of COLOR_MODE_KEYS) {
|
|
547
|
+
if (mode in value) {
|
|
548
|
+
result[mode] = validateControlSolidPartial(value[mode], `${path}.${mode}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
function validateStyleConfigPartial(value) {
|
|
554
|
+
if (value === void 0 || value === null) return {};
|
|
555
|
+
assertPlainObject(value, "style");
|
|
556
|
+
assertAllowedKeys(value, STYLE_OBJECT_KEYS, "style");
|
|
557
|
+
const result = {};
|
|
558
|
+
if ("iconLibrary" in value) {
|
|
559
|
+
if (typeof value.iconLibrary !== "string" || !isIconLibrary(value.iconLibrary)) {
|
|
560
|
+
throw new PresetValidationError(
|
|
561
|
+
"Invalid style.iconLibrary. Expected tabler | lucide.",
|
|
562
|
+
"style.iconLibrary"
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
result.iconLibrary = value.iconLibrary;
|
|
566
|
+
}
|
|
567
|
+
if ("radius" in value) {
|
|
568
|
+
if (typeof value.radius !== "string" || !isRadiusPreset(value.radius)) {
|
|
569
|
+
throw new PresetValidationError(
|
|
570
|
+
"Invalid style.radius. Expected none | small | medium | large.",
|
|
571
|
+
"style.radius"
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
result.radius = value.radius;
|
|
575
|
+
}
|
|
576
|
+
if ("fonts" in value) {
|
|
577
|
+
result.fonts = validateFontsPartial(value.fonts, "style.fonts");
|
|
578
|
+
}
|
|
579
|
+
if ("colors" in value) {
|
|
580
|
+
result.colors = validateColorsPartial(value.colors, "style.colors");
|
|
581
|
+
}
|
|
582
|
+
if ("presetCode" in value) {
|
|
583
|
+
if (typeof value.presetCode !== "string" || value.presetCode.length === 0) {
|
|
584
|
+
throw new PresetValidationError(
|
|
585
|
+
"Invalid style.presetCode. Expected a non-empty string.",
|
|
586
|
+
"style.presetCode"
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (value.presetCode.length > PRESET_CODEC_MAX_CODE_LENGTH) {
|
|
590
|
+
throw new PresetValidationError(
|
|
591
|
+
`Invalid style.presetCode. Exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
|
|
592
|
+
"style.presetCode"
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
result.presetCode = value.presetCode;
|
|
596
|
+
}
|
|
597
|
+
return result;
|
|
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
|
+
}
|
|
684
|
+
|
|
685
|
+
// ../wensity-preset/src/normalize.ts
|
|
686
|
+
function mergeControlSolid(base, partial) {
|
|
687
|
+
if (!partial) return { ...base };
|
|
688
|
+
const merged = { ...base };
|
|
689
|
+
for (const key of CONTROL_SOLID_COLOR_KEYS) {
|
|
690
|
+
const value = partial[key];
|
|
691
|
+
if (value !== void 0) merged[key] = value;
|
|
692
|
+
}
|
|
693
|
+
return merged;
|
|
694
|
+
}
|
|
695
|
+
function mergeFonts(base, partial) {
|
|
696
|
+
if (!partial) return { ...base };
|
|
697
|
+
const merged = { ...base };
|
|
698
|
+
for (const key of FONT_ROLE_KEYS) {
|
|
699
|
+
const value = partial[key];
|
|
700
|
+
if (value !== void 0) merged[key] = value;
|
|
701
|
+
}
|
|
702
|
+
return merged;
|
|
703
|
+
}
|
|
704
|
+
function mergeColors(base, partial) {
|
|
705
|
+
return {
|
|
706
|
+
light: mergeControlSolid(base.light, partial?.light),
|
|
707
|
+
dark: mergeControlSolid(base.dark, partial?.dark)
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function normalizeStyleConfig(partial) {
|
|
711
|
+
const validated = validateStyleConfigPartial(partial ?? void 0);
|
|
712
|
+
const defaults = createDefaultStyleConfig();
|
|
713
|
+
const normalized = {
|
|
714
|
+
iconLibrary: validated.iconLibrary ?? defaults.iconLibrary,
|
|
715
|
+
radius: validated.radius ?? defaults.radius,
|
|
716
|
+
fonts: mergeFonts(defaults.fonts, validated.fonts),
|
|
717
|
+
colors: mergeColors(defaults.colors, validated.colors)
|
|
718
|
+
};
|
|
719
|
+
if (validated.presetCode !== void 0) {
|
|
720
|
+
normalized.presetCode = validated.presetCode;
|
|
721
|
+
}
|
|
722
|
+
return normalized;
|
|
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
|
+
}
|
|
741
|
+
|
|
742
|
+
// ../wensity-preset/src/codec.ts
|
|
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
|
+
];
|
|
754
|
+
function assertPlainObject2(value, path) {
|
|
755
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
756
|
+
throw new PresetValidationError(`Expected an object at ${path}.`, path);
|
|
757
|
+
}
|
|
758
|
+
assertNoForbiddenObjectKeys(value, path);
|
|
759
|
+
}
|
|
760
|
+
function assertAllowedKeys2(value, allowed, path) {
|
|
761
|
+
const allowedSet = new Set(allowed);
|
|
762
|
+
for (const key of Object.keys(value)) {
|
|
763
|
+
if (!allowedSet.has(key)) {
|
|
764
|
+
throw new PresetValidationError(`Unknown key "${key}" at ${path}.`, `${path}.${key}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function utf8ByteLength(text) {
|
|
769
|
+
return new TextEncoder().encode(text).byteLength;
|
|
770
|
+
}
|
|
771
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
772
|
+
function toBase64Url(bytes) {
|
|
773
|
+
let out = "";
|
|
774
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
775
|
+
const a = bytes[i];
|
|
776
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
777
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
778
|
+
const triple = a << 16 | b << 8 | c;
|
|
779
|
+
out += BASE64URL_ALPHABET[triple >> 18 & 63];
|
|
780
|
+
out += BASE64URL_ALPHABET[triple >> 12 & 63];
|
|
781
|
+
if (i + 1 < bytes.length) out += BASE64URL_ALPHABET[triple >> 6 & 63];
|
|
782
|
+
if (i + 2 < bytes.length) out += BASE64URL_ALPHABET[triple & 63];
|
|
783
|
+
}
|
|
784
|
+
return out;
|
|
785
|
+
}
|
|
786
|
+
function fromBase64Url(code) {
|
|
787
|
+
if (code.length === 0 || /[^A-Za-z0-9_-]/.test(code)) {
|
|
788
|
+
throw new PresetValidationError("Preset code is not valid base64url.", "preset");
|
|
789
|
+
}
|
|
790
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
791
|
+
for (let i = 0; i < BASE64URL_ALPHABET.length; i++) {
|
|
792
|
+
lookup.set(BASE64URL_ALPHABET[i], i);
|
|
793
|
+
}
|
|
794
|
+
const out = new Uint8Array(Math.floor(code.length * 3 / 4));
|
|
795
|
+
let outIndex = 0;
|
|
796
|
+
for (let i = 0; i < code.length; i += 4) {
|
|
797
|
+
const c0 = lookup.get(code[i]);
|
|
798
|
+
const c1 = lookup.get(code[i + 1]);
|
|
799
|
+
const c2 = i + 2 < code.length ? lookup.get(code[i + 2]) : 0;
|
|
800
|
+
const c3 = i + 3 < code.length ? lookup.get(code[i + 3]) : 0;
|
|
801
|
+
if (c0 === void 0 || c1 === void 0 || c2 === void 0 || c3 === void 0) {
|
|
802
|
+
throw new PresetValidationError("Preset code is not valid base64url.", "preset");
|
|
803
|
+
}
|
|
804
|
+
const triple = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
805
|
+
if (outIndex < out.length) out[outIndex++] = triple >> 16 & 255;
|
|
806
|
+
if (i + 2 < code.length && outIndex < out.length) out[outIndex++] = triple >> 8 & 255;
|
|
807
|
+
if (i + 3 < code.length && outIndex < out.length) out[outIndex++] = triple & 255;
|
|
808
|
+
}
|
|
809
|
+
return outIndex === out.length ? out : out.subarray(0, outIndex);
|
|
810
|
+
}
|
|
811
|
+
function stableStringifyCodecPayload(payload) {
|
|
812
|
+
const parts = [
|
|
813
|
+
`"v":${payload.v}`,
|
|
814
|
+
`"iconLibrary":${JSON.stringify(payload.iconLibrary)}`,
|
|
815
|
+
`"radius":${JSON.stringify(payload.radius)}`,
|
|
816
|
+
`"fonts":${stableStringifyFonts(payload.fonts)}`
|
|
817
|
+
];
|
|
818
|
+
if (payload.colors !== void 0) {
|
|
819
|
+
parts.push(`"colors":${stableStringifyColors(payload.colors)}`);
|
|
820
|
+
}
|
|
821
|
+
return `{${parts.join(",")}}`;
|
|
822
|
+
}
|
|
823
|
+
function stableStringifyFonts(fonts) {
|
|
824
|
+
return `{"sans":${JSON.stringify(fonts.sans)},"display":${JSON.stringify(fonts.display)},"mono":${JSON.stringify(fonts.mono)}}`;
|
|
825
|
+
}
|
|
826
|
+
function stableStringifyColors(colors) {
|
|
827
|
+
const modeParts = [];
|
|
828
|
+
for (const mode of COLOR_MODE_KEYS) {
|
|
829
|
+
const partial = colors[mode];
|
|
830
|
+
if (!partial) continue;
|
|
831
|
+
const keyParts = [];
|
|
832
|
+
for (const key of CONTROL_SOLID_COLOR_KEYS) {
|
|
833
|
+
const value = partial[key];
|
|
834
|
+
if (value !== void 0) {
|
|
835
|
+
keyParts.push(`${JSON.stringify(key)}:${JSON.stringify(value)}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (keyParts.length > 0) {
|
|
839
|
+
modeParts.push(`${JSON.stringify(mode)}:{${keyParts.join(",")}}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return `{${modeParts.join(",")}}`;
|
|
843
|
+
}
|
|
844
|
+
function validateCodecFonts(value, path) {
|
|
845
|
+
assertPlainObject2(value, path);
|
|
846
|
+
assertAllowedKeys2(value, FONT_ROLE_KEYS, path);
|
|
847
|
+
for (const key of FONT_ROLE_KEYS) {
|
|
848
|
+
if (!(key in value)) {
|
|
849
|
+
throw new PresetValidationError(`Missing required font role "${key}" at ${path}.`, `${path}.${key}`);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const fonts = {};
|
|
853
|
+
for (const key of FONT_ROLE_KEYS) {
|
|
854
|
+
const raw = value[key];
|
|
855
|
+
if (typeof raw !== "string" || !isFontRoleValue(raw)) {
|
|
856
|
+
throw new PresetValidationError(
|
|
857
|
+
`Invalid font at ${path}.${key}. Expected site | system-sans | system-mono | custom:<stack>.`,
|
|
858
|
+
`${path}.${key}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (raw.startsWith(CUSTOM_FONT_PREFIX)) {
|
|
862
|
+
validateCustomFontStack(raw, `${path}.${key}`);
|
|
863
|
+
}
|
|
864
|
+
fonts[key] = raw;
|
|
865
|
+
}
|
|
866
|
+
return fonts;
|
|
867
|
+
}
|
|
868
|
+
function validateCodecColors(value, path) {
|
|
869
|
+
const partial = validateStyleConfigPartial({ colors: value });
|
|
870
|
+
if (!partial.colors) {
|
|
871
|
+
throw new PresetValidationError(`Invalid colors at ${path}.`, path);
|
|
872
|
+
}
|
|
873
|
+
return partial.colors;
|
|
874
|
+
}
|
|
875
|
+
function validateCodecPayload(value) {
|
|
876
|
+
assertPlainObject2(value, "preset");
|
|
877
|
+
assertAllowedKeys2(value, CODEC_PAYLOAD_KEYS, "preset");
|
|
878
|
+
if (value.v !== 1) {
|
|
879
|
+
throw new PresetValidationError(
|
|
880
|
+
typeof value.v === "number" ? `Unsupported preset codec version ${value.v}. Expected v: 1.` : "Preset payload missing v: 1.",
|
|
881
|
+
"preset.v"
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
if (typeof value.iconLibrary !== "string" || !isIconLibrary(value.iconLibrary)) {
|
|
885
|
+
throw new PresetValidationError(
|
|
886
|
+
"Invalid preset.iconLibrary. Expected tabler | lucide.",
|
|
887
|
+
"preset.iconLibrary"
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
if (typeof value.radius !== "string" || !isRadiusPreset(value.radius)) {
|
|
891
|
+
throw new PresetValidationError(
|
|
892
|
+
"Invalid preset.radius. Expected none | small | medium | large.",
|
|
893
|
+
"preset.radius"
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if (!("fonts" in value)) {
|
|
897
|
+
throw new PresetValidationError("Preset payload missing fonts.", "preset.fonts");
|
|
898
|
+
}
|
|
899
|
+
const payload = {
|
|
900
|
+
v: 1,
|
|
901
|
+
iconLibrary: value.iconLibrary,
|
|
902
|
+
radius: value.radius,
|
|
903
|
+
fonts: validateCodecFonts(value.fonts, "preset.fonts")
|
|
904
|
+
};
|
|
905
|
+
if ("colors" in value) {
|
|
906
|
+
payload.colors = validateCodecColors(value.colors, "preset.colors");
|
|
907
|
+
}
|
|
908
|
+
return payload;
|
|
909
|
+
}
|
|
910
|
+
function codecPayloadToStyleInput(payload) {
|
|
911
|
+
const input = {
|
|
912
|
+
iconLibrary: payload.iconLibrary,
|
|
913
|
+
radius: payload.radius,
|
|
914
|
+
fonts: payload.fonts
|
|
915
|
+
};
|
|
916
|
+
if (payload.colors) {
|
|
917
|
+
input.colors = payload.colors;
|
|
918
|
+
}
|
|
919
|
+
return input;
|
|
920
|
+
}
|
|
921
|
+
function encodePreset(style) {
|
|
922
|
+
const normalized = normalizeStyleConfig(style);
|
|
923
|
+
for (const role of FONT_ROLE_KEYS) {
|
|
924
|
+
const value = normalized.fonts[role];
|
|
925
|
+
if (value.startsWith(CUSTOM_FONT_PREFIX)) {
|
|
926
|
+
validateCustomFontStack(value, `style.fonts.${role}`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const colors = normalized.colors ? {
|
|
930
|
+
light: pickDefinedColors(normalized.colors.light),
|
|
931
|
+
dark: pickDefinedColors(normalized.colors.dark)
|
|
932
|
+
} : void 0;
|
|
933
|
+
const hasColors = colors && (colors.light && Object.keys(colors.light).length > 0 || colors.dark && Object.keys(colors.dark).length > 0);
|
|
934
|
+
const payload = {
|
|
935
|
+
v: 1,
|
|
936
|
+
iconLibrary: normalized.iconLibrary,
|
|
937
|
+
radius: normalized.radius,
|
|
938
|
+
fonts: { ...normalized.fonts },
|
|
939
|
+
...hasColors ? { colors } : {}
|
|
940
|
+
};
|
|
941
|
+
const json = stableStringifyCodecPayload(payload);
|
|
942
|
+
const bytes = new TextEncoder().encode(json);
|
|
943
|
+
if (bytes.byteLength > PRESET_CODEC_MAX_DECODED_JSON_BYTES) {
|
|
944
|
+
throw new PresetValidationError(
|
|
945
|
+
`Preset JSON exceeds ${PRESET_CODEC_MAX_DECODED_JSON_BYTES} bytes.`,
|
|
946
|
+
"preset"
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
const code = `${PRESET_CODEC_PREFIX}.${toBase64Url(bytes)}`;
|
|
950
|
+
if (code.length > PRESET_CODEC_MAX_CODE_LENGTH) {
|
|
951
|
+
throw new PresetValidationError(
|
|
952
|
+
`Preset code exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
|
|
953
|
+
"preset"
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
return code;
|
|
957
|
+
}
|
|
958
|
+
function pickDefinedColors(partial) {
|
|
959
|
+
if (!partial) return void 0;
|
|
960
|
+
const out = {};
|
|
961
|
+
for (const key of CONTROL_SOLID_COLOR_KEYS) {
|
|
962
|
+
if (partial[key] !== void 0) out[key] = partial[key];
|
|
963
|
+
}
|
|
964
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
965
|
+
}
|
|
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) {
|
|
1084
|
+
if (typeof code !== "string" || code.length === 0) {
|
|
1085
|
+
throw new PresetValidationError("Preset code is required.", "preset");
|
|
1086
|
+
}
|
|
1087
|
+
if (code.length > PRESET_CODEC_MAX_CODE_LENGTH) {
|
|
1088
|
+
throw new PresetValidationError(
|
|
1089
|
+
`Preset code exceeds ${PRESET_CODEC_MAX_CODE_LENGTH} characters.`,
|
|
1090
|
+
"preset"
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
const prefix = `${PRESET_CODEC_PREFIX}.`;
|
|
1094
|
+
if (!code.startsWith(prefix)) {
|
|
1095
|
+
throw new PresetValidationError(
|
|
1096
|
+
`Unsupported or missing preset prefix. Expected codes starting with "${PRESET_CODEC_PREFIX}".`,
|
|
1097
|
+
"preset"
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const encoded = code.slice(prefix.length);
|
|
1101
|
+
if (!encoded) {
|
|
1102
|
+
throw new PresetValidationError("Preset code payload is empty.", "preset");
|
|
1103
|
+
}
|
|
1104
|
+
if (encoded.includes("=") || /[^A-Za-z0-9_-]/.test(encoded)) {
|
|
1105
|
+
throw new PresetValidationError("Preset code payload is not valid unpadded base64url.", "preset");
|
|
1106
|
+
}
|
|
1107
|
+
const bytes = fromBase64Url(encoded);
|
|
1108
|
+
if (bytes.byteLength === 0) {
|
|
1109
|
+
throw new PresetValidationError("Preset code decodes to empty payload.", "preset");
|
|
1110
|
+
}
|
|
1111
|
+
if (bytes.byteLength > PRESET_CODEC_MAX_DECODED_JSON_BYTES) {
|
|
1112
|
+
throw new PresetValidationError(
|
|
1113
|
+
`Decoded preset JSON exceeds ${PRESET_CODEC_MAX_DECODED_JSON_BYTES} bytes.`,
|
|
1114
|
+
"preset"
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
let jsonText;
|
|
1118
|
+
try {
|
|
1119
|
+
jsonText = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1120
|
+
} catch {
|
|
1121
|
+
throw new PresetValidationError("Preset payload is not valid UTF-8.", "preset");
|
|
1122
|
+
}
|
|
1123
|
+
if (utf8ByteLength(jsonText) > PRESET_CODEC_MAX_DECODED_JSON_BYTES) {
|
|
1124
|
+
throw new PresetValidationError(
|
|
1125
|
+
`Decoded preset JSON exceeds ${PRESET_CODEC_MAX_DECODED_JSON_BYTES} bytes.`,
|
|
1126
|
+
"preset"
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
let parsed;
|
|
1130
|
+
try {
|
|
1131
|
+
parsed = JSON.parse(jsonText);
|
|
1132
|
+
} catch {
|
|
1133
|
+
throw new PresetValidationError("Preset payload is not valid JSON.", "preset");
|
|
1134
|
+
}
|
|
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");
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// ../wensity-preset/src/icon-map.ts
|
|
1158
|
+
var ICON_TRANSFORM_EXCLUSIONS = [
|
|
1159
|
+
"IconFileTypeCsv",
|
|
1160
|
+
"IconFileTypeDoc",
|
|
1161
|
+
"IconFileTypePdf",
|
|
1162
|
+
"IconFileTypePpt",
|
|
1163
|
+
"IconFileTypeXls",
|
|
1164
|
+
"IconPointFilled",
|
|
1165
|
+
"IconLoader2"
|
|
1166
|
+
];
|
|
1167
|
+
var TABLER_TO_LUCIDE = {
|
|
1168
|
+
IconAlertTriangle: "AlertTriangle",
|
|
1169
|
+
IconArrowDownRight: "ArrowDownRight",
|
|
1170
|
+
IconArrowLeft: "ArrowLeft",
|
|
1171
|
+
IconArrowUpRight: "ArrowUpRight",
|
|
1172
|
+
IconCalendar: "Calendar",
|
|
1173
|
+
IconCamera: "Camera",
|
|
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"
|
|
1203
|
+
};
|
|
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];
|
|
1223
|
+
}
|
|
1224
|
+
var LUCIDE_TO_TABLER = Object.fromEntries(
|
|
1225
|
+
Object.entries(TABLER_TO_LUCIDE).map(([tabler, lucide]) => [lucide, tabler])
|
|
1226
|
+
);
|
|
1227
|
+
|
|
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"
|
|
1244
|
+
}
|
|
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"
|
|
1260
|
+
}
|
|
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"
|
|
1276
|
+
}
|
|
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"
|
|
1292
|
+
}
|
|
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"
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
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)`;
|
|
1527
|
+
}
|
|
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;
|
|
1533
|
+
}
|
|
1534
|
+
return out;
|
|
1535
|
+
}
|
|
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
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function baseFromInherit(mode) {
|
|
1547
|
+
return { ...INHERIT_BASE_EXPRESSIONS[mode] };
|
|
1548
|
+
}
|
|
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
|
+
};
|
|
112
1613
|
}
|
|
113
|
-
function
|
|
114
|
-
if (
|
|
115
|
-
|
|
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
|
+
};
|
|
116
1640
|
}
|
|
117
1641
|
|
|
118
|
-
// src/config.ts
|
|
119
|
-
import { existsSync } from "fs";
|
|
120
|
-
import { access, mkdir, readFile, writeFile } from "fs/promises";
|
|
121
|
-
import { dirname as dirname2, join as join2, relative as relative2 } from "path";
|
|
122
|
-
|
|
123
1642
|
// src/paths.ts
|
|
124
1643
|
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
125
1644
|
function normalizeUrlOrigin(input) {
|
|
@@ -188,6 +1707,35 @@ function extensionlessPath(path) {
|
|
|
188
1707
|
return path.replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
189
1708
|
}
|
|
190
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
|
+
|
|
191
1739
|
// src/config.ts
|
|
192
1740
|
async function loadConfig(cwd, overrides = {}) {
|
|
193
1741
|
const foundPath = await findUp(cwd, CONFIG_FILE_NAME);
|
|
@@ -205,7 +1753,20 @@ async function loadConfig(cwd, overrides = {}) {
|
|
|
205
1753
|
try {
|
|
206
1754
|
const parsed = JSON.parse(await readFile(foundPath, "utf8"));
|
|
207
1755
|
return {
|
|
208
|
-
config: normalizeConfig(
|
|
1756
|
+
config: normalizeConfig(
|
|
1757
|
+
{
|
|
1758
|
+
...defaults,
|
|
1759
|
+
...parsed,
|
|
1760
|
+
aliases: {
|
|
1761
|
+
...defaults.aliases,
|
|
1762
|
+
...parsed.aliases
|
|
1763
|
+
},
|
|
1764
|
+
tailwind: parsed.tailwind ? { ...defaults.tailwind, ...parsed.tailwind } : defaults.tailwind,
|
|
1765
|
+
style: parsed.style,
|
|
1766
|
+
version: parsed.version ?? defaults.version
|
|
1767
|
+
},
|
|
1768
|
+
overrides
|
|
1769
|
+
),
|
|
209
1770
|
configPath,
|
|
210
1771
|
projectRoot,
|
|
211
1772
|
exists: true
|
|
@@ -214,6 +1775,9 @@ async function loadConfig(cwd, overrides = {}) {
|
|
|
214
1775
|
if (error instanceof SyntaxError) {
|
|
215
1776
|
throw new CliError(`${CONFIG_FILE_NAME} contains invalid JSON.`);
|
|
216
1777
|
}
|
|
1778
|
+
if (error instanceof PresetValidationError) {
|
|
1779
|
+
throw new CliError(`${CONFIG_FILE_NAME}: ${error.message}`);
|
|
1780
|
+
}
|
|
217
1781
|
throw error;
|
|
218
1782
|
}
|
|
219
1783
|
}
|
|
@@ -221,6 +1785,7 @@ function createDefaultConfig(projectRoot, apiUrl = DEFAULT_API_ORIGIN) {
|
|
|
221
1785
|
const apiOrigin = normalizeUrlOrigin(apiUrl);
|
|
222
1786
|
return {
|
|
223
1787
|
$schema: joinUrl(apiOrigin, "/schema/wensity.json"),
|
|
1788
|
+
version: CONFIG_SCHEMA_VERSION,
|
|
224
1789
|
registry: joinUrl(apiOrigin, DEFAULT_REGISTRY_PATH),
|
|
225
1790
|
components: DEFAULT_COMPONENTS_DIR,
|
|
226
1791
|
utils: DEFAULT_UTILS_PATH,
|
|
@@ -231,7 +1796,8 @@ function createDefaultConfig(projectRoot, apiUrl = DEFAULT_API_ORIGIN) {
|
|
|
231
1796
|
aliases: {
|
|
232
1797
|
components: DEFAULT_COMPONENTS_ALIAS,
|
|
233
1798
|
utils: DEFAULT_UTILS_ALIAS
|
|
234
|
-
}
|
|
1799
|
+
},
|
|
1800
|
+
style: createDefaultStyleConfig()
|
|
235
1801
|
};
|
|
236
1802
|
}
|
|
237
1803
|
async function writeConfigFile(path, config, force) {
|
|
@@ -242,6 +1808,26 @@ async function writeConfigFile(path, config, force) {
|
|
|
242
1808
|
await writeFile(path, `${JSON.stringify(config, null, 2)}
|
|
243
1809
|
`, "utf8");
|
|
244
1810
|
}
|
|
1811
|
+
async function mergeStyleIntoConfigFile(configPath, style) {
|
|
1812
|
+
const raw = JSON.parse(await readFile(configPath, "utf8"));
|
|
1813
|
+
const merged = {
|
|
1814
|
+
...raw,
|
|
1815
|
+
version: raw.version ?? CONFIG_SCHEMA_VERSION,
|
|
1816
|
+
registry: raw.registry ?? "",
|
|
1817
|
+
components: raw.components ?? DEFAULT_COMPONENTS_DIR,
|
|
1818
|
+
utils: raw.utils ?? DEFAULT_UTILS_PATH,
|
|
1819
|
+
aliases: {
|
|
1820
|
+
components: raw.aliases?.components ?? DEFAULT_COMPONENTS_ALIAS,
|
|
1821
|
+
utils: raw.aliases?.utils ?? DEFAULT_UTILS_ALIAS
|
|
1822
|
+
},
|
|
1823
|
+
style
|
|
1824
|
+
};
|
|
1825
|
+
if (raw.$schema) merged.$schema = raw.$schema;
|
|
1826
|
+
if (raw.tailwind) merged.tailwind = raw.tailwind;
|
|
1827
|
+
await writeFile(configPath, `${JSON.stringify(merged, null, 2)}
|
|
1828
|
+
`, "utf8");
|
|
1829
|
+
return normalizeConfig(merged, {});
|
|
1830
|
+
}
|
|
245
1831
|
function resolveConfiguredFilePath(projectRoot, relativePath, extension = "ts") {
|
|
246
1832
|
try {
|
|
247
1833
|
return resolveInside(projectRoot, withExtension(relativePath, extension));
|
|
@@ -271,23 +1857,40 @@ async function findUp(start, fileName) {
|
|
|
271
1857
|
}
|
|
272
1858
|
function normalizeConfig(config, overrides) {
|
|
273
1859
|
const apiOrigin = overrides.apiUrl ? normalizeUrlOrigin(overrides.apiUrl) : void 0;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
1860
|
+
try {
|
|
1861
|
+
return {
|
|
1862
|
+
...config,
|
|
1863
|
+
version: config.version ?? CONFIG_SCHEMA_VERSION,
|
|
1864
|
+
registry: overrides.registry ?? (apiOrigin ? joinUrl(apiOrigin, DEFAULT_REGISTRY_PATH) : config.registry),
|
|
1865
|
+
components: config.components || DEFAULT_COMPONENTS_DIR,
|
|
1866
|
+
utils: config.utils || DEFAULT_UTILS_PATH,
|
|
1867
|
+
aliases: {
|
|
1868
|
+
components: config.aliases?.components || DEFAULT_COMPONENTS_ALIAS,
|
|
1869
|
+
utils: config.aliases?.utils || DEFAULT_UTILS_ALIAS
|
|
1870
|
+
},
|
|
1871
|
+
// In-memory only: missing style fields resolve to defaults without rewriting disk.
|
|
1872
|
+
// Accepts either legacy or canonical style shapes.
|
|
1873
|
+
style: normalizeProjectStyle(config.style)
|
|
1874
|
+
};
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
if (error instanceof PresetValidationError) {
|
|
1877
|
+
throw new CliError(`${CONFIG_FILE_NAME}: ${error.message}`);
|
|
282
1878
|
}
|
|
283
|
-
|
|
1879
|
+
throw error;
|
|
1880
|
+
}
|
|
284
1881
|
}
|
|
285
1882
|
function detectTailwindConfig(projectRoot) {
|
|
286
1883
|
const candidates = ["tailwind.config.ts", "tailwind.config.js", "tailwind.config.mjs"];
|
|
287
1884
|
return candidates.find((candidate) => existsSync(join2(projectRoot, candidate))) ?? "tailwind.config.ts";
|
|
288
1885
|
}
|
|
289
1886
|
function detectGlobalCss(projectRoot) {
|
|
290
|
-
const candidates = [
|
|
1887
|
+
const candidates = [
|
|
1888
|
+
"src/app/globals.css",
|
|
1889
|
+
"src/styles/globals.css",
|
|
1890
|
+
"app/globals.css",
|
|
1891
|
+
"src/index.css",
|
|
1892
|
+
"styles/globals.css"
|
|
1893
|
+
];
|
|
291
1894
|
return candidates.find((candidate) => existsSync(join2(projectRoot, candidate))) ?? "src/app/globals.css";
|
|
292
1895
|
}
|
|
293
1896
|
function withExtension(path, extension) {
|
|
@@ -396,6 +1999,122 @@ function requiredHome(env) {
|
|
|
396
1999
|
throw new CliError("HOME is not set, so the CLI cannot locate ~/.config/wensity.");
|
|
397
2000
|
}
|
|
398
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
|
+
|
|
399
2118
|
// src/output.ts
|
|
400
2119
|
import boxen from "boxen";
|
|
401
2120
|
import Table from "cli-table3";
|
|
@@ -614,6 +2333,262 @@ Upgrade here: ${pricingBlue("https://ui.wensity.com/pricing")}`;
|
|
|
614
2333
|
return message;
|
|
615
2334
|
}
|
|
616
2335
|
|
|
2336
|
+
// src/icon-transform.ts
|
|
2337
|
+
var UI_PRIMITIVES_CATEGORY = "UI Primitives";
|
|
2338
|
+
var TABLER_ICONS_PACKAGE = "@tabler/icons-react";
|
|
2339
|
+
var LUCIDE_ICONS_PACKAGE = "lucide-react";
|
|
2340
|
+
var EMPTY_SWAP = { remove: [], add: [] };
|
|
2341
|
+
var NATIVE_SVG_TAGS = /* @__PURE__ */ new Set([
|
|
2342
|
+
"svg",
|
|
2343
|
+
"path",
|
|
2344
|
+
"circle",
|
|
2345
|
+
"rect",
|
|
2346
|
+
"line",
|
|
2347
|
+
"polyline",
|
|
2348
|
+
"polygon",
|
|
2349
|
+
"g",
|
|
2350
|
+
"defs",
|
|
2351
|
+
"use",
|
|
2352
|
+
"symbol",
|
|
2353
|
+
"clipPath",
|
|
2354
|
+
"mask",
|
|
2355
|
+
"linearGradient",
|
|
2356
|
+
"radialGradient",
|
|
2357
|
+
"stop",
|
|
2358
|
+
"text",
|
|
2359
|
+
"tspan",
|
|
2360
|
+
"foreignObject"
|
|
2361
|
+
]);
|
|
2362
|
+
function createTablerAdapter() {
|
|
2363
|
+
return {
|
|
2364
|
+
id: "tabler",
|
|
2365
|
+
packageName: TABLER_ICONS_PACKAGE,
|
|
2366
|
+
oppositePackageName: LUCIDE_ICONS_PACKAGE,
|
|
2367
|
+
resolveExportFromTabler: (tablerName) => tablerName
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
function createLucideAdapter() {
|
|
2371
|
+
return {
|
|
2372
|
+
id: "lucide",
|
|
2373
|
+
packageName: LUCIDE_ICONS_PACKAGE,
|
|
2374
|
+
oppositePackageName: TABLER_ICONS_PACKAGE,
|
|
2375
|
+
resolveExportFromTabler: (tablerName, context) => {
|
|
2376
|
+
try {
|
|
2377
|
+
return resolveLucideExport(tablerName);
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2380
|
+
throw new CliError(`${context}: ${message}`);
|
|
2381
|
+
}
|
|
2382
|
+
},
|
|
2383
|
+
strokePropTo: "strokeWidth"
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
var ICON_LIBRARY_ADAPTERS = {
|
|
2387
|
+
tabler: createTablerAdapter,
|
|
2388
|
+
lucide: createLucideAdapter
|
|
2389
|
+
};
|
|
2390
|
+
function getIconLibraryAdapter(library) {
|
|
2391
|
+
return ICON_LIBRARY_ADAPTERS[library]();
|
|
2392
|
+
}
|
|
2393
|
+
function isPrimitivePayloadCategory(category) {
|
|
2394
|
+
return category === UI_PRIMITIVES_CATEGORY;
|
|
2395
|
+
}
|
|
2396
|
+
function transformIconLibrary(content, options) {
|
|
2397
|
+
if (!options.isPrimitiveCategory) {
|
|
2398
|
+
return { content, dependencySwap: EMPTY_SWAP };
|
|
2399
|
+
}
|
|
2400
|
+
const adapter = getIconLibraryAdapter(options.iconLibrary);
|
|
2401
|
+
const context = options.slug ? `primitive "${options.slug}"` : "primitive source";
|
|
2402
|
+
const hasTabler = hasPackageImport(content, TABLER_ICONS_PACKAGE);
|
|
2403
|
+
const hasLucide = hasPackageImport(content, LUCIDE_ICONS_PACKAGE);
|
|
2404
|
+
if (adapter.id === "lucide") {
|
|
2405
|
+
if (hasTabler) {
|
|
2406
|
+
const transformed = rewriteTablerImportsToTarget(content, adapter, context);
|
|
2407
|
+
return {
|
|
2408
|
+
content: rewriteStrokeProps(transformed.content, transformed.localBindings),
|
|
2409
|
+
dependencySwap: {
|
|
2410
|
+
remove: [TABLER_ICONS_PACKAGE],
|
|
2411
|
+
add: [LUCIDE_ICONS_PACKAGE]
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
if (hasLucide) {
|
|
2416
|
+
return {
|
|
2417
|
+
content,
|
|
2418
|
+
dependencySwap: {
|
|
2419
|
+
remove: [TABLER_ICONS_PACKAGE],
|
|
2420
|
+
add: [LUCIDE_ICONS_PACKAGE]
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
return { content, dependencySwap: EMPTY_SWAP };
|
|
2425
|
+
}
|
|
2426
|
+
if (hasLucide && !hasTabler) {
|
|
2427
|
+
return {
|
|
2428
|
+
content,
|
|
2429
|
+
dependencySwap: {
|
|
2430
|
+
remove: [LUCIDE_ICONS_PACKAGE],
|
|
2431
|
+
add: [TABLER_ICONS_PACKAGE]
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
if (hasTabler) {
|
|
2436
|
+
assertNoUnmappedTablerImports(content, context);
|
|
2437
|
+
return {
|
|
2438
|
+
content,
|
|
2439
|
+
dependencySwap: {
|
|
2440
|
+
remove: [LUCIDE_ICONS_PACKAGE],
|
|
2441
|
+
add: [TABLER_ICONS_PACKAGE]
|
|
2442
|
+
}
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
return { content, dependencySwap: EMPTY_SWAP };
|
|
2446
|
+
}
|
|
2447
|
+
function aggregateIconDependencySwaps(swaps) {
|
|
2448
|
+
const remove = /* @__PURE__ */ new Set();
|
|
2449
|
+
const add = /* @__PURE__ */ new Set();
|
|
2450
|
+
for (const swap of swaps) {
|
|
2451
|
+
for (const pkg of swap.remove) remove.add(pkg);
|
|
2452
|
+
for (const pkg of swap.add) add.add(pkg);
|
|
2453
|
+
}
|
|
2454
|
+
return {
|
|
2455
|
+
remove: [...remove],
|
|
2456
|
+
add: [...add]
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
function applyIconDependencySwap(declared, swap) {
|
|
2460
|
+
const remove = new Set(swap.remove);
|
|
2461
|
+
const next = declared.filter((dep) => !remove.has(dep));
|
|
2462
|
+
for (const pkg of swap.add) {
|
|
2463
|
+
if (!next.includes(pkg)) next.push(pkg);
|
|
2464
|
+
}
|
|
2465
|
+
return next;
|
|
2466
|
+
}
|
|
2467
|
+
function warnOppositeIconPackageMessage(iconLibrary, installedPackages) {
|
|
2468
|
+
const selected = iconLibrary === "lucide" ? LUCIDE_ICONS_PACKAGE : TABLER_ICONS_PACKAGE;
|
|
2469
|
+
const opposite = iconLibrary === "lucide" ? TABLER_ICONS_PACKAGE : LUCIDE_ICONS_PACKAGE;
|
|
2470
|
+
if (installedPackages.has(opposite) && !installedPackages.has(selected)) {
|
|
2471
|
+
return `Project package.json still lists ${opposite} while style.iconLibrary is "${iconLibrary}". Wensity will install ${selected} for new primitive installs but will not uninstall ${opposite} (it may be used outside Wensity).`;
|
|
2472
|
+
}
|
|
2473
|
+
if (installedPackages.has(opposite) && installedPackages.has(selected)) {
|
|
2474
|
+
return `Project package.json lists both ${selected} and ${opposite}. Wensity primitives use only ${selected} (style.iconLibrary="${iconLibrary}"); consider removing ${opposite} if unused.`;
|
|
2475
|
+
}
|
|
2476
|
+
return void 0;
|
|
2477
|
+
}
|
|
2478
|
+
function hasPackageImport(source, packageName) {
|
|
2479
|
+
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2480
|
+
return new RegExp(`from\\s+["']${escaped}["']`).test(source);
|
|
2481
|
+
}
|
|
2482
|
+
function rewriteTablerImportsToTarget(content, adapter, context) {
|
|
2483
|
+
const localBindings = /* @__PURE__ */ new Set();
|
|
2484
|
+
const importPattern = /^(?<indent>[ \t]*)(?<typeOnly>import\s+type\s+|import\s+)\{(?<clause>[^}]*)\}\s*from\s+(?<quote>["'])@tabler\/icons-react\k<quote>;?[ \t]*$/gm;
|
|
2485
|
+
const next = content.replace(
|
|
2486
|
+
importPattern,
|
|
2487
|
+
(full, indent, typePrefix, clause, quote) => {
|
|
2488
|
+
const specs = parseNamedImportClause(clause);
|
|
2489
|
+
if (specs.length === 0) return full;
|
|
2490
|
+
const rewritten = [];
|
|
2491
|
+
for (const spec of specs) {
|
|
2492
|
+
if (spec.isTypeOnly || typePrefix.includes("type")) {
|
|
2493
|
+
if (spec.imported === "Icon" || isIconTransformExclusion(spec.imported)) {
|
|
2494
|
+
throw new CliError(
|
|
2495
|
+
`${context}: unexpected type-only icon import "${spec.imported}" from ${TABLER_ICONS_PACKAGE}.`
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
if (isIconTransformExclusion(spec.imported)) {
|
|
2500
|
+
throw new CliError(
|
|
2501
|
+
`${context}: "${spec.imported}" is excluded from the icon map (Phase 0 internal geometry). It must not appear as a library import.`
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
const targetExport = adapter.resolveExportFromTabler(spec.imported, context);
|
|
2505
|
+
localBindings.add(spec.local);
|
|
2506
|
+
if (adapter.id === "lucide") {
|
|
2507
|
+
if (targetExport === spec.local) {
|
|
2508
|
+
rewritten.push(targetExport);
|
|
2509
|
+
} else {
|
|
2510
|
+
rewritten.push(`${targetExport} as ${spec.local}`);
|
|
2511
|
+
}
|
|
2512
|
+
} else {
|
|
2513
|
+
rewritten.push(
|
|
2514
|
+
spec.imported === spec.local ? spec.imported : `${spec.imported} as ${spec.local}`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
const prefix = typePrefix.startsWith("import type") ? "import type " : "import ";
|
|
2519
|
+
return `${indent}${prefix}{ ${rewritten.join(", ")} } from ${quote}${adapter.packageName}${quote};`;
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
if (hasPackageImport(next, TABLER_ICONS_PACKAGE)) {
|
|
2523
|
+
throw new CliError(
|
|
2524
|
+
`${context}: failed to rewrite all ${TABLER_ICONS_PACKAGE} imports for ${adapter.id}.`
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
if (/(?:import|export)\s+\*\s+as\s+\w+\s+from\s+["']@tabler\/icons-react["']/.test(content)) {
|
|
2528
|
+
throw new CliError(
|
|
2529
|
+
`${context}: namespace imports from ${TABLER_ICONS_PACKAGE} are not supported. Use named imports.`
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
return { content: next, localBindings };
|
|
2533
|
+
}
|
|
2534
|
+
function assertNoUnmappedTablerImports(content, context) {
|
|
2535
|
+
const importPattern = /\{([^}]*)\}\s*from\s+["']@tabler\/icons-react["']/g;
|
|
2536
|
+
for (const match of content.matchAll(importPattern)) {
|
|
2537
|
+
const specs = parseNamedImportClause(match[1] ?? "");
|
|
2538
|
+
for (const spec of specs) {
|
|
2539
|
+
if (isIconTransformExclusion(spec.imported)) {
|
|
2540
|
+
throw new CliError(
|
|
2541
|
+
`${context}: "${spec.imported}" must not appear as a library import (Phase 0 exclusion).`
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
if (spec.imported.startsWith("Icon")) {
|
|
2545
|
+
resolveLucideExport(spec.imported);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function parseNamedImportClause(clause) {
|
|
2551
|
+
const specs = [];
|
|
2552
|
+
for (const raw of clause.split(",")) {
|
|
2553
|
+
const part = raw.trim();
|
|
2554
|
+
if (!part) continue;
|
|
2555
|
+
const typeOnly = /^type\s+/.test(part);
|
|
2556
|
+
const body = typeOnly ? part.replace(/^type\s+/, "").trim() : part;
|
|
2557
|
+
const asMatch = body.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
2558
|
+
if (asMatch) {
|
|
2559
|
+
specs.push({
|
|
2560
|
+
imported: asMatch[1],
|
|
2561
|
+
local: asMatch[2],
|
|
2562
|
+
isTypeOnly: typeOnly
|
|
2563
|
+
});
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
if (/^\w+$/.test(body)) {
|
|
2567
|
+
specs.push({ imported: body, local: body, isTypeOnly: typeOnly });
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
return specs;
|
|
2571
|
+
}
|
|
2572
|
+
function rewriteStrokeProps(content, localBindings) {
|
|
2573
|
+
if (localBindings.size === 0) return content;
|
|
2574
|
+
const tags = new Set(localBindings);
|
|
2575
|
+
if (localBindings.size > 0 && /\bFileIcon\b/.test(content) && /\bpackIconMap\b/.test(content)) {
|
|
2576
|
+
tags.add("FileIcon");
|
|
2577
|
+
}
|
|
2578
|
+
const tagAlternation = [...tags].map((tag) => tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
2579
|
+
if (!tagAlternation) return content;
|
|
2580
|
+
const openTagPattern = new RegExp(`<(${tagAlternation})(\\s[\\s\\S]*?)(/?)>`, "g");
|
|
2581
|
+
return content.replace(openTagPattern, (full, tagName, attrs, selfClose) => {
|
|
2582
|
+
if (NATIVE_SVG_TAGS.has(tagName)) return full;
|
|
2583
|
+
if (!attrs) return full;
|
|
2584
|
+
const nextAttrs = attrs.replace(
|
|
2585
|
+
/(^|[\s])stroke(\s*=\s*(?:\{[^}]*\}|"[^"]*"|'[^']*'))/g,
|
|
2586
|
+
"$1strokeWidth$2"
|
|
2587
|
+
);
|
|
2588
|
+
return `<${tagName}${nextAttrs}${selfClose}>`;
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
|
|
617
2592
|
// src/package-manager.ts
|
|
618
2593
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
619
2594
|
import { spawnSync } from "child_process";
|
|
@@ -626,16 +2601,22 @@ function detectPackageManager(projectRoot) {
|
|
|
626
2601
|
return "npm";
|
|
627
2602
|
}
|
|
628
2603
|
function missingDependencies(projectRoot, dependencies) {
|
|
2604
|
+
const installed = readInstalledPackageNames(projectRoot);
|
|
2605
|
+
if (!installed) return dependencies;
|
|
2606
|
+
return dependencies.filter((dependency) => !installed.has(dependency));
|
|
2607
|
+
}
|
|
2608
|
+
function readInstalledPackageNames(projectRoot) {
|
|
629
2609
|
const packageJsonPath = join4(projectRoot, "package.json");
|
|
630
|
-
if (!existsSync2(packageJsonPath)) return
|
|
2610
|
+
if (!existsSync2(packageJsonPath)) return void 0;
|
|
631
2611
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
2612
|
+
return new Set(
|
|
2613
|
+
Object.keys({
|
|
2614
|
+
...packageJson.dependencies,
|
|
2615
|
+
...packageJson.devDependencies,
|
|
2616
|
+
...packageJson.peerDependencies,
|
|
2617
|
+
...packageJson.optionalDependencies
|
|
2618
|
+
})
|
|
2619
|
+
);
|
|
639
2620
|
}
|
|
640
2621
|
function runInstall(projectRoot, dependencies) {
|
|
641
2622
|
if (dependencies.length === 0) return;
|
|
@@ -671,9 +2652,218 @@ function installArgs(packageManager, dependencies) {
|
|
|
671
2652
|
}
|
|
672
2653
|
}
|
|
673
2654
|
|
|
674
|
-
// src/
|
|
2655
|
+
// src/preset.ts
|
|
2656
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2657
|
+
async function resolvePresetStyle(input) {
|
|
2658
|
+
if (input.presetCode && input.presetFile) {
|
|
2659
|
+
throw new CliError("Use either --preset or --preset-file, not both.");
|
|
2660
|
+
}
|
|
2661
|
+
let code = input.presetCode;
|
|
2662
|
+
if (input.presetFile) {
|
|
2663
|
+
try {
|
|
2664
|
+
const raw = await readFile3(input.presetFile, "utf8");
|
|
2665
|
+
const trimmed = raw.trim();
|
|
2666
|
+
if (trimmed.startsWith("{")) {
|
|
2667
|
+
const parsed = JSON.parse(trimmed);
|
|
2668
|
+
code = encodePresetFileJson(parsed);
|
|
2669
|
+
} else {
|
|
2670
|
+
code = trimmed;
|
|
2671
|
+
}
|
|
2672
|
+
} catch (error) {
|
|
2673
|
+
if (error instanceof PresetValidationError) {
|
|
2674
|
+
throw toCliPresetError(error);
|
|
2675
|
+
}
|
|
2676
|
+
if (error instanceof SyntaxError) {
|
|
2677
|
+
throw new CliError(`Preset file contains invalid JSON: ${error.message}`);
|
|
2678
|
+
}
|
|
2679
|
+
if (error instanceof CliError) throw error;
|
|
2680
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2681
|
+
throw new CliError(`Failed to read preset file: ${message}`);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
if (!code) {
|
|
2685
|
+
throw new CliError("Missing preset. Pass --preset <code> or --preset-file <path>.");
|
|
2686
|
+
}
|
|
2687
|
+
try {
|
|
2688
|
+
const trimmed = code.trim();
|
|
2689
|
+
const payload = decodeAnyPreset(trimmed);
|
|
2690
|
+
const style = styleFromDecodedPayload(payload, trimmed);
|
|
2691
|
+
return { style, presetCode: trimmed };
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
if (error instanceof PresetValidationError) {
|
|
2694
|
+
throw toCliPresetError(error);
|
|
2695
|
+
}
|
|
2696
|
+
throw error;
|
|
2697
|
+
}
|
|
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
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// src/preset-init-writes.ts
|
|
675
2754
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
676
|
-
import { dirname as dirname4
|
|
2755
|
+
import { dirname as dirname4 } from "path";
|
|
2756
|
+
async function commitPresetInitWrites(input) {
|
|
2757
|
+
let cssWritten = false;
|
|
2758
|
+
try {
|
|
2759
|
+
if (input.cssChanged) {
|
|
2760
|
+
await mkdir3(dirname4(input.cssAbsolute), { recursive: true });
|
|
2761
|
+
await writeFile3(input.cssAbsolute, input.nextCss, "utf8");
|
|
2762
|
+
cssWritten = true;
|
|
2763
|
+
}
|
|
2764
|
+
if (input.writeUtility) {
|
|
2765
|
+
await mkdir3(dirname4(input.utilityAbsolutePath), { recursive: true });
|
|
2766
|
+
await writeFile3(input.utilityAbsolutePath, input.utilityContent, "utf8");
|
|
2767
|
+
}
|
|
2768
|
+
await input.commitConfig();
|
|
2769
|
+
} catch (error) {
|
|
2770
|
+
if (cssWritten) {
|
|
2771
|
+
try {
|
|
2772
|
+
await writeFile3(input.cssAbsolute, input.existingCss, "utf8");
|
|
2773
|
+
} catch {
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
throw error;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/style-css.ts
|
|
2781
|
+
var ROOT_SELECTOR = ":root";
|
|
2782
|
+
var DARK_SELECTOR = ".dark";
|
|
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) {
|
|
2788
|
+
let next = block;
|
|
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);
|
|
2794
|
+
}
|
|
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]);
|
|
2799
|
+
}
|
|
2800
|
+
return next;
|
|
2801
|
+
}
|
|
2802
|
+
function applyStyleToPrimitiveCss(block, styleInput) {
|
|
2803
|
+
const style = normalizeProjectStyle(styleInput);
|
|
2804
|
+
return applyResolvedTokensToPrimitiveCss(block, resolveTokensForStyle(style));
|
|
2805
|
+
}
|
|
2806
|
+
function replaceDeclInSelector(css, selector, property, value) {
|
|
2807
|
+
const block = findTopLevelRule(css, selector);
|
|
2808
|
+
if (!block) return css;
|
|
2809
|
+
const declPattern = new RegExp(
|
|
2810
|
+
`(${escapeRegExp(property)}\\s*:\\s*)([^;]+)(;)`
|
|
2811
|
+
);
|
|
2812
|
+
if (!declPattern.test(block.body)) {
|
|
2813
|
+
return css;
|
|
2814
|
+
}
|
|
2815
|
+
const updatedBody = block.body.replace(declPattern, `$1${value}$3`);
|
|
2816
|
+
if (updatedBody === block.body) return css;
|
|
2817
|
+
return `${css.slice(0, block.bodyStart)}${updatedBody}${css.slice(block.bodyEnd)}`;
|
|
2818
|
+
}
|
|
2819
|
+
function findTopLevelRule(css, selector) {
|
|
2820
|
+
const needle = `${selector} {`;
|
|
2821
|
+
let searchFrom = 0;
|
|
2822
|
+
while (searchFrom < css.length) {
|
|
2823
|
+
const start = css.indexOf(needle, searchFrom);
|
|
2824
|
+
if (start === -1) return void 0;
|
|
2825
|
+
const bodyStart = start + needle.length;
|
|
2826
|
+
const bodyEnd = findMatchingBrace(css, bodyStart - 1);
|
|
2827
|
+
if (bodyEnd === -1) return void 0;
|
|
2828
|
+
if (isLikelyTopLevel(css, start)) {
|
|
2829
|
+
return {
|
|
2830
|
+
bodyStart,
|
|
2831
|
+
bodyEnd,
|
|
2832
|
+
body: css.slice(bodyStart, bodyEnd)
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
searchFrom = bodyEnd + 1;
|
|
2836
|
+
}
|
|
2837
|
+
return void 0;
|
|
2838
|
+
}
|
|
2839
|
+
function isLikelyTopLevel(css, index) {
|
|
2840
|
+
let depth = 0;
|
|
2841
|
+
for (let i = 0; i < index; i += 1) {
|
|
2842
|
+
const ch = css[i];
|
|
2843
|
+
if (ch === "{") depth += 1;
|
|
2844
|
+
else if (ch === "}") depth -= 1;
|
|
2845
|
+
}
|
|
2846
|
+
return depth === 0;
|
|
2847
|
+
}
|
|
2848
|
+
function findMatchingBrace(css, openIndex) {
|
|
2849
|
+
let depth = 0;
|
|
2850
|
+
for (let i = openIndex; i < css.length; i += 1) {
|
|
2851
|
+
const ch = css[i];
|
|
2852
|
+
if (ch === "{") depth += 1;
|
|
2853
|
+
else if (ch === "}") {
|
|
2854
|
+
depth -= 1;
|
|
2855
|
+
if (depth === 0) return i;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
return -1;
|
|
2859
|
+
}
|
|
2860
|
+
function escapeRegExp(value) {
|
|
2861
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
// src/component-install.ts
|
|
2865
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2866
|
+
import { dirname as dirname5, join as join5, relative as relative3 } from "path";
|
|
677
2867
|
|
|
678
2868
|
// src/templates.ts
|
|
679
2869
|
function cnUtilityTemplate() {
|
|
@@ -830,27 +3020,46 @@ export function WensitySecondaryButton({
|
|
|
830
3020
|
|
|
831
3021
|
// src/component-install.ts
|
|
832
3022
|
var ALWAYS_PRESENT_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "next"]);
|
|
833
|
-
|
|
3023
|
+
var OVERLAY_HELPER_PATH = "components/wensity/overlay.tsx";
|
|
3024
|
+
var PRIMITIVE_CSS_START_MARKER = "/* \u2500\u2500 Wensity Primitive Tokens";
|
|
3025
|
+
var PRIMITIVE_CSS_END_MARKER = "/* \u2500\u2500 End Wensity Primitive Tokens \u2500\u2500 */";
|
|
3026
|
+
async function prepareInstall(projectRoot, config, payload, options = {}) {
|
|
834
3027
|
const fallbackFileName = componentFileName(payload.slug);
|
|
835
3028
|
const files = [];
|
|
836
3029
|
const componentContents = [];
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
3030
|
+
const iconSwaps = [];
|
|
3031
|
+
const style = normalizeProjectStyle(config.style);
|
|
3032
|
+
const isPrimitiveCategory = isPrimitivePayloadCategory(payload.category);
|
|
3033
|
+
const payloadHelperNeeds = normalizePayloadHelperNeeds(
|
|
3034
|
+
"helperNeeds" in payload ? payload.helperNeeds : void 0
|
|
3035
|
+
);
|
|
3036
|
+
const inferredHelperNeeds = { utils: false, primitives: false, overlay: false };
|
|
3037
|
+
const hasGraphHelperFiles = payload.files.some((file) => isGraphHelperFile(file));
|
|
3038
|
+
for (const [index, payloadFile] of payload.files.entries()) {
|
|
3039
|
+
const payloadFileKind = getPayloadFileKind(payloadFile);
|
|
3040
|
+
const relativeOutputPath = getPayloadOutputPath(payloadFile, fallbackFileName, config);
|
|
3041
|
+
const absoluteOutputPath = resolveInside(projectRoot, relativeOutputPath);
|
|
3042
|
+
if (!payloadHelperNeeds) mergeHelperNeeds(inferredHelperNeeds, inferHelperNeeds(payloadFile.content));
|
|
3043
|
+
const transformed = transformComponentSource(payloadFile.content, config, {
|
|
3044
|
+
isPrimitiveCategory,
|
|
3045
|
+
iconLibrary: style.iconLibrary,
|
|
3046
|
+
slug: payload.slug
|
|
851
3047
|
});
|
|
3048
|
+
componentContents.push(transformed.content);
|
|
3049
|
+
iconSwaps.push(transformed.dependencySwap);
|
|
3050
|
+
const isPrimaryFile = index === 0 && payloadFileKind === "component";
|
|
3051
|
+
const shouldWriteFile = isPrimaryFile || options.overwriteExistingDependencyFiles || !await fileExists(absoluteOutputPath);
|
|
3052
|
+
if (shouldWriteFile) {
|
|
3053
|
+
files.push({
|
|
3054
|
+
absolutePath: absoluteOutputPath,
|
|
3055
|
+
relativePath: relativeOutputPath,
|
|
3056
|
+
content: transformed.content,
|
|
3057
|
+
kind: payloadFileKind
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
852
3060
|
}
|
|
853
|
-
|
|
3061
|
+
const helperNeeds = payloadHelperNeeds ?? inferredHelperNeeds;
|
|
3062
|
+
if (!hasGraphHelperFiles && helperNeeds.utils) {
|
|
854
3063
|
const utilityPath = withSourceExtension(config.utils, "ts");
|
|
855
3064
|
if (!await fileExists(resolveInside(projectRoot, utilityPath))) {
|
|
856
3065
|
files.push({
|
|
@@ -861,7 +3070,7 @@ async function prepareInstall(projectRoot, config, payload) {
|
|
|
861
3070
|
});
|
|
862
3071
|
}
|
|
863
3072
|
}
|
|
864
|
-
if (
|
|
3073
|
+
if (!hasGraphHelperFiles && helperNeeds.primitives) {
|
|
865
3074
|
const primitivesPath = toPosixPath(join5(config.components, "primitives.tsx"));
|
|
866
3075
|
if (!await fileExists(resolveInside(projectRoot, primitivesPath))) {
|
|
867
3076
|
const primitives = primitivesTemplate(config.aliases.utils);
|
|
@@ -874,17 +3083,75 @@ async function prepareInstall(projectRoot, config, payload) {
|
|
|
874
3083
|
componentContents.push(primitives);
|
|
875
3084
|
}
|
|
876
3085
|
}
|
|
3086
|
+
const iconDependencySwap = aggregateIconDependencySwaps(iconSwaps);
|
|
3087
|
+
const declaredAfterSwap = applyIconDependencySwap(payload.dependencies ?? [], iconDependencySwap);
|
|
3088
|
+
const fonts = curatedFontsFromStyle(style);
|
|
3089
|
+
const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
|
|
877
3090
|
const dependencies = collectDependencies({
|
|
878
|
-
declared:
|
|
3091
|
+
declared: [...declaredAfterSwap, ...fontPlan.packages],
|
|
879
3092
|
source: componentContents.join("\n"),
|
|
880
|
-
needsUtility
|
|
3093
|
+
needsUtility: helperNeeds.utils
|
|
881
3094
|
});
|
|
882
|
-
|
|
3095
|
+
const cssFile = await planPrimitiveCssFile(projectRoot, config, payload);
|
|
3096
|
+
return {
|
|
3097
|
+
files,
|
|
3098
|
+
dependencies,
|
|
3099
|
+
...cssFile ? { cssFile } : {},
|
|
3100
|
+
...isPrimitiveCategory ? { iconDependencySwap } : {},
|
|
3101
|
+
...fontPlan.packages.length > 0 ? { fontPackages: fontPlan.packages } : {}
|
|
3102
|
+
};
|
|
3103
|
+
}
|
|
3104
|
+
async function planPrimitiveCssFile(projectRoot, config, payload) {
|
|
3105
|
+
if (!("primitiveCss" in payload) || typeof payload.primitiveCss !== "string") {
|
|
3106
|
+
return void 0;
|
|
3107
|
+
}
|
|
3108
|
+
let primitiveCss = payload.primitiveCss.trim();
|
|
3109
|
+
if (!primitiveCss) return void 0;
|
|
3110
|
+
const style = normalizeProjectStyle(config.style);
|
|
3111
|
+
primitiveCss = applyStyleToPrimitiveCss(primitiveCss, style).trim();
|
|
3112
|
+
const cssRelative = config.tailwind?.css;
|
|
3113
|
+
if (!cssRelative) return void 0;
|
|
3114
|
+
const absolutePath = resolveInside(projectRoot, cssRelative);
|
|
3115
|
+
const existing = await fileExists(absolutePath) ? await readFile4(absolutePath, "utf8") : "";
|
|
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);
|
|
3120
|
+
if (next === existing) return void 0;
|
|
3121
|
+
return {
|
|
3122
|
+
absolutePath,
|
|
3123
|
+
relativePath: toPosixPath(cssRelative),
|
|
3124
|
+
content: next,
|
|
3125
|
+
kind: "config"
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
function upsertPrimitiveCssBlock(existingCss, block) {
|
|
3129
|
+
const normalizedBlock = block.trim();
|
|
3130
|
+
const start = existingCss.indexOf(PRIMITIVE_CSS_START_MARKER);
|
|
3131
|
+
const end = existingCss.indexOf(PRIMITIVE_CSS_END_MARKER);
|
|
3132
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
3133
|
+
const before = existingCss.slice(0, start).replace(/\s*$/, "\n\n");
|
|
3134
|
+
const after = existingCss.slice(end + PRIMITIVE_CSS_END_MARKER.length).replace(/^\s*/, "\n");
|
|
3135
|
+
return `${before}${normalizedBlock}${after}`.replace(/\n{3,}/g, "\n\n");
|
|
3136
|
+
}
|
|
3137
|
+
if (start !== -1 || end !== -1) {
|
|
3138
|
+
throw new CliError(
|
|
3139
|
+
"Configured Tailwind CSS has a partial Wensity Primitive Tokens block. Remove the broken markers and re-run install."
|
|
3140
|
+
);
|
|
3141
|
+
}
|
|
3142
|
+
const trimmed = existingCss.replace(/\s*$/, "");
|
|
3143
|
+
if (!trimmed) return `${normalizedBlock}
|
|
3144
|
+
`;
|
|
3145
|
+
return `${trimmed}
|
|
3146
|
+
|
|
3147
|
+
${normalizedBlock}
|
|
3148
|
+
`;
|
|
883
3149
|
}
|
|
884
3150
|
async function assertNoConflicts(files, force) {
|
|
885
3151
|
if (force) return;
|
|
886
3152
|
const conflicts = [];
|
|
887
3153
|
for (const file of files) {
|
|
3154
|
+
if (file.kind === "config") continue;
|
|
888
3155
|
if (await fileExists(file.absolutePath)) conflicts.push(file.relativePath);
|
|
889
3156
|
}
|
|
890
3157
|
if (conflicts.length > 0) {
|
|
@@ -894,27 +3161,93 @@ ${conflicts.map((path) => ` - ${path}`).join("\n")}`);
|
|
|
894
3161
|
}
|
|
895
3162
|
async function writePlannedFiles(files) {
|
|
896
3163
|
for (const file of files) {
|
|
897
|
-
await
|
|
898
|
-
await
|
|
3164
|
+
await mkdir4(dirname5(file.absolutePath), { recursive: true });
|
|
3165
|
+
await writeFile4(file.absolutePath, file.content, "utf8");
|
|
899
3166
|
}
|
|
900
3167
|
}
|
|
901
3168
|
function summarizeFiles(projectRoot, files) {
|
|
902
3169
|
return files.map((file) => toPosixPath(relative3(projectRoot, file.absolutePath)));
|
|
903
3170
|
}
|
|
904
|
-
function transformComponentSource(content, config) {
|
|
905
|
-
const needsUtility = content.includes("@wensity/ui/lib") || importsFrom(content, "@/lib/utils");
|
|
906
|
-
const needsPrimitives = content.includes("@wensity/ui/components") || importsFrom(content, "@/components/wensity/primitives");
|
|
3171
|
+
function transformComponentSource(content, config, iconOptions) {
|
|
907
3172
|
let transformed = content;
|
|
908
3173
|
transformed = transformed.replace(/from\s+["']@wensity\/ui\/lib["']/g, `from "${config.aliases.utils}"`);
|
|
909
3174
|
transformed = transformed.replace(/from\s+["']@\/lib\/utils["']/g, `from "${config.aliases.utils}"`);
|
|
910
|
-
transformed = transformed.replace(/from\s+["']@wensity\/ui\/components["']/g, `from "
|
|
911
|
-
transformed = transformed
|
|
912
|
-
|
|
3175
|
+
transformed = transformed.replace(/from\s+["']@wensity\/ui\/components["']/g, `from "@/components/wensity/primitives"`);
|
|
3176
|
+
transformed = rewriteWensityComponentAlias(transformed, config.aliases.components);
|
|
3177
|
+
const iconResult = transformIconLibrary(transformed, {
|
|
3178
|
+
isPrimitiveCategory: iconOptions.isPrimitiveCategory,
|
|
3179
|
+
iconLibrary: iconOptions.iconLibrary,
|
|
3180
|
+
slug: iconOptions.slug
|
|
3181
|
+
});
|
|
3182
|
+
return { content: iconResult.content, dependencySwap: iconResult.dependencySwap };
|
|
3183
|
+
}
|
|
3184
|
+
function getPayloadFileKind(payloadFile) {
|
|
3185
|
+
if ("kind" in payloadFile && payloadFile.kind === "helper") return "helper";
|
|
3186
|
+
return isGraphHelperFile(payloadFile) ? "helper" : "component";
|
|
3187
|
+
}
|
|
3188
|
+
function getPayloadOutputPath(payloadFile, fallbackFileName, config) {
|
|
3189
|
+
if (isUtilsHelperFile(payloadFile)) return withSourceExtension(config.utils, "ts");
|
|
3190
|
+
if (isOverlayHelperFile(payloadFile)) return toPosixPath(join5(config.components, "overlay.tsx"));
|
|
3191
|
+
if (isPrimitivesHelperFile(payloadFile)) return toPosixPath(join5(config.components, "primitives.tsx"));
|
|
3192
|
+
if (payloadFile.kind === "helper") {
|
|
3193
|
+
throw new CliError(`Unsupported registry helper file path: ${payloadFile.path}`);
|
|
3194
|
+
}
|
|
3195
|
+
const safePayloadPath = sanitizePayloadPath(payloadFile.path, fallbackFileName);
|
|
3196
|
+
return toPosixPath(join5(config.components, safePayloadPath));
|
|
3197
|
+
}
|
|
3198
|
+
function isGraphHelperFile(payloadFile) {
|
|
3199
|
+
return isUtilsHelperFile(payloadFile) || isOverlayHelperFile(payloadFile) || isPrimitivesHelperFile(payloadFile);
|
|
3200
|
+
}
|
|
3201
|
+
function isUtilsHelperFile(payloadFile) {
|
|
3202
|
+
const normalized = payloadFile.path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3203
|
+
return payloadFile.kind === "helper" && (normalized === "lib/utils.ts" || normalized === "src/lib/utils.ts");
|
|
3204
|
+
}
|
|
3205
|
+
function isOverlayHelperFile(payloadFile) {
|
|
3206
|
+
const normalized = payloadFile.path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3207
|
+
return payloadFile.kind === "helper" && (normalized === OVERLAY_HELPER_PATH || normalized === `src/${OVERLAY_HELPER_PATH}`);
|
|
3208
|
+
}
|
|
3209
|
+
function isPrimitivesHelperFile(payloadFile) {
|
|
3210
|
+
const normalized = payloadFile.path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3211
|
+
return payloadFile.kind === "helper" && (normalized === "components/wensity/primitives.tsx" || normalized === "src/components/wensity/primitives.tsx");
|
|
3212
|
+
}
|
|
3213
|
+
function rewriteWensityComponentAlias(source, componentsAlias) {
|
|
3214
|
+
const replacement = componentsAlias.replace(/\/$/, "");
|
|
3215
|
+
return source.replace(
|
|
3216
|
+
/(from\s+["'])@\/components\/wensity(\/[^"']*)?(["'])/g,
|
|
3217
|
+
(_match, prefix, suffix, quote) => `${prefix}${replacement}${suffix ?? ""}${quote}`
|
|
3218
|
+
).replace(
|
|
3219
|
+
/(import\(\s*["'])@\/components\/wensity(\/[^"']*)?(["']\s*\))/g,
|
|
3220
|
+
(_match, prefix, suffix, suffixQuote) => `${prefix}${replacement}${suffix ?? ""}${suffixQuote}`
|
|
3221
|
+
);
|
|
913
3222
|
}
|
|
914
3223
|
function importsFrom(source, specifier) {
|
|
915
3224
|
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
916
3225
|
return new RegExp(`from\\s+["']${escaped}["']`).test(source);
|
|
917
3226
|
}
|
|
3227
|
+
function normalizePayloadHelperNeeds(value) {
|
|
3228
|
+
if (!value || typeof value !== "object") return void 0;
|
|
3229
|
+
const helperNeeds = value;
|
|
3230
|
+
if (typeof helperNeeds.utils !== "boolean" || typeof helperNeeds.primitives !== "boolean") {
|
|
3231
|
+
return void 0;
|
|
3232
|
+
}
|
|
3233
|
+
const overlay = helperNeeds.overlay === true;
|
|
3234
|
+
return {
|
|
3235
|
+
utils: helperNeeds.primitives || overlay || helperNeeds.utils,
|
|
3236
|
+
primitives: helperNeeds.primitives,
|
|
3237
|
+
overlay
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
function inferHelperNeeds(source) {
|
|
3241
|
+
const overlay = importsFrom(source, "@/components/wensity/overlay");
|
|
3242
|
+
const primitives = source.includes("@wensity/ui/components") || importsFrom(source, "@/components/wensity/primitives");
|
|
3243
|
+
const utils = overlay || primitives || source.includes("@wensity/ui/lib") || importsFrom(source, "@/lib/utils");
|
|
3244
|
+
return { utils, primitives, overlay };
|
|
3245
|
+
}
|
|
3246
|
+
function mergeHelperNeeds(target, source) {
|
|
3247
|
+
target.overlay ||= source.overlay === true;
|
|
3248
|
+
target.primitives ||= source.primitives;
|
|
3249
|
+
target.utils ||= source.utils || source.primitives || source.overlay === true;
|
|
3250
|
+
}
|
|
918
3251
|
function collectDependencies(input) {
|
|
919
3252
|
const dependencies = /* @__PURE__ */ new Set();
|
|
920
3253
|
for (const dependency of input.declared) {
|
|
@@ -1008,11 +3341,26 @@ async function initCommand(context, parsed) {
|
|
|
1008
3341
|
const force = hasFlag(parsed, "force");
|
|
1009
3342
|
const install = !hasFlag(parsed, "no-install");
|
|
1010
3343
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
|
|
3344
|
+
const presetCode = getStringOption(parsed, "preset");
|
|
3345
|
+
const presetFile = getStringOption(parsed, "preset-file");
|
|
3346
|
+
const hasPreset = Boolean(presetCode || presetFile);
|
|
3347
|
+
const dependencies = ["clsx", "tailwind-merge"];
|
|
3348
|
+
if (hasPreset) {
|
|
3349
|
+
await initWithPreset(context, {
|
|
3350
|
+
dryRun,
|
|
3351
|
+
force,
|
|
3352
|
+
install,
|
|
3353
|
+
apiUrl,
|
|
3354
|
+
presetCode,
|
|
3355
|
+
presetFile,
|
|
3356
|
+
dependencies
|
|
3357
|
+
});
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
1011
3360
|
const config = createDefaultConfig(context.cwd, apiUrl);
|
|
1012
3361
|
const configPath = join6(context.cwd, CONFIG_FILE_NAME);
|
|
1013
3362
|
const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
|
|
1014
3363
|
const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
|
|
1015
|
-
const dependencies = ["clsx", "tailwind-merge"];
|
|
1016
3364
|
if (dryRun) {
|
|
1017
3365
|
printTitle("Project initialization preview");
|
|
1018
3366
|
print(action("Would write:"));
|
|
@@ -1039,6 +3387,118 @@ async function initCommand(context, parsed) {
|
|
|
1039
3387
|
"Install your chosen slug: npx wensity add <component-slug>"
|
|
1040
3388
|
]));
|
|
1041
3389
|
}
|
|
3390
|
+
async function initWithPreset(context, options) {
|
|
3391
|
+
const { style, presetCode } = await resolvePresetStyle({
|
|
3392
|
+
presetCode: options.presetCode,
|
|
3393
|
+
presetFile: options.presetFile
|
|
3394
|
+
});
|
|
3395
|
+
const fonts = curatedFontsFromStyle(style);
|
|
3396
|
+
const fontPlan = planFontInstall(fonts.headingFont, fonts.bodyFont);
|
|
3397
|
+
const installDependencies = [...options.dependencies, ...fontPlan.packages];
|
|
3398
|
+
const configPath = join6(context.cwd, CONFIG_FILE_NAME);
|
|
3399
|
+
const configExists = await fileExists(configPath);
|
|
3400
|
+
let config;
|
|
3401
|
+
if (configExists && !options.force) {
|
|
3402
|
+
const loaded = await loadConfig(context.cwd, { apiUrl: options.apiUrl });
|
|
3403
|
+
config = {
|
|
3404
|
+
...loaded.config,
|
|
3405
|
+
style
|
|
3406
|
+
};
|
|
3407
|
+
} else {
|
|
3408
|
+
config = {
|
|
3409
|
+
...createDefaultConfig(context.cwd, options.apiUrl),
|
|
3410
|
+
style
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
const utilityPath = resolveConfiguredFilePath(context.cwd, config.utils);
|
|
3414
|
+
const utilityRelativePath = toPosixPath(config.utils.endsWith(".ts") ? config.utils : `${config.utils}.ts`);
|
|
3415
|
+
const registry = config.registry;
|
|
3416
|
+
let primitiveCss;
|
|
3417
|
+
try {
|
|
3418
|
+
const payload = await withSpinner(
|
|
3419
|
+
`Fetching primitive tokens (${INIT_PRIMITIVE_CSS_SLUG})`,
|
|
3420
|
+
() => fetchComponentPayload({ slug: INIT_PRIMITIVE_CSS_SLUG, registry }),
|
|
3421
|
+
"Fetched primitive tokens"
|
|
3422
|
+
);
|
|
3423
|
+
if (typeof payload.primitiveCss !== "string" || !payload.primitiveCss.trim()) {
|
|
3424
|
+
throw new CliError(
|
|
3425
|
+
`Pinned slug "${INIT_PRIMITIVE_CSS_SLUG}" did not return primitiveCss. Cannot apply preset styles.`
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
primitiveCss = applyStyleToPrimitiveCss(payload.primitiveCss, style).trim();
|
|
3429
|
+
} catch (error) {
|
|
3430
|
+
if (error instanceof CliError) throw error;
|
|
3431
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3432
|
+
throw new CliError(`Failed to fetch primitive CSS for preset init: ${message}`);
|
|
3433
|
+
}
|
|
3434
|
+
const cssRelative = config.tailwind?.css;
|
|
3435
|
+
if (!cssRelative) {
|
|
3436
|
+
throw new CliError("No Tailwind CSS path configured. Set tailwind.css in wensity.json.");
|
|
3437
|
+
}
|
|
3438
|
+
const cssAbsolute = resolveInside(context.cwd, cssRelative);
|
|
3439
|
+
const existingCss = await fileExists(cssAbsolute) ? await readFile5(cssAbsolute, "utf8") : "";
|
|
3440
|
+
const withTokens = upsertPrimitiveCssBlock(existingCss, primitiveCss);
|
|
3441
|
+
const nextCss = upsertFontImportBlock(withTokens, fontPlan.block);
|
|
3442
|
+
const cssChanged = nextCss !== existingCss;
|
|
3443
|
+
const installedPackages = readInstalledPackageNames(context.cwd);
|
|
3444
|
+
const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
|
|
3445
|
+
if (options.dryRun) {
|
|
3446
|
+
printTitle("Project initialization preview (preset)");
|
|
3447
|
+
print(action("Would write:"));
|
|
3448
|
+
const planned = [CONFIG_FILE_NAME, utilityRelativePath];
|
|
3449
|
+
if (cssChanged) planned.push(toPosixPath(cssRelative));
|
|
3450
|
+
print(formatList(planned));
|
|
3451
|
+
print(action(`Preset: ${presetCode.slice(0, 24)}\u2026`));
|
|
3452
|
+
print(action(`Style: icon=${style.iconLibrary} radius=${style.radius}`));
|
|
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);
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
const shouldWriteUtility = options.force || !await fileExists(utilityPath);
|
|
3461
|
+
await commitPresetInitWrites({
|
|
3462
|
+
cssAbsolute,
|
|
3463
|
+
existingCss,
|
|
3464
|
+
nextCss,
|
|
3465
|
+
cssChanged,
|
|
3466
|
+
writeUtility: shouldWriteUtility,
|
|
3467
|
+
utilityAbsolutePath: utilityPath,
|
|
3468
|
+
utilityContent: cnUtilityTemplate(),
|
|
3469
|
+
commitConfig: async () => {
|
|
3470
|
+
if (configExists && !options.force) {
|
|
3471
|
+
await mergeStyleIntoConfigFile(configPath, style);
|
|
3472
|
+
} else {
|
|
3473
|
+
await writeConfigFile(configPath, config, options.force);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
});
|
|
3477
|
+
if (options.install) {
|
|
3478
|
+
runInstall(context.cwd, missingDependencies(context.cwd, installDependencies));
|
|
3479
|
+
}
|
|
3480
|
+
printTitle("Project initialized");
|
|
3481
|
+
const created = [
|
|
3482
|
+
success(CONFIG_FILE_NAME),
|
|
3483
|
+
success(utilityRelativePath),
|
|
3484
|
+
success(`registry: ${options.apiUrl}`),
|
|
3485
|
+
success(`preset applied (${style.iconLibrary}, radius=${style.radius})`)
|
|
3486
|
+
];
|
|
3487
|
+
if (cssChanged) {
|
|
3488
|
+
created.push(success(`CSS tokens: ${toPosixPath(cssRelative)}`));
|
|
3489
|
+
} else {
|
|
3490
|
+
created.push(action(`CSS tokens unchanged: ${toPosixPath(cssRelative)}`));
|
|
3491
|
+
}
|
|
3492
|
+
if (fontPlan.packages.length > 0) {
|
|
3493
|
+
created.push(success(`fonts: ${fontPlan.packages.join(", ")}`));
|
|
3494
|
+
}
|
|
3495
|
+
print(panel("Created", created));
|
|
3496
|
+
if (unusedFontWarning) warn(unusedFontWarning);
|
|
3497
|
+
print(nextSteps([
|
|
3498
|
+
"Browse available components: npx wensity list",
|
|
3499
|
+
"Install a primitive: npx wensity add button"
|
|
3500
|
+
]));
|
|
3501
|
+
}
|
|
1042
3502
|
async function loginCommand(context, parsed) {
|
|
1043
3503
|
const apiUrl = getStringOption(parsed, "api-url") ?? context.env.WENSITY_API_URL ?? DEFAULT_API_ORIGIN;
|
|
1044
3504
|
const apiBaseUrl = normalizeUrlOrigin(apiUrl);
|
|
@@ -1167,22 +3627,35 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
|
|
|
1167
3627
|
const loaded = await loadConfig(context.cwd, { registry: registryOverride, apiUrl });
|
|
1168
3628
|
const credentials = await readCredentials(context.env);
|
|
1169
3629
|
const registry = registryUrlForType(loaded.config.registry, resourceType, registryOverride);
|
|
1170
|
-
const payload = resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials });
|
|
1171
|
-
const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload);
|
|
1172
3630
|
const force = updateMode || hasFlag(parsed, "force");
|
|
3631
|
+
const payload = resourceType === "block" ? await fetchBlockPayload({ slug, registry, credentials }) : await fetchComponentPayload({ slug, registry, credentials });
|
|
3632
|
+
const prepared = await prepareInstall(loaded.projectRoot, loaded.config, payload, {
|
|
3633
|
+
overwriteExistingDependencyFiles: force
|
|
3634
|
+
});
|
|
1173
3635
|
const dryRun = hasFlag(parsed, "dry-run");
|
|
1174
3636
|
const install = !hasFlag(parsed, "no-install");
|
|
1175
|
-
|
|
1176
|
-
|
|
3637
|
+
const plannedInstallFiles = prepared.cssFile ? [...prepared.files, prepared.cssFile] : prepared.files;
|
|
3638
|
+
await assertNoConflicts(plannedInstallFiles, force || dryRun);
|
|
3639
|
+
const plannedFiles = summarizeFiles(loaded.projectRoot, plannedInstallFiles);
|
|
1177
3640
|
const missing = missingDependencies(loaded.projectRoot, prepared.dependencies);
|
|
3641
|
+
const style = normalizeProjectStyle(loaded.config.style);
|
|
3642
|
+
const fonts = curatedFontsFromStyle(style);
|
|
3643
|
+
const installedPackages = readInstalledPackageNames(loaded.projectRoot);
|
|
3644
|
+
const oppositeIconWarning = installedPackages && prepared.iconDependencySwap ? warnOppositeIconPackageMessage(style.iconLibrary, installedPackages) : void 0;
|
|
3645
|
+
const unusedFontWarning = installedPackages ? warnUnusedFontPackageMessage(fonts.headingFont, fonts.bodyFont, installedPackages) : void 0;
|
|
1178
3646
|
if (dryRun) {
|
|
1179
3647
|
printTitle(updateMode ? "Update preview" : "Install preview");
|
|
1180
3648
|
print(action("Would write:"));
|
|
1181
3649
|
print(formatList(plannedFiles));
|
|
3650
|
+
if (prepared.cssFile) {
|
|
3651
|
+
print(action(`Would update CSS tokens in: ${prepared.cssFile.relativePath}`));
|
|
3652
|
+
}
|
|
1182
3653
|
if (install && missing.length > 0) print(action(`Would install: ${missing.join(" ")}`));
|
|
3654
|
+
if (oppositeIconWarning) warn(oppositeIconWarning);
|
|
3655
|
+
if (unusedFontWarning) warn(unusedFontWarning);
|
|
1183
3656
|
return;
|
|
1184
3657
|
}
|
|
1185
|
-
await writePlannedFiles(
|
|
3658
|
+
await writePlannedFiles(plannedInstallFiles);
|
|
1186
3659
|
if (install) {
|
|
1187
3660
|
runInstall(loaded.projectRoot, missing);
|
|
1188
3661
|
}
|
|
@@ -1191,6 +3664,8 @@ Usage: wensity ${updateMode ? "update" : "add"} <slug>${resourceType === "block"
|
|
|
1191
3664
|
success(`${updateMode ? "Updated" : "Added"} ${payload.slug}`),
|
|
1192
3665
|
...plannedFiles.map((file) => success(file))
|
|
1193
3666
|
]));
|
|
3667
|
+
if (oppositeIconWarning) warn(oppositeIconWarning);
|
|
3668
|
+
if (unusedFontWarning) warn(unusedFontWarning);
|
|
1194
3669
|
if (!install && missing.length > 0) {
|
|
1195
3670
|
warn(`Dependencies were not installed: ${missing.join(" ")}`);
|
|
1196
3671
|
print(nextSteps([
|
|
@@ -1313,8 +3788,8 @@ function formatAccountPanel(whoami, savedPath) {
|
|
|
1313
3788
|
return panel(isPro ? "Wensity Pro" : "Wensity Free", lines);
|
|
1314
3789
|
}
|
|
1315
3790
|
async function writeTextFile(path, content) {
|
|
1316
|
-
await
|
|
1317
|
-
await
|
|
3791
|
+
await mkdir5(dirname6(path), { recursive: true });
|
|
3792
|
+
await writeFile5(path, content, "utf8");
|
|
1318
3793
|
}
|
|
1319
3794
|
|
|
1320
3795
|
// src/index.ts
|