@dsai-io/tools 0.0.1

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.
@@ -0,0 +1,3682 @@
1
+ import * as fs3 from 'fs';
2
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, rmSync } from 'fs';
3
+ import * as path from 'path';
4
+ import { relative, basename, extname, join, dirname, resolve } from 'path';
5
+ import { execSync } from 'child_process';
6
+
7
+ /* @dsai-io/tools - DSAi Design System Build Tools */
8
+
9
+ // src/tokens/types.ts
10
+ var VALID_TOKEN_TYPES = [
11
+ "color",
12
+ "dimension",
13
+ "fontFamily",
14
+ "fontWeight",
15
+ "fontStyle",
16
+ "shadow",
17
+ "number",
18
+ "string",
19
+ "duration",
20
+ "cubicBezier",
21
+ "strokeStyle",
22
+ "border",
23
+ "transition",
24
+ "gradient",
25
+ "typography",
26
+ "letterSpacing",
27
+ "lineHeight",
28
+ "paragraphSpacing",
29
+ "textDecoration",
30
+ "textCase"
31
+ ];
32
+ function isDTCGToken(obj) {
33
+ if (typeof obj !== "object" || obj === null) {
34
+ return false;
35
+ }
36
+ return "$value" in obj;
37
+ }
38
+ function isLegacyToken(obj) {
39
+ if (typeof obj !== "object" || obj === null) {
40
+ return false;
41
+ }
42
+ return "value" in obj && !("$value" in obj);
43
+ }
44
+ function isToken(obj) {
45
+ return isDTCGToken(obj) || isLegacyToken(obj);
46
+ }
47
+ function isValidTokenType(type) {
48
+ return VALID_TOKEN_TYPES.includes(type);
49
+ }
50
+ function isTokenReference(value) {
51
+ return typeof value === "string" && value.startsWith("{") && value.endsWith("}");
52
+ }
53
+ function getTokenValue(token) {
54
+ return isDTCGToken(token) ? token.$value : token.value;
55
+ }
56
+ function getTokenType(token) {
57
+ if (isDTCGToken(token)) {
58
+ return token.$type;
59
+ }
60
+ return token.type;
61
+ }
62
+ function getTokenDescription(token) {
63
+ if (isDTCGToken(token)) {
64
+ return token.$description;
65
+ }
66
+ const legacy = token;
67
+ return legacy.description ?? legacy.comment;
68
+ }
69
+ function toDTCGToken(token) {
70
+ const dtcg = {
71
+ $value: token.value
72
+ };
73
+ if (token.type && isValidTokenType(token.type)) {
74
+ dtcg.$type = token.type;
75
+ }
76
+ const desc = token.description ?? token.comment;
77
+ if (desc) {
78
+ dtcg.$description = desc;
79
+ }
80
+ return dtcg;
81
+ }
82
+ function parseTokenReference(ref) {
83
+ if (!isTokenReference(ref)) {
84
+ return null;
85
+ }
86
+ const inner = ref.slice(1, -1);
87
+ return inner.split(".");
88
+ }
89
+ var HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
90
+ var RGB_PATTERN = /^rgba?\s*\(/i;
91
+ var HSL_PATTERN = /^hsla?\s*\(/i;
92
+ var NAMED_COLORS = /* @__PURE__ */ new Set([
93
+ "transparent",
94
+ "currentcolor",
95
+ "inherit",
96
+ "initial",
97
+ "unset",
98
+ "black",
99
+ "white",
100
+ "red",
101
+ "green",
102
+ "blue",
103
+ "yellow",
104
+ "orange",
105
+ "purple",
106
+ "pink",
107
+ "gray",
108
+ "grey"
109
+ ]);
110
+ function isValidColor(value) {
111
+ if (HEX_COLOR_PATTERN.test(value)) {
112
+ return true;
113
+ }
114
+ if (RGB_PATTERN.test(value)) {
115
+ return true;
116
+ }
117
+ if (HSL_PATTERN.test(value)) {
118
+ return true;
119
+ }
120
+ if (isTokenReference(value)) {
121
+ return true;
122
+ }
123
+ if (NAMED_COLORS.has(value.toLowerCase())) {
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ var VALID_UNITS = /* @__PURE__ */ new Set([
129
+ "px",
130
+ "rem",
131
+ "em",
132
+ "%",
133
+ "vh",
134
+ "vw",
135
+ "vmin",
136
+ "vmax",
137
+ "pt",
138
+ "cm",
139
+ "mm",
140
+ "in",
141
+ "ch",
142
+ "ex"
143
+ ]);
144
+ function isValidDimension(value) {
145
+ if (value === "0") {
146
+ return true;
147
+ }
148
+ if (isTokenReference(value)) {
149
+ return true;
150
+ }
151
+ const trimmed = value.trim();
152
+ if (trimmed.length === 0) {
153
+ return false;
154
+ }
155
+ let i = 0;
156
+ const firstChar = trimmed.charAt(0);
157
+ if (firstChar === "-" || firstChar === "+") {
158
+ i = 1;
159
+ }
160
+ let hasDigits = false;
161
+ let hasDot = false;
162
+ while (i < trimmed.length) {
163
+ const char = trimmed.charAt(i);
164
+ if (char >= "0" && char <= "9") {
165
+ hasDigits = true;
166
+ i++;
167
+ } else if (char === "." && !hasDot) {
168
+ hasDot = true;
169
+ i++;
170
+ } else {
171
+ break;
172
+ }
173
+ }
174
+ if (!hasDigits) {
175
+ return false;
176
+ }
177
+ const unit = trimmed.slice(i).toLowerCase();
178
+ if (unit === "") {
179
+ return true;
180
+ }
181
+ return VALID_UNITS.has(unit);
182
+ }
183
+ function validateSingleToken(path5, token, errors, warnings) {
184
+ const value = getTokenValue(token);
185
+ const type = getTokenType(token);
186
+ const isDtcg = isDTCGToken(token);
187
+ if (!isDtcg) {
188
+ warnings.push({
189
+ path: path5,
190
+ message: "Token uses legacy format. Consider migrating to DTCG ($value, $type).",
191
+ severity: "warning",
192
+ suggestion: "Use $value instead of value, $type instead of type"
193
+ });
194
+ }
195
+ if (value === void 0 || value === null) {
196
+ errors.push({
197
+ path: path5,
198
+ message: "Token has no value",
199
+ severity: "error"
200
+ });
201
+ return;
202
+ }
203
+ if (value === "") {
204
+ warnings.push({
205
+ path: path5,
206
+ message: "Token has empty string value",
207
+ severity: "warning",
208
+ value
209
+ });
210
+ }
211
+ if (!type) {
212
+ warnings.push({
213
+ path: path5,
214
+ message: "Token has no type specified",
215
+ severity: "warning",
216
+ suggestion: `Add $type property with one of: ${VALID_TOKEN_TYPES.slice(0, 5).join(", ")}...`
217
+ });
218
+ } else if (!isValidTokenType(type)) {
219
+ warnings.push({
220
+ path: path5,
221
+ message: `Unknown token type: "${type}"`,
222
+ severity: "warning",
223
+ value: type,
224
+ suggestion: `Valid types: ${VALID_TOKEN_TYPES.join(", ")}`
225
+ });
226
+ }
227
+ if (type && typeof value === "string") {
228
+ validateTypedValue(path5, value, type, errors);
229
+ }
230
+ }
231
+ function validateTypedValue(path5, value, type, errors) {
232
+ switch (type) {
233
+ case "color":
234
+ if (!isValidColor(value)) {
235
+ errors.push({
236
+ path: path5,
237
+ message: `Invalid color value: "${value}"`,
238
+ severity: "error",
239
+ value,
240
+ suggestion: "Use hex (#fff), rgb(), hsl(), or token reference"
241
+ });
242
+ }
243
+ break;
244
+ case "dimension":
245
+ if (!isValidDimension(value)) {
246
+ errors.push({
247
+ path: path5,
248
+ message: `Invalid dimension value: "${value}"`,
249
+ severity: "error",
250
+ value,
251
+ suggestion: "Use value with unit (16px, 1rem) or token reference"
252
+ });
253
+ }
254
+ break;
255
+ case "fontWeight":
256
+ if (!isValidFontWeight(value)) {
257
+ errors.push({
258
+ path: path5,
259
+ message: `Invalid fontWeight value: "${value}"`,
260
+ severity: "error",
261
+ value,
262
+ suggestion: "Use numeric (100-900) or keyword (normal, bold)"
263
+ });
264
+ }
265
+ break;
266
+ }
267
+ }
268
+ function isValidFontWeight(value) {
269
+ const numeric = parseInt(value, 10);
270
+ if (!Number.isNaN(numeric) && numeric >= 1 && numeric <= 1e3) {
271
+ return true;
272
+ }
273
+ const keywords = ["normal", "bold", "lighter", "bolder"];
274
+ if (keywords.includes(value.toLowerCase())) {
275
+ return true;
276
+ }
277
+ if (isTokenReference(value)) {
278
+ return true;
279
+ }
280
+ return false;
281
+ }
282
+ function validateCollection(collection, basePath, errors, warnings, tokenCount) {
283
+ for (const [key, value] of Object.entries(collection)) {
284
+ if (key.startsWith("$")) {
285
+ continue;
286
+ }
287
+ const path5 = basePath ? `${basePath}.${key}` : key;
288
+ if (isToken(value)) {
289
+ tokenCount.count += 1;
290
+ validateSingleToken(path5, value, errors, warnings);
291
+ } else if (typeof value === "object" && value !== null) {
292
+ validateCollection(value, path5, errors, warnings, tokenCount);
293
+ }
294
+ }
295
+ }
296
+ function validateFile(filePath, errors, warnings) {
297
+ try {
298
+ const content = readFileSync(filePath, "utf-8");
299
+ const data = JSON.parse(content);
300
+ const tokenCount = { count: 0 };
301
+ validateCollection(data, "", errors, warnings, tokenCount);
302
+ return tokenCount.count;
303
+ } catch (error) {
304
+ const errorMessage = error instanceof Error ? error.message : String(error);
305
+ errors.push({
306
+ path: filePath,
307
+ message: `Failed to parse file: ${errorMessage}`,
308
+ severity: "error"
309
+ });
310
+ return 0;
311
+ }
312
+ }
313
+ function findJsonFiles(dir, files = []) {
314
+ if (!existsSync(dir)) {
315
+ return files;
316
+ }
317
+ const entries = readdirSync(dir);
318
+ for (const entry of entries) {
319
+ const fullPath = join(dir, entry);
320
+ const stat = statSync(fullPath);
321
+ if (stat.isDirectory()) {
322
+ findJsonFiles(fullPath, files);
323
+ } else if (entry.endsWith(".json")) {
324
+ files.push(fullPath);
325
+ }
326
+ }
327
+ return files;
328
+ }
329
+ async function validateTokens(config, options = {}) {
330
+ const startTime = Date.now();
331
+ const { verbose = false, quiet = false, strict = false } = options;
332
+ const errors = [];
333
+ const warnings = [];
334
+ let totalTokens = 0;
335
+ let fileCount = 0;
336
+ const collectionsDir = options.collectionsDir ?? config.tokens.collectionsDir;
337
+ if (!existsSync(collectionsDir)) {
338
+ return {
339
+ valid: false,
340
+ errors: [
341
+ {
342
+ path: collectionsDir,
343
+ message: "Collections directory does not exist",
344
+ severity: "error"
345
+ }
346
+ ],
347
+ warnings: [],
348
+ tokenCount: 0,
349
+ fileCount: 0,
350
+ duration: Date.now() - startTime
351
+ };
352
+ }
353
+ const jsonFiles = findJsonFiles(collectionsDir);
354
+ if (!quiet) {
355
+ logInfo(`Validating ${jsonFiles.length} token files...`);
356
+ }
357
+ for (const file of jsonFiles) {
358
+ fileCount += 1;
359
+ const relativePath = relative(collectionsDir, file);
360
+ if (verbose) {
361
+ logDebug(`Validating: ${relativePath}`);
362
+ }
363
+ const tokenCount = validateFile(file, errors, warnings);
364
+ totalTokens += tokenCount;
365
+ }
366
+ const valid = strict ? errors.length === 0 && warnings.length === 0 : errors.length === 0;
367
+ if (!quiet) {
368
+ if (valid) {
369
+ logSuccess(`Validated ${totalTokens} tokens in ${fileCount} files`);
370
+ if (warnings.length > 0) {
371
+ logWarn(`${warnings.length} warnings found`);
372
+ }
373
+ } else {
374
+ logError(`Validation failed with ${errors.length} errors`);
375
+ }
376
+ }
377
+ return {
378
+ valid,
379
+ errors,
380
+ warnings,
381
+ tokenCount: totalTokens,
382
+ fileCount,
383
+ duration: Date.now() - startTime
384
+ };
385
+ }
386
+ async function validateTokensCLI(config, options = {}) {
387
+ const result = await validateTokens(config, { ...options, verbose: true });
388
+ if (!result.valid) {
389
+ for (const error of result.errors) {
390
+ console.error(`\u274C ${error.path}: ${error.message}`);
391
+ }
392
+ process.exit(1);
393
+ }
394
+ for (const warning of result.warnings) {
395
+ console.warn(`\u26A0\uFE0F ${warning.path}: ${warning.message}`);
396
+ }
397
+ }
398
+ function logInfo(message) {
399
+ console.info(`\u2139\uFE0F ${message}`);
400
+ }
401
+ function logDebug(message) {
402
+ console.info(`\u{1F50D} ${message}`);
403
+ }
404
+ function logSuccess(message) {
405
+ console.info(`\u2705 ${message}`);
406
+ }
407
+ function logWarn(message) {
408
+ console.warn(`\u26A0\uFE0F ${message}`);
409
+ }
410
+ function logError(message) {
411
+ console.error(`\u274C ${message}`);
412
+ }
413
+ var DEFAULT_COLLECTIONS = {
414
+ foundation: {
415
+ input: "foundation.json",
416
+ modeAware: true,
417
+ modes: ["Light", "Dark"]
418
+ },
419
+ typography: {
420
+ input: "typography.json",
421
+ modeAware: false,
422
+ modes: ["Base"]
423
+ },
424
+ spacing: {
425
+ input: "spacing.json",
426
+ modeAware: false,
427
+ modes: ["Base"]
428
+ },
429
+ radius: {
430
+ input: "radius.json",
431
+ modeAware: false,
432
+ modes: ["Base"]
433
+ },
434
+ layout: {
435
+ input: "layout.json",
436
+ modeAware: false,
437
+ modes: ["Base"]
438
+ },
439
+ shadows: {
440
+ input: "shadows.json",
441
+ modeAware: false,
442
+ modes: ["Base"]
443
+ }
444
+ };
445
+ function validateFigmaToken(path5, token, errors, warnings) {
446
+ if (!isToken(token)) {
447
+ return;
448
+ }
449
+ if (isDTCGToken(token)) {
450
+ if (token.$value === void 0 || token.$value === null) {
451
+ errors.push({
452
+ path: path5,
453
+ message: "Token has $value property but value is undefined or null",
454
+ severity: "error"
455
+ });
456
+ }
457
+ if (token.$type !== void 0) {
458
+ if (!isValidTokenType(token.$type)) {
459
+ warnings.push({
460
+ path: path5,
461
+ message: `Unknown token type: ${token.$type}`,
462
+ severity: "warning"
463
+ });
464
+ }
465
+ }
466
+ } else if (isLegacyToken(token)) {
467
+ if (token.value === void 0 || token.value === null) {
468
+ errors.push({
469
+ path: path5,
470
+ message: "Token has value property but value is undefined or null",
471
+ severity: "error"
472
+ });
473
+ }
474
+ warnings.push({
475
+ path: path5,
476
+ message: "Token uses legacy format (value instead of $value), consider migrating to DTCG",
477
+ severity: "warning"
478
+ });
479
+ }
480
+ }
481
+ function validateTokenTree(obj, basePath, errors, warnings) {
482
+ if (obj === null || typeof obj !== "object") {
483
+ return;
484
+ }
485
+ for (const [key, value] of Object.entries(obj)) {
486
+ const path5 = basePath ? `${basePath}.${key}` : key;
487
+ if (isToken(value)) {
488
+ validateFigmaToken(path5, value, errors, warnings);
489
+ } else if (typeof value === "object" && value !== null) {
490
+ validateTokenTree(value, path5, errors, warnings);
491
+ }
492
+ }
493
+ }
494
+ function validateCollectionStructure(data, collectionName, expectedCollection, errors, warnings) {
495
+ const detectedModes = /* @__PURE__ */ new Set();
496
+ const collection = Object.entries(data).find(
497
+ ([key]) => key.toLowerCase() === collectionName.toLowerCase()
498
+ )?.[1];
499
+ if (!collection) {
500
+ errors.push({
501
+ path: collectionName,
502
+ message: `Expected collection "${collectionName}" not found in export`,
503
+ severity: "error"
504
+ });
505
+ return detectedModes;
506
+ }
507
+ if (collection.modes) {
508
+ const modes = Object.keys(collection.modes);
509
+ for (const mode of modes) {
510
+ detectedModes.add(mode);
511
+ }
512
+ if (expectedCollection.modeAware && expectedCollection.modes) {
513
+ for (const expectedMode of expectedCollection.modes) {
514
+ if (!modes.includes(expectedMode)) {
515
+ warnings.push({
516
+ path: `${collectionName}.modes`,
517
+ message: `Expected mode "${expectedMode}" not found, available modes: ${modes.join(", ")}`,
518
+ severity: "warning"
519
+ });
520
+ }
521
+ }
522
+ }
523
+ for (const [modeName, modeData] of Object.entries(collection.modes)) {
524
+ if (modeData && typeof modeData === "object") {
525
+ validateTokenTree(modeData, `${collectionName}.modes.${modeName}`, errors, warnings);
526
+ }
527
+ }
528
+ } else {
529
+ validateTokenTree(collection, collectionName, errors, warnings);
530
+ }
531
+ return detectedModes;
532
+ }
533
+ function validateFigmaFile(filePath, expectedCollection) {
534
+ const errors = [];
535
+ const warnings = [];
536
+ const detectedModes = /* @__PURE__ */ new Set();
537
+ let tokenCount = 0;
538
+ if (!existsSync(filePath)) {
539
+ errors.push({
540
+ path: filePath,
541
+ message: "File not found",
542
+ severity: "error"
543
+ });
544
+ return { valid: false, errors, warnings, detectedModes, tokenCount: 0, fileCount: 0 };
545
+ }
546
+ let data;
547
+ try {
548
+ const content = readFileSync(filePath, "utf-8");
549
+ data = JSON.parse(content);
550
+ } catch (error) {
551
+ const message = error instanceof Error ? error.message : "Unknown error";
552
+ errors.push({
553
+ path: filePath,
554
+ message: `Failed to parse JSON: ${message}`,
555
+ severity: "error"
556
+ });
557
+ return { valid: false, errors, warnings, detectedModes, tokenCount: 0, fileCount: 1 };
558
+ }
559
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
560
+ errors.push({
561
+ path: filePath,
562
+ message: "Figma export must be a JSON object",
563
+ severity: "error"
564
+ });
565
+ return { valid: false, errors, warnings, detectedModes, tokenCount: 0, fileCount: 1 };
566
+ }
567
+ const fileName = basename(filePath, extname(filePath));
568
+ const collectionName = fileName.charAt(0).toUpperCase() + fileName.slice(1);
569
+ const countTokens2 = (obj) => {
570
+ if (obj === null || typeof obj !== "object") {
571
+ return 0;
572
+ }
573
+ if (isToken(obj)) {
574
+ return 1;
575
+ }
576
+ let count = 0;
577
+ for (const value of Object.values(obj)) {
578
+ count += countTokens2(value);
579
+ }
580
+ return count;
581
+ };
582
+ if (expectedCollection) {
583
+ const modes = validateCollectionStructure(
584
+ data,
585
+ collectionName,
586
+ expectedCollection,
587
+ errors,
588
+ warnings
589
+ );
590
+ for (const mode of modes) {
591
+ detectedModes.add(mode);
592
+ }
593
+ } else {
594
+ for (const [key, value] of Object.entries(data)) {
595
+ if (typeof value === "object" && value !== null) {
596
+ const collection = value;
597
+ if (collection.modes) {
598
+ for (const mode of Object.keys(collection.modes)) {
599
+ detectedModes.add(mode);
600
+ }
601
+ }
602
+ validateTokenTree(value, key, errors, warnings);
603
+ }
604
+ }
605
+ }
606
+ tokenCount = countTokens2(data);
607
+ return {
608
+ valid: errors.length === 0,
609
+ errors,
610
+ warnings,
611
+ detectedModes,
612
+ tokenCount,
613
+ fileCount: 1
614
+ };
615
+ }
616
+ function validateFigmaExports(options = {}) {
617
+ const {
618
+ exportsDir,
619
+ collections = DEFAULT_COLLECTIONS,
620
+ strict = false,
621
+ skipMissing = false
622
+ } = options;
623
+ const errors = [];
624
+ const warnings = [];
625
+ const files = /* @__PURE__ */ new Map();
626
+ const detectedModes = /* @__PURE__ */ new Set();
627
+ const missingFiles = [];
628
+ let totalTokenCount = 0;
629
+ let totalFileCount = 0;
630
+ const targetDir = exportsDir ?? options.config?.tokens.sourceDir;
631
+ if (!targetDir) {
632
+ errors.push({
633
+ path: "config",
634
+ message: "Figma exports directory not specified",
635
+ severity: "error"
636
+ });
637
+ return {
638
+ valid: false,
639
+ errors,
640
+ warnings,
641
+ files,
642
+ detectedModes,
643
+ missingFiles,
644
+ tokenCount: 0,
645
+ fileCount: 0
646
+ };
647
+ }
648
+ if (!existsSync(targetDir)) {
649
+ errors.push({
650
+ path: targetDir,
651
+ message: "Figma exports directory not found",
652
+ severity: "error"
653
+ });
654
+ return {
655
+ valid: false,
656
+ errors,
657
+ warnings,
658
+ files,
659
+ detectedModes,
660
+ missingFiles,
661
+ tokenCount: 0,
662
+ fileCount: 0
663
+ };
664
+ }
665
+ for (const [_name, expectedCollection] of Object.entries(collections)) {
666
+ const filePath = join(targetDir, expectedCollection.input);
667
+ if (!existsSync(filePath)) {
668
+ missingFiles.push(expectedCollection.input);
669
+ if (!skipMissing) {
670
+ errors.push({
671
+ path: filePath,
672
+ message: `Expected Figma export "${expectedCollection.input}" not found`,
673
+ severity: "error"
674
+ });
675
+ }
676
+ continue;
677
+ }
678
+ const result = validateFigmaFile(filePath, expectedCollection);
679
+ files.set(expectedCollection.input, {
680
+ valid: result.valid,
681
+ errors: result.errors,
682
+ warnings: result.warnings,
683
+ tokenCount: result.tokenCount,
684
+ fileCount: result.fileCount
685
+ });
686
+ errors.push(...result.errors);
687
+ warnings.push(...result.warnings);
688
+ totalTokenCount += result.tokenCount;
689
+ totalFileCount += result.fileCount;
690
+ for (const mode of result.detectedModes) {
691
+ detectedModes.add(mode);
692
+ }
693
+ }
694
+ try {
695
+ const existingFiles = readdirSync(targetDir).filter(
696
+ (f) => f.endsWith(".json") && statSync(join(targetDir, f)).isFile()
697
+ );
698
+ const expectedFiles = new Set(Object.values(collections).map((c) => c.input));
699
+ for (const file of existingFiles) {
700
+ if (!expectedFiles.has(file)) {
701
+ warnings.push({
702
+ path: join(targetDir, file),
703
+ message: `Unexpected file in exports directory: ${file}`,
704
+ severity: "warning"
705
+ });
706
+ }
707
+ }
708
+ } catch {
709
+ }
710
+ const effectiveErrors = strict ? [...errors, ...warnings] : errors;
711
+ return {
712
+ valid: effectiveErrors.length === 0,
713
+ errors,
714
+ warnings,
715
+ files,
716
+ detectedModes,
717
+ missingFiles,
718
+ tokenCount: totalTokenCount,
719
+ fileCount: totalFileCount
720
+ };
721
+ }
722
+ function detectModes(data, collectionName) {
723
+ if (!data || typeof data !== "object") {
724
+ return ["Base"];
725
+ }
726
+ if (collectionName) {
727
+ const collection = Object.entries(data).find(
728
+ ([key]) => key.toLowerCase() === collectionName.toLowerCase()
729
+ )?.[1];
730
+ if (collection?.modes) {
731
+ return Object.keys(collection.modes);
732
+ }
733
+ return ["Base"];
734
+ }
735
+ for (const [, value] of Object.entries(data)) {
736
+ if (typeof value === "object" && value !== null) {
737
+ const collection = value;
738
+ if (collection.modes) {
739
+ return Object.keys(collection.modes);
740
+ }
741
+ }
742
+ }
743
+ return ["Base"];
744
+ }
745
+ function validateFigmaCLI(exportsDir, options = {}) {
746
+ console.info(`
747
+ \u{1F50D} Validating Figma exports in: ${exportsDir}
748
+ `);
749
+ const result = validateFigmaExports({ ...options, exportsDir });
750
+ for (const [file, fileResult] of result.files) {
751
+ if (fileResult.errors.length === 0 && fileResult.warnings.length === 0) {
752
+ console.info(`\u2705 ${file}`);
753
+ } else {
754
+ console.info(`
755
+ \u{1F4C4} ${file}`);
756
+ for (const error of fileResult.errors) {
757
+ console.error(` \u274C ${error.path}: ${error.message}`);
758
+ }
759
+ for (const warning of fileResult.warnings) {
760
+ console.warn(` \u26A0\uFE0F ${warning.path}: ${warning.message}`);
761
+ }
762
+ }
763
+ }
764
+ if (result.missingFiles.length > 0) {
765
+ console.warn(`
766
+ \u26A0\uFE0F Missing files: ${result.missingFiles.join(", ")}`);
767
+ }
768
+ if (result.detectedModes.size > 0) {
769
+ console.info(`
770
+ \u{1F4CA} Detected modes: ${[...result.detectedModes].join(", ")}`);
771
+ }
772
+ console.info(`
773
+ ${"\u2500".repeat(50)}`);
774
+ if (result.valid) {
775
+ console.info("\u2705 All Figma exports are valid");
776
+ } else {
777
+ console.error(
778
+ `\u274C Validation failed: ${result.errors.length} error(s), ${result.warnings.length} warning(s)`
779
+ );
780
+ }
781
+ return result.valid;
782
+ }
783
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
784
+ function isSafeKey(key) {
785
+ return !UNSAFE_KEYS.has(key);
786
+ }
787
+ function getNestedValue(obj, ...keys) {
788
+ let current = obj;
789
+ for (const key of keys) {
790
+ if (current === null || current === void 0 || typeof current !== "object") {
791
+ return void 0;
792
+ }
793
+ if (!isSafeKey(key)) {
794
+ return void 0;
795
+ }
796
+ current = current[key];
797
+ }
798
+ return current;
799
+ }
800
+ function shouldKeepUnitless(_type, scopes = [], tokenPath = "") {
801
+ if (scopes.includes("FONT_WEIGHT")) {
802
+ return true;
803
+ }
804
+ if (scopes.includes("LINE_HEIGHT")) {
805
+ return true;
806
+ }
807
+ const pathLower = tokenPath.toLowerCase();
808
+ if (pathLower.includes("columns") || pathLower.includes("row-columns")) {
809
+ return true;
810
+ }
811
+ return false;
812
+ }
813
+ function shouldAddUnit(type, scopes = [], tokenPath = "") {
814
+ if (shouldKeepUnitless(type, scopes, tokenPath)) {
815
+ return false;
816
+ }
817
+ return type === "number";
818
+ }
819
+ function transformValue(value, type, options = {}) {
820
+ if (type === "string" && options.fontStack && typeof value === "string") {
821
+ const fontName = value;
822
+ const stack = options.fontStack;
823
+ if (stack.toLowerCase().includes(fontName.toLowerCase())) {
824
+ return stack;
825
+ }
826
+ return `${fontName}, ${stack}`;
827
+ }
828
+ if (typeof value === "number" && shouldKeepUnitless(type ?? "", options.scopes, options.tokenPath)) {
829
+ return value;
830
+ }
831
+ if (typeof value === "number" && shouldAddUnit(type ?? "", options.scopes, options.tokenPath)) {
832
+ if (options.isCircle) {
833
+ return "50%";
834
+ }
835
+ if (options.isPill) {
836
+ return "9999px";
837
+ }
838
+ return `${value}px`;
839
+ }
840
+ return value;
841
+ }
842
+ function transformType(figmaType) {
843
+ if (!figmaType) {
844
+ return void 0;
845
+ }
846
+ const typeMap = {
847
+ number: "dimension",
848
+ string: "fontFamily",
849
+ color: "color"
850
+ };
851
+ return typeMap[figmaType] ?? figmaType;
852
+ }
853
+ function transformToken(figmaToken, options = {}) {
854
+ if (!figmaToken || typeof figmaToken !== "object") {
855
+ return null;
856
+ }
857
+ const tokenObj = figmaToken;
858
+ if (!Object.hasOwn(tokenObj, "$value")) {
859
+ return null;
860
+ }
861
+ const transformOptions = {
862
+ ...options,
863
+ scopes: tokenObj["$scopes"] ?? []
864
+ };
865
+ const rawType = tokenObj["$type"];
866
+ const transformedType = transformType(rawType);
867
+ const token = {
868
+ $value: transformValue(tokenObj["$value"], rawType, transformOptions),
869
+ $type: transformedType ?? "string"
870
+ };
871
+ if (tokenObj["$description"] && typeof tokenObj["$description"] === "string") {
872
+ token.$description = tokenObj["$description"];
873
+ }
874
+ if (tokenObj["$extensions"] && typeof tokenObj["$extensions"] === "object") {
875
+ token.$extensions = tokenObj["$extensions"];
876
+ }
877
+ return token;
878
+ }
879
+ function transformTokenTree(obj, parentKey = "", options = {}) {
880
+ const result = {};
881
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
882
+ return result;
883
+ }
884
+ for (const [key, value] of Object.entries(obj)) {
885
+ const currentPath = parentKey ? `${parentKey}.${key}` : key;
886
+ const tokenOptions = {
887
+ ...options[key] ?? {},
888
+ tokenPath: currentPath
889
+ };
890
+ const transformed = transformToken(value, tokenOptions);
891
+ if (transformed) {
892
+ result[key] = transformed;
893
+ } else if (typeof value === "object" && value !== null) {
894
+ const nested = transformTokenTree(
895
+ value,
896
+ currentPath,
897
+ options[key] ?? {}
898
+ );
899
+ if (Object.keys(nested).length > 0) {
900
+ result[key] = nested;
901
+ }
902
+ }
903
+ }
904
+ return result;
905
+ }
906
+ function extractBrandColors(data, mode = "Light") {
907
+ const brandData = getNestedValue(data, "Foundation", "modes", mode, "colors", "brand");
908
+ if (!brandData) {
909
+ console.warn(`No brand colors found in ${mode} mode`);
910
+ return {};
911
+ }
912
+ return {
913
+ color: transformTokenTree(brandData)
914
+ };
915
+ }
916
+ function extractThemeColors(data, mode = "Light") {
917
+ const themeData = getNestedValue(data, "Foundation", "modes", mode, "colors", "theme");
918
+ if (!themeData) {
919
+ console.warn(`No theme colors found in ${mode} mode`);
920
+ return {};
921
+ }
922
+ return {
923
+ theme: transformTokenTree(themeData)
924
+ };
925
+ }
926
+ function extractSemanticColors(data, mode = "Light") {
927
+ const semantic = getNestedValue(data, "Foundation", "modes", mode, "semantic");
928
+ if (!semantic) {
929
+ console.warn(`No semantic colors found in ${mode} mode`);
930
+ return {};
931
+ }
932
+ return {
933
+ semantic: transformTokenTree(semantic)
934
+ };
935
+ }
936
+ function extractNeutralColors(data, mode = "Light") {
937
+ const neutral = getNestedValue(data, "Foundation", "modes", mode, "colors", "neutral");
938
+ if (!neutral) {
939
+ console.warn(`No neutral colors found in ${mode} mode`);
940
+ return {};
941
+ }
942
+ return {
943
+ neutral: transformTokenTree(neutral)
944
+ };
945
+ }
946
+ function extractBackgroundColors(data, mode = "Light") {
947
+ const background = getNestedValue(data, "Foundation", "modes", mode, "colors", "background");
948
+ if (!background) {
949
+ console.warn(`No background colors found in ${mode} mode`);
950
+ return {};
951
+ }
952
+ return {
953
+ background: transformTokenTree(background)
954
+ };
955
+ }
956
+ function extractOpacityColors(data, mode = "Light") {
957
+ const opacity = getNestedValue(data, "Foundation", "modes", mode, "colors", "opacity");
958
+ if (!opacity) {
959
+ console.warn(`No opacity tokens found in ${mode} mode`);
960
+ return {};
961
+ }
962
+ return {
963
+ opacity: transformTokenTree(opacity)
964
+ };
965
+ }
966
+ function extractBorderColors(data, mode = "Light") {
967
+ const borderColor = getNestedValue(data, "Foundation", "modes", mode, "borders", "color");
968
+ if (!borderColor) {
969
+ console.warn(`No border color tokens found in ${mode} mode`);
970
+ return {};
971
+ }
972
+ return {
973
+ border: {
974
+ color: transformTokenTree(borderColor)
975
+ }
976
+ };
977
+ }
978
+ function extractBorderWidths(data, mode = "Light") {
979
+ const borderWidth = getNestedValue(data, "Foundation", "modes", mode, "borders", "width");
980
+ if (!borderWidth) {
981
+ console.warn(`No border width tokens found in ${mode} mode`);
982
+ return {};
983
+ }
984
+ return {
985
+ border: {
986
+ width: transformTokenTree(borderWidth)
987
+ }
988
+ };
989
+ }
990
+ function extractTypography(data) {
991
+ const base = getNestedValue(data, "Typography", "modes", "Base");
992
+ if (!base) {
993
+ console.warn("No typography tokens found");
994
+ return {};
995
+ }
996
+ const options = {};
997
+ const fontFamily = base["fontFamily"];
998
+ if (fontFamily) {
999
+ for (const key of Object.keys(fontFamily)) {
1000
+ const fontToken = fontFamily[key];
1001
+ const extensions = fontToken?.["$extensions"];
1002
+ const fontStack = extensions?.["platform"]?.["fontStack"];
1003
+ if (fontStack) {
1004
+ options[key] = { fontStack };
1005
+ }
1006
+ }
1007
+ }
1008
+ return {
1009
+ typography: transformTokenTree(base, "", { fontFamily: options })
1010
+ };
1011
+ }
1012
+ function extractSpacing(data) {
1013
+ const base = getNestedValue(data, "Spacing", "modes", "Base", "spacing");
1014
+ if (!base) {
1015
+ console.warn("No spacing tokens found");
1016
+ return {};
1017
+ }
1018
+ return {
1019
+ spacing: transformTokenTree(base)
1020
+ };
1021
+ }
1022
+ function extractRadius(data) {
1023
+ const base = getNestedValue(data, "Radius", "modes", "Base", "border-radius") || getNestedValue(data, "Radius", "modes", "Base", "radius");
1024
+ if (!base) {
1025
+ console.warn("No radius tokens found");
1026
+ return {};
1027
+ }
1028
+ const options = {
1029
+ circle: { isCircle: true },
1030
+ pill: { isPill: true }
1031
+ };
1032
+ return {
1033
+ border: {
1034
+ radius: transformTokenTree(base, "", options)
1035
+ }
1036
+ };
1037
+ }
1038
+ function extractBreakpoints(data) {
1039
+ const breakpoints = getNestedValue(data, "Layout", "modes", "Base", "breakpoints");
1040
+ if (!breakpoints) {
1041
+ console.warn("No breakpoint tokens found");
1042
+ return {};
1043
+ }
1044
+ return {
1045
+ layout: {
1046
+ breakpoints: transformTokenTree(breakpoints)
1047
+ }
1048
+ };
1049
+ }
1050
+ function extractContainers(data) {
1051
+ const container = getNestedValue(data, "Layout", "modes", "Base", "container");
1052
+ if (!container) {
1053
+ console.warn("No container tokens found");
1054
+ return {};
1055
+ }
1056
+ return {
1057
+ layout: {
1058
+ container: transformTokenTree(container)
1059
+ }
1060
+ };
1061
+ }
1062
+ function extractGrid(data) {
1063
+ const base = getNestedValue(data, "Layout", "modes", "Base");
1064
+ const grid = base?.["grid"];
1065
+ const gutters = base?.["gutters"];
1066
+ if (!grid) {
1067
+ console.warn("No grid tokens found");
1068
+ return {};
1069
+ }
1070
+ const result = {
1071
+ layout: {
1072
+ grid: transformTokenTree(grid)
1073
+ }
1074
+ };
1075
+ if (gutters) {
1076
+ result.layout.gutters = transformTokenTree(gutters);
1077
+ }
1078
+ return result;
1079
+ }
1080
+ function extractShadows(data) {
1081
+ const base = getNestedValue(data, "Shadows", "modes", "Base", "shadows");
1082
+ if (!base) {
1083
+ console.warn("No shadow tokens found");
1084
+ return {};
1085
+ }
1086
+ const result = { shadow: {} };
1087
+ for (const [key, value] of Object.entries(base)) {
1088
+ const composite = value?.["composite"];
1089
+ if (composite) {
1090
+ const token = {
1091
+ $value: composite["$value"],
1092
+ $type: "shadow"
1093
+ };
1094
+ if (composite["$description"] && typeof composite["$description"] === "string") {
1095
+ token.$description = composite["$description"];
1096
+ }
1097
+ if (composite["$extensions"] && typeof composite["$extensions"] === "object") {
1098
+ token.$extensions = composite["$extensions"];
1099
+ }
1100
+ result.shadow[key] = token;
1101
+ }
1102
+ }
1103
+ return result;
1104
+ }
1105
+ var DEFAULT_COLLECTIONS2 = {
1106
+ foundation: {
1107
+ input: "foundation.json",
1108
+ modeAware: true,
1109
+ outputs: [
1110
+ { file: "collections/color/primitive.json", extractor: extractBrandColors },
1111
+ { file: "collections/color/neutral.json", extractor: extractNeutralColors },
1112
+ { file: "collections/color/background.json", extractor: extractBackgroundColors },
1113
+ { file: "collections/color/opacity.json", extractor: extractOpacityColors },
1114
+ { file: "collections/color/semantic.json", extractor: extractThemeColors },
1115
+ { file: "collections/color/component.json", extractor: extractSemanticColors },
1116
+ { file: "collections/border/color.json", extractor: extractBorderColors },
1117
+ { file: "collections/border/width.json", extractor: extractBorderWidths }
1118
+ ]
1119
+ },
1120
+ typography: {
1121
+ input: "typography.json",
1122
+ modeAware: false,
1123
+ outputs: [{ file: "collections/typography/base.json", extractor: extractTypography }]
1124
+ },
1125
+ spacing: {
1126
+ input: "spacing.json",
1127
+ modeAware: false,
1128
+ outputs: [{ file: "collections/spacing/base.json", extractor: extractSpacing }]
1129
+ },
1130
+ radius: {
1131
+ input: "radius.json",
1132
+ modeAware: false,
1133
+ outputs: [{ file: "collections/border/radius.json", extractor: extractRadius }]
1134
+ },
1135
+ layout: {
1136
+ input: "layout.json",
1137
+ modeAware: false,
1138
+ outputs: [
1139
+ { file: "collections/layout/breakpoints.json", extractor: extractBreakpoints },
1140
+ { file: "collections/layout/containers.json", extractor: extractContainers },
1141
+ { file: "collections/layout/grid.json", extractor: extractGrid }
1142
+ ]
1143
+ },
1144
+ shadows: {
1145
+ input: "shadows.json",
1146
+ modeAware: false,
1147
+ outputs: [{ file: "collections/shadow/base.json", extractor: extractShadows }]
1148
+ }
1149
+ };
1150
+ function ensureDir(filePath) {
1151
+ const dir = dirname(filePath);
1152
+ if (!existsSync(dir)) {
1153
+ mkdirSync(dir, { recursive: true });
1154
+ }
1155
+ }
1156
+ function detectModes2(data, collectionPath) {
1157
+ if (!data || typeof data !== "object") {
1158
+ return ["Base"];
1159
+ }
1160
+ if (collectionPath) {
1161
+ const collection = getNestedValue(data, collectionPath);
1162
+ const modes = collection?.["modes"];
1163
+ if (modes) {
1164
+ return Object.keys(modes);
1165
+ }
1166
+ return ["Base"];
1167
+ }
1168
+ for (const value of Object.values(data)) {
1169
+ if (typeof value === "object" && value !== null) {
1170
+ const collection = value;
1171
+ const modes = collection["modes"];
1172
+ if (modes) {
1173
+ return Object.keys(modes);
1174
+ }
1175
+ }
1176
+ }
1177
+ return ["Base"];
1178
+ }
1179
+ function getInputSource(collectionConfig, exportsDir, buildSource) {
1180
+ const themeFile = join(exportsDir, "theme.json");
1181
+ const collectionFile = join(exportsDir, collectionConfig.input);
1182
+ {
1183
+ if (existsSync(themeFile)) {
1184
+ return themeFile;
1185
+ }
1186
+ if (existsSync(collectionFile)) {
1187
+ console.warn(` \u26A0\uFE0F theme.json not found, using ${collectionConfig.input}`);
1188
+ return collectionFile;
1189
+ }
1190
+ return themeFile;
1191
+ }
1192
+ }
1193
+ function transformTokens(options) {
1194
+ const {
1195
+ sourceDir,
1196
+ collectionsDir,
1197
+ ignoreModes = [],
1198
+ defaultMode = "Light",
1199
+ dryRun = false,
1200
+ verbose = false
1201
+ } = options;
1202
+ const startTime = Date.now();
1203
+ const errors = [];
1204
+ const warnings = [];
1205
+ const filesWritten = [];
1206
+ const allModesDetected = /* @__PURE__ */ new Set();
1207
+ let tokensProcessed = 0;
1208
+ if (verbose) {
1209
+ console.info("\u{1F3A8} Transforming Figma tokens to Style Dictionary format...\n");
1210
+ console.info("\u{1F4CB} Configuration:");
1211
+ console.info(` Source directory: ${sourceDir}`);
1212
+ console.info(` Output directory: ${collectionsDir}`);
1213
+ console.info(` Default mode: ${defaultMode}`);
1214
+ if (ignoreModes.length > 0) {
1215
+ console.info(` Ignored modes: ${ignoreModes.join(", ")}`);
1216
+ }
1217
+ if (dryRun) {
1218
+ console.info(" DRY RUN - no files will be written");
1219
+ }
1220
+ console.info("");
1221
+ }
1222
+ const detectedModes = {};
1223
+ const themeFile = join(sourceDir, "theme.json");
1224
+ if (existsSync(themeFile)) {
1225
+ try {
1226
+ const content = readFileSync(themeFile, "utf-8");
1227
+ const themeData = JSON.parse(content);
1228
+ for (const [name, collectionConfig] of Object.entries(DEFAULT_COLLECTIONS2)) {
1229
+ const typedConfig = collectionConfig;
1230
+ if (typedConfig.modeAware) {
1231
+ const collectionPath = name.charAt(0).toUpperCase() + name.slice(1);
1232
+ const modes = detectModes2(themeData, collectionPath);
1233
+ if (modes.length > 0) {
1234
+ detectedModes[collectionPath] = modes;
1235
+ for (const mode of modes) {
1236
+ allModesDetected.add(mode);
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ } catch (error) {
1242
+ warnings.push(
1243
+ `Failed to read theme.json for mode detection: ${error instanceof Error ? error.message : "Unknown error"}`
1244
+ );
1245
+ }
1246
+ }
1247
+ for (const [collectionName, collectionConfig] of Object.entries(DEFAULT_COLLECTIONS2)) {
1248
+ const typedConfig = collectionConfig;
1249
+ if (verbose) {
1250
+ console.info(`Processing ${collectionName} collection...`);
1251
+ }
1252
+ const inputPath = getInputSource(typedConfig, sourceDir);
1253
+ if (!existsSync(inputPath)) {
1254
+ warnings.push(`Input file not found: ${inputPath}`);
1255
+ continue;
1256
+ }
1257
+ let data;
1258
+ try {
1259
+ const content = readFileSync(inputPath, "utf-8");
1260
+ data = JSON.parse(content);
1261
+ } catch (error) {
1262
+ errors.push(
1263
+ `Failed to read ${inputPath}: ${error instanceof Error ? error.message : "Unknown error"}`
1264
+ );
1265
+ continue;
1266
+ }
1267
+ let modesToProcess = [defaultMode];
1268
+ if (typedConfig.modeAware) {
1269
+ const collectionPath = collectionName.charAt(0).toUpperCase() + collectionName.slice(1);
1270
+ if (detectedModes[collectionPath]) {
1271
+ modesToProcess = detectedModes[collectionPath].filter((m) => !ignoreModes.includes(m));
1272
+ } else {
1273
+ const modes = detectModes2(data, collectionPath);
1274
+ if (modes.length > 1 || !modes.includes("Base")) {
1275
+ modesToProcess = modes.filter((m) => !ignoreModes.includes(m));
1276
+ }
1277
+ }
1278
+ }
1279
+ for (const output of typedConfig.outputs) {
1280
+ for (const mode of modesToProcess) {
1281
+ let outputFile = output.file;
1282
+ if (typedConfig.modeAware && modesToProcess.length > 1) {
1283
+ const ext = extname(output.file);
1284
+ const base = basename(output.file, ext);
1285
+ const dir = dirname(output.file);
1286
+ if (mode !== defaultMode) {
1287
+ outputFile = join(dir, `${base}-${mode.toLowerCase()}${ext}`);
1288
+ }
1289
+ }
1290
+ const outputPath = join(collectionsDir, outputFile);
1291
+ try {
1292
+ const tokens = typedConfig.modeAware ? output.extractor(data, mode) : output.extractor(data);
1293
+ const tokenCount = Object.keys(tokens).length;
1294
+ if (tokenCount === 0) {
1295
+ warnings.push(`No tokens extracted for ${outputFile} (${mode} mode)`);
1296
+ continue;
1297
+ }
1298
+ tokensProcessed += tokenCount;
1299
+ if (!dryRun) {
1300
+ ensureDir(outputPath);
1301
+ writeFileSync(outputPath, `${JSON.stringify(tokens, null, 2)}
1302
+ `, "utf-8");
1303
+ }
1304
+ filesWritten.push(outputFile);
1305
+ if (verbose) {
1306
+ const dryRunLabel = dryRun ? " (dry run)" : "";
1307
+ console.info(
1308
+ ` \u2705 Created ${outputFile}${modesToProcess.length > 1 ? ` (${mode})` : ""}${dryRunLabel}`
1309
+ );
1310
+ }
1311
+ } catch (error) {
1312
+ errors.push(
1313
+ `Error processing ${outputFile}: ${error instanceof Error ? error.message : "Unknown error"}`
1314
+ );
1315
+ }
1316
+ }
1317
+ }
1318
+ }
1319
+ const duration = Date.now() - startTime;
1320
+ if (verbose) {
1321
+ console.info("\n\u2728 Transformation complete!");
1322
+ console.info(`
1323
+ \u{1F4C1} Output directory: ${collectionsDir}`);
1324
+ console.info(`\u23F1\uFE0F Duration: ${duration}ms`);
1325
+ }
1326
+ return {
1327
+ success: errors.length === 0,
1328
+ filesWritten,
1329
+ tokensProcessed,
1330
+ modesDetected: Array.from(allModesDetected),
1331
+ errors,
1332
+ warnings,
1333
+ duration
1334
+ };
1335
+ }
1336
+ function transformTokensCLI(sourceDir, collectionsDir, options = {}) {
1337
+ const result = transformTokens({
1338
+ sourceDir,
1339
+ collectionsDir,
1340
+ verbose: true,
1341
+ ...options
1342
+ });
1343
+ for (const error of result.errors) {
1344
+ console.error(`\u274C ${error}`);
1345
+ }
1346
+ for (const warning of result.warnings) {
1347
+ console.warn(`\u26A0\uFE0F ${warning}`);
1348
+ }
1349
+ return result.success;
1350
+ }
1351
+ var DEFAULT_SOURCE_RELATIVE = "dist/js/tokens.js";
1352
+ var DEFAULT_TARGET_RELATIVE = "src/tokens-flat.ts";
1353
+ var TS_HEADER = `/**
1354
+ * Do not edit directly, this file was auto-generated.
1355
+ * Generated from Style Dictionary output (dist/js/tokens.js)
1356
+ *
1357
+ * To update: run \`pnpm tokens:build\` which will:
1358
+ * 1. Transform Figma tokens
1359
+ * 2. Build with Style Dictionary
1360
+ * 3. Sync this file automatically
1361
+ */
1362
+
1363
+ `;
1364
+ var EXPECTED_FONTS = ["Inter", "Roboto Mono"];
1365
+ function verifyFontFamilies(content) {
1366
+ const found = [];
1367
+ const missing = [];
1368
+ for (const font of EXPECTED_FONTS) {
1369
+ const pattern = `'${font},`;
1370
+ if (content.includes(pattern) || content.includes(`"${font},`)) {
1371
+ found.push(font);
1372
+ } else {
1373
+ missing.push(font);
1374
+ }
1375
+ }
1376
+ return { found, missing };
1377
+ }
1378
+ function countTokenExports(content) {
1379
+ const exportPattern = /export\s+const\s+\w+/g;
1380
+ const matches = content.match(exportPattern);
1381
+ return matches ? matches.length : 0;
1382
+ }
1383
+ function getDefaultSyncPaths(tokensDir) {
1384
+ return {
1385
+ sourceFile: join(tokensDir, DEFAULT_SOURCE_RELATIVE),
1386
+ targetFile: join(tokensDir, DEFAULT_TARGET_RELATIVE)
1387
+ };
1388
+ }
1389
+ function syncTokens(options) {
1390
+ const { sourceFile, targetFile, dryRun = false, verbose = false } = options;
1391
+ const errors = [];
1392
+ if (verbose) {
1393
+ console.info("\u{1F504} Syncing tokens from Style Dictionary output...");
1394
+ console.info(` Source: ${sourceFile}`);
1395
+ console.info(` Target: ${targetFile}`);
1396
+ if (dryRun) {
1397
+ console.info(" DRY RUN - no files will be written");
1398
+ }
1399
+ }
1400
+ if (!existsSync(sourceFile)) {
1401
+ const error = `Source file not found: ${sourceFile}`;
1402
+ if (verbose) {
1403
+ console.error(`\u274C ${error}`);
1404
+ }
1405
+ return {
1406
+ success: false,
1407
+ tokensCount: 0,
1408
+ changed: false,
1409
+ errors: [error]
1410
+ };
1411
+ }
1412
+ let sourceContent;
1413
+ try {
1414
+ sourceContent = readFileSync(sourceFile, "utf-8");
1415
+ } catch (err) {
1416
+ const error = `Failed to read source file: ${err instanceof Error ? err.message : "Unknown error"}`;
1417
+ if (verbose) {
1418
+ console.error(`\u274C ${error}`);
1419
+ }
1420
+ return {
1421
+ success: false,
1422
+ tokensCount: 0,
1423
+ changed: false,
1424
+ errors: [error]
1425
+ };
1426
+ }
1427
+ const tsContent = `${TS_HEADER}${sourceContent}`;
1428
+ const fontFamilies = verifyFontFamilies(sourceContent);
1429
+ if (fontFamilies.missing.length > 0) {
1430
+ for (const font of fontFamilies.missing) {
1431
+ errors.push(`Font family may be missing: ${font}`);
1432
+ }
1433
+ }
1434
+ const tokensCount = countTokenExports(sourceContent);
1435
+ let changed = true;
1436
+ if (existsSync(targetFile)) {
1437
+ try {
1438
+ const existingContent = readFileSync(targetFile, "utf-8");
1439
+ if (existingContent === tsContent) {
1440
+ changed = false;
1441
+ if (verbose) {
1442
+ console.info("\u2705 Target file is already up to date");
1443
+ }
1444
+ }
1445
+ } catch {
1446
+ }
1447
+ }
1448
+ if (changed && !dryRun) {
1449
+ try {
1450
+ const targetDir = dirname(targetFile);
1451
+ if (!existsSync(targetDir)) {
1452
+ mkdirSync(targetDir, { recursive: true });
1453
+ }
1454
+ writeFileSync(targetFile, tsContent, "utf-8");
1455
+ } catch (err) {
1456
+ const error = `Failed to write target file: ${err instanceof Error ? err.message : "Unknown error"}`;
1457
+ if (verbose) {
1458
+ console.error(`\u274C ${error}`);
1459
+ }
1460
+ return {
1461
+ success: false,
1462
+ tokensCount,
1463
+ changed: false,
1464
+ errors: [error]
1465
+ };
1466
+ }
1467
+ }
1468
+ if (verbose) {
1469
+ if (changed) {
1470
+ console.info("\u2705 Successfully synced tokens");
1471
+ }
1472
+ console.info(` Tokens exported: ${tokensCount}`);
1473
+ if (fontFamilies.found.length > 0) {
1474
+ console.info(` Font families found: ${fontFamilies.found.join(", ")}`);
1475
+ }
1476
+ if (fontFamilies.missing.length > 0) {
1477
+ console.warn(` \u26A0\uFE0F Missing fonts: ${fontFamilies.missing.join(", ")}`);
1478
+ }
1479
+ }
1480
+ return {
1481
+ success: errors.length === 0 || errors.every((e) => e.includes("Font family")),
1482
+ tokensCount,
1483
+ changed,
1484
+ errors: errors.length > 0 ? errors : void 0
1485
+ };
1486
+ }
1487
+ function syncTokensCLI(tokensDir) {
1488
+ const paths = getDefaultSyncPaths(tokensDir);
1489
+ const result = syncTokens({
1490
+ ...paths,
1491
+ verbose: true
1492
+ });
1493
+ if (result.errors) {
1494
+ for (const error of result.errors) {
1495
+ if (error.includes("Font family")) {
1496
+ console.warn(`\u26A0\uFE0F ${error}`);
1497
+ } else {
1498
+ console.error(`\u274C ${error}`);
1499
+ }
1500
+ }
1501
+ }
1502
+ return result.success;
1503
+ }
1504
+ var DEFAULT_CSS_DIR = "dist/css";
1505
+ var DEFAULT_FILES = ["dsai-theme-bs.css", "dsai-theme-bs.min.css"];
1506
+ var DEFAULT_TRANSFORMATIONS = [
1507
+ {
1508
+ description: "Theme attribute",
1509
+ from: /data-bs-theme/g,
1510
+ to: "data-dsai-theme"
1511
+ }
1512
+ ];
1513
+ function applyReplacements(content, replacements, verbose) {
1514
+ let result = content;
1515
+ let totalCount = 0;
1516
+ for (const rule of replacements) {
1517
+ let matchCount = 0;
1518
+ if (typeof rule.from === "string") {
1519
+ let pos = 0;
1520
+ const searchStr = rule.from;
1521
+ while (pos < result.length) {
1522
+ const idx = result.indexOf(searchStr, pos);
1523
+ if (idx === -1) {
1524
+ break;
1525
+ }
1526
+ matchCount++;
1527
+ pos = idx + 1;
1528
+ }
1529
+ if (matchCount > 0) {
1530
+ result = result.split(rule.from).join(rule.to);
1531
+ }
1532
+ } else {
1533
+ const matches = result.match(rule.from);
1534
+ matchCount = matches ? matches.length : 0;
1535
+ if (matchCount > 0) {
1536
+ result = result.replace(rule.from, rule.to);
1537
+ }
1538
+ }
1539
+ if (matchCount > 0) {
1540
+ totalCount += matchCount;
1541
+ if (verbose) {
1542
+ const desc = rule.description ?? (typeof rule.from === "string" ? rule.from : String(rule.from));
1543
+ console.info(` \u2713 ${desc}: ${matchCount} replacement(s)`);
1544
+ }
1545
+ }
1546
+ }
1547
+ return { result, count: totalCount };
1548
+ }
1549
+ function postprocessCss(options) {
1550
+ const {
1551
+ inputFile,
1552
+ outputFile = inputFile,
1553
+ replacements = DEFAULT_TRANSFORMATIONS,
1554
+ dryRun = false,
1555
+ verbose = false
1556
+ } = options;
1557
+ if (!existsSync(inputFile)) {
1558
+ return {
1559
+ success: false,
1560
+ replacementsMade: 0,
1561
+ outputFile,
1562
+ errors: [`File not found: ${inputFile}`]
1563
+ };
1564
+ }
1565
+ let content;
1566
+ try {
1567
+ content = readFileSync(inputFile, "utf-8");
1568
+ } catch (err) {
1569
+ return {
1570
+ success: false,
1571
+ replacementsMade: 0,
1572
+ outputFile,
1573
+ errors: [`Failed to read file: ${err instanceof Error ? err.message : "Unknown error"}`]
1574
+ };
1575
+ }
1576
+ const { result, count } = applyReplacements(content, replacements, verbose);
1577
+ if (count > 0 && !dryRun) {
1578
+ try {
1579
+ writeFileSync(outputFile, result, "utf-8");
1580
+ } catch (err) {
1581
+ return {
1582
+ success: false,
1583
+ replacementsMade: count,
1584
+ outputFile,
1585
+ errors: [`Failed to write file: ${err instanceof Error ? err.message : "Unknown error"}`]
1586
+ };
1587
+ }
1588
+ }
1589
+ return {
1590
+ success: true,
1591
+ replacementsMade: count,
1592
+ outputFile
1593
+ };
1594
+ }
1595
+ function postprocessCssFiles(options) {
1596
+ const {
1597
+ cssDir,
1598
+ files = DEFAULT_FILES,
1599
+ replacements = DEFAULT_TRANSFORMATIONS,
1600
+ dryRun = false,
1601
+ verbose = false
1602
+ } = options;
1603
+ let filesModified = 0;
1604
+ let totalReplacements = 0;
1605
+ const errors = [];
1606
+ if (verbose) {
1607
+ console.info("\u{1F527} Post-processing CSS files...\n");
1608
+ }
1609
+ for (const fileName of files) {
1610
+ const filePath = join(cssDir, fileName);
1611
+ if (verbose) {
1612
+ console.info(`\u{1F4C4} ${fileName}`);
1613
+ }
1614
+ const result = postprocessCss({
1615
+ inputFile: filePath,
1616
+ replacements,
1617
+ dryRun,
1618
+ verbose
1619
+ });
1620
+ if (result.success) {
1621
+ if (result.replacementsMade > 0) {
1622
+ filesModified++;
1623
+ totalReplacements += result.replacementsMade;
1624
+ } else if (verbose) {
1625
+ console.info(" (no changes needed)");
1626
+ }
1627
+ } else if (result.errors) {
1628
+ errors.push(...result.errors);
1629
+ if (verbose) {
1630
+ for (const err of result.errors) {
1631
+ console.warn(` \u26A0\uFE0F ${err}`);
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+ if (verbose) {
1637
+ console.info(`
1638
+ \u2705 Post-processing complete. ${filesModified} file(s) modified.`);
1639
+ if (totalReplacements > 0) {
1640
+ console.info(` Total replacements: ${totalReplacements}`);
1641
+ }
1642
+ }
1643
+ return {
1644
+ success: errors.length === 0,
1645
+ filesModified,
1646
+ totalReplacements,
1647
+ errors
1648
+ };
1649
+ }
1650
+ function postprocessCLI(tokensDir) {
1651
+ const cssDir = join(tokensDir, DEFAULT_CSS_DIR);
1652
+ const result = postprocessCssFiles({
1653
+ cssDir,
1654
+ verbose: true
1655
+ });
1656
+ return result.success;
1657
+ }
1658
+ function getDefaultCssDir(tokensDir) {
1659
+ return join(tokensDir, DEFAULT_CSS_DIR);
1660
+ }
1661
+ function getDefaultFiles() {
1662
+ return [...DEFAULT_FILES];
1663
+ }
1664
+ function getDefaultTransformations() {
1665
+ return [...DEFAULT_TRANSFORMATIONS];
1666
+ }
1667
+
1668
+ // src/tokens/build.ts
1669
+ var SASS_FLAGS = [
1670
+ "--quiet-deps",
1671
+ "--silence-deprecation=import",
1672
+ "--silence-deprecation=global-builtin",
1673
+ "--silence-deprecation=color-functions"
1674
+ ].join(" ");
1675
+ var SASS_FLAGS_MINIMAL = ["--quiet-deps", "--silence-deprecation=import"].join(" ");
1676
+ var DEFAULT_PIPELINE_STEPS = [
1677
+ "validate",
1678
+ "transform",
1679
+ "style-dictionary",
1680
+ "sync",
1681
+ "sass-theme",
1682
+ "sass-theme-minified",
1683
+ "postprocess",
1684
+ "sass-utilities",
1685
+ "sass-utilities-minified",
1686
+ "bundle"
1687
+ ];
1688
+ var DEFAULT_PIPELINE_PATHS = {
1689
+ syncSource: "dist/js/tokens.js",
1690
+ syncTarget: "src/tokens-flat.ts",
1691
+ sassThemeInput: "src/scss/dsai-theme-bs.scss",
1692
+ sassThemeOutput: "dist/css/dsai-theme-bs.css",
1693
+ sassThemeMinifiedOutput: "dist/css/dsai-theme-bs.min.css",
1694
+ sassUtilitiesInput: "src/scss/dsai-utilities.scss",
1695
+ sassUtilitiesOutput: "dist/css/dsai.css",
1696
+ sassUtilitiesMinifiedOutput: "dist/css/dsai.min.css"
1697
+ };
1698
+ function runStep(step, index, total, verbose) {
1699
+ const stepNum = `[${index + 1}/${total}]`;
1700
+ if (step.skip) {
1701
+ if (verbose) {
1702
+ console.info(`${stepNum} \u23ED\uFE0F ${step.name} (skipped)`);
1703
+ }
1704
+ return true;
1705
+ }
1706
+ if (verbose) {
1707
+ console.info(`
1708
+ ${stepNum} \u{1F527} ${step.name}`);
1709
+ }
1710
+ if (step.fn) {
1711
+ try {
1712
+ const result = step.fn();
1713
+ if (result instanceof Promise) {
1714
+ console.warn(` \u26A0\uFE0F Async step ${step.name} - running synchronously`);
1715
+ }
1716
+ if (result === false) {
1717
+ console.error(` \u274C Failed: Step returned false`);
1718
+ return false;
1719
+ }
1720
+ if (verbose) {
1721
+ console.info(" \u2705 Done");
1722
+ }
1723
+ return true;
1724
+ } catch (error) {
1725
+ console.error(` \u274C Failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1726
+ return false;
1727
+ }
1728
+ }
1729
+ if (step.command) {
1730
+ if (verbose) {
1731
+ const shortCmd = step.command.split(" ").slice(0, 4).join(" ");
1732
+ console.info(` $ ${shortCmd}...`);
1733
+ }
1734
+ try {
1735
+ execSync(step.command, {
1736
+ cwd: step.cwd ?? process.cwd(),
1737
+ stdio: verbose ? "inherit" : "pipe",
1738
+ env: { ...process.env, FORCE_COLOR: "1" }
1739
+ });
1740
+ if (verbose) {
1741
+ console.info(" \u2705 Done");
1742
+ }
1743
+ return true;
1744
+ } catch (error) {
1745
+ console.error(` \u274C Failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1746
+ return false;
1747
+ }
1748
+ }
1749
+ console.warn(` \u26A0\uFE0F Step ${step.name} has no command or function`);
1750
+ return true;
1751
+ }
1752
+ function getPipelinePaths(customPaths) {
1753
+ return {
1754
+ ...DEFAULT_PIPELINE_PATHS,
1755
+ ...customPaths
1756
+ };
1757
+ }
1758
+ var STEP_DISPLAY_NAMES = {
1759
+ validate: "Validate Tokens",
1760
+ transform: "Transform Figma Tokens",
1761
+ "style-dictionary": "Build Style Dictionary",
1762
+ sync: "Sync tokens-flat.ts",
1763
+ "sass-theme": "Compile Bootstrap Theme (unminified)",
1764
+ "sass-theme-minified": "Compile Bootstrap Theme (minified)",
1765
+ postprocess: "Post-process Theme CSS",
1766
+ "sass-utilities": "Compile DSAi Utilities (unminified)",
1767
+ "sass-utilities-minified": "Compile DSAi Utilities (minified)",
1768
+ bundle: "Bundle with tsup"
1769
+ };
1770
+ function createStepFromName(stepName, tokensPackageDir, figmaExportsDir, tokensDir, paths, sdConfigFile) {
1771
+ const displayName = STEP_DISPLAY_NAMES[stepName];
1772
+ switch (stepName) {
1773
+ case "validate":
1774
+ return {
1775
+ name: displayName,
1776
+ command: "dsai tokens validate",
1777
+ cwd: tokensPackageDir
1778
+ };
1779
+ case "transform":
1780
+ return {
1781
+ name: displayName,
1782
+ fn: () => {
1783
+ const result = transformTokens({
1784
+ sourceDir: figmaExportsDir,
1785
+ collectionsDir: tokensDir,
1786
+ verbose: true
1787
+ });
1788
+ if (!result.success) {
1789
+ for (const error of result.errors) {
1790
+ console.error(`\u274C ${error}`);
1791
+ }
1792
+ }
1793
+ return result.success;
1794
+ }
1795
+ };
1796
+ case "style-dictionary":
1797
+ return {
1798
+ name: displayName,
1799
+ command: `style-dictionary build --config ${sdConfigFile}`,
1800
+ cwd: tokensPackageDir
1801
+ };
1802
+ case "sync":
1803
+ return {
1804
+ name: displayName,
1805
+ fn: () => syncTokensCLI(tokensPackageDir)
1806
+ };
1807
+ case "sass-theme":
1808
+ return {
1809
+ name: displayName,
1810
+ command: `sass ${SASS_FLAGS} ${paths.sassThemeInput} ${paths.sassThemeOutput}`,
1811
+ cwd: tokensPackageDir
1812
+ };
1813
+ case "sass-theme-minified":
1814
+ return {
1815
+ name: displayName,
1816
+ command: `sass ${SASS_FLAGS} ${paths.sassThemeInput} ${paths.sassThemeMinifiedOutput} --style=compressed`,
1817
+ cwd: tokensPackageDir
1818
+ };
1819
+ case "postprocess":
1820
+ return {
1821
+ name: displayName,
1822
+ fn: () => postprocessCLI(tokensPackageDir)
1823
+ };
1824
+ case "sass-utilities":
1825
+ return {
1826
+ name: displayName,
1827
+ command: `sass ${SASS_FLAGS_MINIMAL} ${paths.sassUtilitiesInput} ${paths.sassUtilitiesOutput}`,
1828
+ cwd: tokensPackageDir
1829
+ };
1830
+ case "sass-utilities-minified":
1831
+ return {
1832
+ name: displayName,
1833
+ command: `sass ${SASS_FLAGS_MINIMAL} ${paths.sassUtilitiesInput} ${paths.sassUtilitiesMinifiedOutput} --style=compressed`,
1834
+ cwd: tokensPackageDir
1835
+ };
1836
+ case "bundle":
1837
+ return {
1838
+ name: displayName,
1839
+ command: "tsup",
1840
+ cwd: tokensPackageDir
1841
+ };
1842
+ default:
1843
+ return {
1844
+ name: `Unknown step: ${stepName}`,
1845
+ fn: () => {
1846
+ console.warn(`\u26A0\uFE0F Unknown pipeline step: ${stepName}`);
1847
+ return true;
1848
+ }
1849
+ };
1850
+ }
1851
+ }
1852
+ function createBuildSteps(tokensDir, _toolsDir, options, pipeline) {
1853
+ const { skipValidate, skipTransform, onlyTheme } = options;
1854
+ const tokensPackageDir = tokensDir.endsWith("/collections") ? dirname(tokensDir) : dirname(tokensDir);
1855
+ const figmaExportsDir = `${tokensPackageDir}/figma-exports`;
1856
+ const pipelineSteps = pipeline?.steps ?? DEFAULT_PIPELINE_STEPS;
1857
+ const paths = getPipelinePaths(pipeline?.paths);
1858
+ const sdConfigFile = pipeline?.styleDictionaryConfig ?? "sd.config.mjs";
1859
+ const steps = [];
1860
+ for (const stepName of pipelineSteps) {
1861
+ const step = createStepFromName(
1862
+ stepName,
1863
+ tokensPackageDir,
1864
+ figmaExportsDir,
1865
+ tokensDir,
1866
+ paths,
1867
+ sdConfigFile
1868
+ );
1869
+ if (stepName === "validate" && skipValidate) {
1870
+ step.skip = true;
1871
+ }
1872
+ if (stepName === "transform" && (skipTransform || onlyTheme)) {
1873
+ step.skip = true;
1874
+ }
1875
+ if (onlyTheme && !["sass-theme", "sass-theme-minified", "postprocess"].includes(stepName)) {
1876
+ if (!["validate"].includes(stepName)) {
1877
+ step.skip = true;
1878
+ }
1879
+ }
1880
+ steps.push(step);
1881
+ }
1882
+ return steps;
1883
+ }
1884
+ function buildTokens(tokensDir, toolsDir, options = {}) {
1885
+ const { skipValidate, onlyTheme, verbose = true, quiet = false } = options;
1886
+ const startTime = Date.now();
1887
+ const stepsCompleted = [];
1888
+ const stepsFailed = [];
1889
+ const errors = [];
1890
+ const warnings = [];
1891
+ try {
1892
+ if (!existsSync(tokensDir)) {
1893
+ return {
1894
+ success: false,
1895
+ stepsCompleted,
1896
+ stepsFailed: ["Directory Check"],
1897
+ duration: Date.now() - startTime,
1898
+ errors: [`Tokens directory not found: ${tokensDir}`],
1899
+ warnings
1900
+ };
1901
+ }
1902
+ } catch {
1903
+ return {
1904
+ success: false,
1905
+ stepsCompleted,
1906
+ stepsFailed: ["Directory Check"],
1907
+ duration: Date.now() - startTime,
1908
+ errors: [`Failed to check tokens directory: ${tokensDir}`],
1909
+ warnings
1910
+ };
1911
+ }
1912
+ try {
1913
+ if (!existsSync(toolsDir)) {
1914
+ return {
1915
+ success: false,
1916
+ stepsCompleted,
1917
+ stepsFailed: ["Directory Check"],
1918
+ duration: Date.now() - startTime,
1919
+ errors: [`Tools directory not found: ${toolsDir}`],
1920
+ warnings
1921
+ };
1922
+ }
1923
+ } catch {
1924
+ return {
1925
+ success: false,
1926
+ stepsCompleted,
1927
+ stepsFailed: ["Directory Check"],
1928
+ duration: Date.now() - startTime,
1929
+ errors: [`Failed to check tools directory: ${toolsDir}`],
1930
+ warnings
1931
+ };
1932
+ }
1933
+ if (verbose && !quiet) {
1934
+ console.info("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
1935
+ console.info("\u2551 DSAi Tokens - Complete Build \u2551");
1936
+ console.info("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
1937
+ if (skipValidate) {
1938
+ console.info("\u26A0\uFE0F Skipping validation (--skip-validate)");
1939
+ }
1940
+ if (onlyTheme) {
1941
+ console.info("\u26A0\uFE0F Building only theme CSS (--only-theme)");
1942
+ }
1943
+ }
1944
+ const steps = createBuildSteps(tokensDir, toolsDir, options, options.pipeline);
1945
+ for (const step of steps) {
1946
+ const stepIndex = steps.indexOf(step);
1947
+ const success = runStep(step, stepIndex, steps.length, verbose && !quiet);
1948
+ if (success) {
1949
+ if (!step.skip) {
1950
+ stepsCompleted.push(step.name);
1951
+ }
1952
+ } else {
1953
+ stepsFailed.push(step.name);
1954
+ errors.push(`Build failed at step: ${step.name}`);
1955
+ const failDuration = Date.now() - startTime;
1956
+ if (verbose && !quiet) {
1957
+ console.error(`
1958
+ \u{1F4A5} Build failed at step: ${step.name}`);
1959
+ }
1960
+ return {
1961
+ success: false,
1962
+ stepsCompleted,
1963
+ stepsFailed,
1964
+ duration: failDuration,
1965
+ errors,
1966
+ warnings
1967
+ };
1968
+ }
1969
+ }
1970
+ const duration = Date.now() - startTime;
1971
+ const durationSec = (duration / 1e3).toFixed(2);
1972
+ if (verbose && !quiet) {
1973
+ console.info("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
1974
+ console.info("\u2551 \u2705 Build Complete \u2551");
1975
+ console.info(
1976
+ `\u2551 \u{1F4CA} ${stepsCompleted.length} steps passed in ${durationSec}s \u2551`
1977
+ );
1978
+ console.info("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
1979
+ }
1980
+ return {
1981
+ success: true,
1982
+ stepsCompleted,
1983
+ stepsFailed,
1984
+ duration,
1985
+ errors,
1986
+ warnings
1987
+ };
1988
+ }
1989
+ function buildTokensCLI(tokensDir, toolsDir, args = []) {
1990
+ const skipValidate = args.includes("--skip-validate");
1991
+ const skipTransform = args.includes("--skip-transform");
1992
+ const onlyTheme = args.includes("--only-theme");
1993
+ const quiet = args.includes("--quiet") || args.includes("-q");
1994
+ const result = buildTokens(tokensDir, toolsDir, {
1995
+ skipValidate,
1996
+ skipTransform,
1997
+ onlyTheme,
1998
+ verbose: !quiet,
1999
+ quiet
2000
+ });
2001
+ return result.success;
2002
+ }
2003
+ function runBuildCLI(tokensDir, toolsDir) {
2004
+ const args = process.argv.slice(2);
2005
+ const success = buildTokensCLI(tokensDir, toolsDir, args);
2006
+ process.exit(success ? 0 : 1);
2007
+ }
2008
+ var DEFAULT_CLEAN_DIRECTORIES = ["dist"];
2009
+ var ALWAYS_PRESERVE = [".gitkeep", ".gitignore"];
2010
+ var PROTECTED_DIRECTORIES = [
2011
+ "src",
2012
+ "node_modules",
2013
+ ".git",
2014
+ ".github",
2015
+ "test",
2016
+ "tests",
2017
+ "__tests__",
2018
+ "docs",
2019
+ "config"
2020
+ ];
2021
+ function validateCleanTarget(dirPath, baseDir) {
2022
+ const resolvedDir = resolve(dirPath);
2023
+ const resolvedBase = resolve(baseDir);
2024
+ if (!resolvedDir.startsWith(resolvedBase)) {
2025
+ return {
2026
+ valid: false,
2027
+ error: `Directory "${dirPath}" is outside base directory "${baseDir}"`
2028
+ };
2029
+ }
2030
+ const dirName = basename(resolvedDir);
2031
+ if (PROTECTED_DIRECTORIES.includes(dirName)) {
2032
+ return {
2033
+ valid: false,
2034
+ error: `Directory "${dirName}" is protected and cannot be cleaned`
2035
+ };
2036
+ }
2037
+ if (resolvedDir === resolvedBase) {
2038
+ return {
2039
+ valid: false,
2040
+ error: "Cannot clean the base directory itself"
2041
+ };
2042
+ }
2043
+ return { valid: true };
2044
+ }
2045
+ function escapeRegexChars(str) {
2046
+ return str.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2047
+ }
2048
+ function globToSafePattern(pattern) {
2049
+ const escaped = escapeRegexChars(pattern);
2050
+ return escaped.replace(/\*/g, ".*?").replace(/\?/g, ".");
2051
+ }
2052
+ function matchGlobPattern(fileName, pattern) {
2053
+ if (!pattern.includes("*") && !pattern.includes("?")) {
2054
+ return fileName === pattern;
2055
+ }
2056
+ if (pattern.startsWith("*.") && !pattern.slice(2).includes("*") && !pattern.includes("?")) {
2057
+ const extension = pattern.slice(1);
2058
+ return fileName.endsWith(extension);
2059
+ }
2060
+ if (pattern.endsWith("*") && !pattern.slice(0, -1).includes("*") && !pattern.includes("?")) {
2061
+ const prefix = pattern.slice(0, -1);
2062
+ return fileName.startsWith(prefix);
2063
+ }
2064
+ const safePattern = globToSafePattern(pattern);
2065
+ const regex = new RegExp(`^${safePattern}$`);
2066
+ return regex.test(fileName);
2067
+ }
2068
+ function shouldPreserve(fileName, preservePatterns) {
2069
+ const allPatterns = [...ALWAYS_PRESERVE, ...preservePatterns];
2070
+ return allPatterns.some((pattern) => matchGlobPattern(fileName, pattern));
2071
+ }
2072
+ function countContents(dirPath) {
2073
+ if (!existsSync(dirPath)) {
2074
+ return { files: 0, dirs: 0 };
2075
+ }
2076
+ let files = 0;
2077
+ let dirs = 0;
2078
+ const entries = readdirSync(dirPath, { withFileTypes: true });
2079
+ for (const entry of entries) {
2080
+ if (entry.isDirectory()) {
2081
+ dirs++;
2082
+ const subCounts = countContents(join(dirPath, entry.name));
2083
+ files += subCounts.files;
2084
+ dirs += subCounts.dirs;
2085
+ } else {
2086
+ files++;
2087
+ }
2088
+ }
2089
+ return { files, dirs };
2090
+ }
2091
+ function cleanDirectory(dirPath, options) {
2092
+ const existed = existsSync(dirPath);
2093
+ if (!existed) {
2094
+ if (options.verbose) {
2095
+ console.info(` \u2139\uFE0F Directory does not exist: ${dirPath}`);
2096
+ }
2097
+ return {
2098
+ path: dirPath,
2099
+ filesRemoved: 0,
2100
+ directoriesRemoved: 0,
2101
+ existed: false
2102
+ };
2103
+ }
2104
+ const counts = countContents(dirPath);
2105
+ if (options.dryRun) {
2106
+ if (options.verbose) {
2107
+ console.info(
2108
+ ` \u{1F50D} Would remove: ${dirPath} (${counts.files} files, ${counts.dirs} directories)`
2109
+ );
2110
+ }
2111
+ return {
2112
+ path: dirPath,
2113
+ filesRemoved: counts.files,
2114
+ directoriesRemoved: counts.dirs,
2115
+ existed: true
2116
+ };
2117
+ }
2118
+ const entries = readdirSync(dirPath, { withFileTypes: true });
2119
+ const toPreserve = entries.filter((e) => shouldPreserve(e.name, options.preserve));
2120
+ if (toPreserve.length > 0) {
2121
+ for (const entry of entries) {
2122
+ if (shouldPreserve(entry.name, options.preserve)) {
2123
+ if (options.verbose) {
2124
+ console.info(` \u{1F4CC} Preserving: ${entry.name}`);
2125
+ }
2126
+ continue;
2127
+ }
2128
+ const entryPath = join(dirPath, entry.name);
2129
+ rmSync(entryPath, { recursive: true, force: true });
2130
+ }
2131
+ } else {
2132
+ rmSync(dirPath, { recursive: true, force: true });
2133
+ }
2134
+ if (options.verbose) {
2135
+ console.info(` \u2705 Cleaned: ${dirPath} (${counts.files} files, ${counts.dirs} directories)`);
2136
+ }
2137
+ return {
2138
+ path: dirPath,
2139
+ filesRemoved: counts.files,
2140
+ directoriesRemoved: counts.dirs,
2141
+ existed: true
2142
+ };
2143
+ }
2144
+ function cleanTokenOutputs(options = {}) {
2145
+ const startTime = Date.now();
2146
+ const normalizedOptions = {
2147
+ baseDir: options.baseDir ?? process.cwd(),
2148
+ directories: options.directories ?? [...DEFAULT_CLEAN_DIRECTORIES],
2149
+ dryRun: options.dryRun ?? false,
2150
+ verbose: options.verbose ?? false,
2151
+ preserve: options.preserve ?? []
2152
+ };
2153
+ const result = {
2154
+ success: true,
2155
+ cleaned: [],
2156
+ totalFilesRemoved: 0,
2157
+ totalDirectoriesRemoved: 0,
2158
+ errors: [],
2159
+ warnings: [],
2160
+ dryRun: normalizedOptions.dryRun,
2161
+ duration: 0
2162
+ };
2163
+ if (normalizedOptions.verbose) {
2164
+ const modeStr = normalizedOptions.dryRun ? "(dry-run)" : "";
2165
+ console.info(`
2166
+ \u{1F9F9} Cleaning token outputs ${modeStr}`);
2167
+ console.info(` Base: ${normalizedOptions.baseDir}`);
2168
+ }
2169
+ for (const dir of normalizedOptions.directories) {
2170
+ const absolutePath = resolve(normalizedOptions.baseDir, dir);
2171
+ const validation = validateCleanTarget(absolutePath, normalizedOptions.baseDir);
2172
+ if (!validation.valid && validation.error) {
2173
+ result.errors.push(validation.error);
2174
+ result.success = false;
2175
+ continue;
2176
+ }
2177
+ try {
2178
+ const cleaned = cleanDirectory(absolutePath, {
2179
+ dryRun: normalizedOptions.dryRun,
2180
+ verbose: normalizedOptions.verbose,
2181
+ preserve: normalizedOptions.preserve
2182
+ });
2183
+ result.cleaned.push(cleaned);
2184
+ result.totalFilesRemoved += cleaned.filesRemoved;
2185
+ result.totalDirectoriesRemoved += cleaned.directoriesRemoved;
2186
+ } catch (error) {
2187
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2188
+ result.errors.push(`Failed to clean "${dir}": ${errorMessage}`);
2189
+ result.success = false;
2190
+ }
2191
+ }
2192
+ result.duration = Date.now() - startTime;
2193
+ if (normalizedOptions.verbose) {
2194
+ console.info("");
2195
+ if (result.success) {
2196
+ const actionStr = normalizedOptions.dryRun ? "Would remove" : "Removed";
2197
+ console.info(
2198
+ `\u2705 ${actionStr} ${result.totalFilesRemoved} files, ${result.totalDirectoriesRemoved} directories in ${result.duration}ms`
2199
+ );
2200
+ } else {
2201
+ console.error("\u274C Clean failed with errors:");
2202
+ for (const error of result.errors) {
2203
+ console.error(` - ${error}`);
2204
+ }
2205
+ }
2206
+ }
2207
+ return result;
2208
+ }
2209
+ function cleanTokensCLI(baseDir, options = {}) {
2210
+ const result = cleanTokenOutputs({
2211
+ ...options,
2212
+ baseDir,
2213
+ verbose: options.verbose ?? true
2214
+ });
2215
+ return result.success;
2216
+ }
2217
+ function isToken2(obj) {
2218
+ if (!obj || typeof obj !== "object") {
2219
+ return false;
2220
+ }
2221
+ const record = obj;
2222
+ return "$type" in record || "$value" in record;
2223
+ }
2224
+ function hasChildTokens(obj) {
2225
+ if (!obj || typeof obj !== "object") {
2226
+ return false;
2227
+ }
2228
+ const record = obj;
2229
+ for (const key of Object.keys(record)) {
2230
+ if (!key.startsWith("$") && typeof record[key] === "object") {
2231
+ return true;
2232
+ }
2233
+ }
2234
+ return false;
2235
+ }
2236
+ function deepMerge(target, source, verbose = false) {
2237
+ const result = target ? { ...target } : {};
2238
+ const targetIsToken = isToken2(target);
2239
+ const sourceIsToken = isToken2(source);
2240
+ const targetHasChildren = hasChildTokens(target);
2241
+ const sourceHasChildren = hasChildTokens(source);
2242
+ if (targetHasChildren && sourceIsToken) {
2243
+ if (verbose) {
2244
+ console.info(" \u26A0\uFE0F Skipping single token in favor of children structure");
2245
+ }
2246
+ return result;
2247
+ }
2248
+ if (sourceHasChildren && targetIsToken) {
2249
+ if (verbose) {
2250
+ console.info(" \u26A0\uFE0F Replacing single token with children structure");
2251
+ }
2252
+ return { ...source };
2253
+ }
2254
+ for (const key of Object.keys(source)) {
2255
+ const sourceValue = source[key];
2256
+ const targetValue = result[key];
2257
+ if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
2258
+ result[key] = deepMerge(
2259
+ targetValue,
2260
+ sourceValue,
2261
+ verbose
2262
+ );
2263
+ } else if (Array.isArray(sourceValue)) {
2264
+ const existingArray = Array.isArray(targetValue) ? targetValue : [];
2265
+ result[key] = [...existingArray, ...sourceValue];
2266
+ } else if (sourceValue !== void 0) {
2267
+ result[key] = sourceValue;
2268
+ }
2269
+ }
2270
+ return result;
2271
+ }
2272
+ function countTokens(obj) {
2273
+ if (!obj || typeof obj !== "object") {
2274
+ return 0;
2275
+ }
2276
+ let count = 0;
2277
+ const record = obj;
2278
+ for (const key of Object.keys(record)) {
2279
+ const value = record[key];
2280
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2281
+ const valueRecord = value;
2282
+ if ("$type" in valueRecord || "$value" in valueRecord) {
2283
+ count++;
2284
+ } else {
2285
+ count += countTokens(value);
2286
+ }
2287
+ }
2288
+ }
2289
+ return count;
2290
+ }
2291
+ function updateReferences(obj, oldName, newName, collectionName) {
2292
+ const normalizedOld = oldName.toLowerCase().replace(/\s+/g, "");
2293
+ const normalizedNew = newName.toLowerCase().replace(/\s+/g, "");
2294
+ const patternsLower = [`{${oldName.toLowerCase()}.`, `{${normalizedOld}.`];
2295
+ for (const key of Object.keys(obj)) {
2296
+ const value = obj[key];
2297
+ if (typeof value === "string" && value.startsWith("{") && value.endsWith("}")) {
2298
+ let newValue = value;
2299
+ const valueLower = value.toLowerCase();
2300
+ for (const pattern of patternsLower) {
2301
+ if (valueLower.startsWith(pattern)) {
2302
+ const suffix = value.slice(pattern.length);
2303
+ newValue = `{${normalizedNew}.${suffix}`;
2304
+ break;
2305
+ }
2306
+ }
2307
+ obj[key] = newValue;
2308
+ if (obj[key].startsWith("{")) {
2309
+ if (!("$libraryName" in obj)) {
2310
+ obj["$libraryName"] = "";
2311
+ }
2312
+ if (!("$collectionName" in obj)) {
2313
+ obj["$collectionName"] = collectionName;
2314
+ }
2315
+ }
2316
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
2317
+ updateReferences(value, oldName, newName, collectionName);
2318
+ }
2319
+ }
2320
+ }
2321
+ function sortProperties(obj) {
2322
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
2323
+ return obj;
2324
+ }
2325
+ const record = obj;
2326
+ const sorted = {};
2327
+ const keys = Object.keys(record).sort((a, b) => {
2328
+ const aIsMeta = a.startsWith("$");
2329
+ const bIsMeta = b.startsWith("$");
2330
+ if (aIsMeta && !bIsMeta) {
2331
+ return -1;
2332
+ }
2333
+ if (!aIsMeta && bIsMeta) {
2334
+ return 1;
2335
+ }
2336
+ return a.localeCompare(b);
2337
+ });
2338
+ for (const key of keys) {
2339
+ sorted[key] = sortProperties(record[key]);
2340
+ }
2341
+ return sorted;
2342
+ }
2343
+ function isDuplicateSection(obj) {
2344
+ if (!obj || typeof obj !== "object") {
2345
+ return false;
2346
+ }
2347
+ let hasOnlyReferences = true;
2348
+ let referenceCount = 0;
2349
+ function checkReferences(item) {
2350
+ for (const key of Object.keys(item)) {
2351
+ if (key.startsWith("$")) {
2352
+ continue;
2353
+ }
2354
+ const value = item[key];
2355
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2356
+ const valueRecord = value;
2357
+ if ("$value" in valueRecord) {
2358
+ referenceCount++;
2359
+ if (typeof valueRecord["$value"] === "string" && valueRecord["$value"].startsWith("{")) {
2360
+ continue;
2361
+ } else {
2362
+ hasOnlyReferences = false;
2363
+ return;
2364
+ }
2365
+ }
2366
+ checkReferences(valueRecord);
2367
+ }
2368
+ }
2369
+ }
2370
+ checkReferences(obj);
2371
+ return hasOnlyReferences && referenceCount > 10;
2372
+ }
2373
+ function getCollectionName(data) {
2374
+ if (Array.isArray(data) && data[0]) {
2375
+ const keys = Object.keys(data[0]);
2376
+ return keys[0] ?? null;
2377
+ }
2378
+ return null;
2379
+ }
2380
+ function loadJSON(filePath, verbose) {
2381
+ try {
2382
+ const fullPath = resolve(filePath);
2383
+ if (verbose) {
2384
+ console.info(`\u{1F4D6} Reading: ${fullPath}`);
2385
+ }
2386
+ const content = readFileSync(fullPath, "utf-8");
2387
+ return JSON.parse(content);
2388
+ } catch {
2389
+ return null;
2390
+ }
2391
+ }
2392
+ function saveJSON(filePath, data, verbose) {
2393
+ try {
2394
+ const fullPath = resolve(filePath);
2395
+ const content = JSON.stringify(data, null, 2);
2396
+ writeFileSync(fullPath, content, "utf-8");
2397
+ if (verbose) {
2398
+ console.info(`\u2705 Saved: ${fullPath}`);
2399
+ }
2400
+ return true;
2401
+ } catch {
2402
+ return false;
2403
+ }
2404
+ }
2405
+ function mergeCollections(options) {
2406
+ const { sourceFiles, outputFile, strategy = "last", dryRun = false, verbose = false } = options;
2407
+ const errors = [];
2408
+ const conflicts = [];
2409
+ if (sourceFiles.length < 2) {
2410
+ return {
2411
+ success: false,
2412
+ collectionsCount: 0,
2413
+ tokensCount: 0,
2414
+ outputFile,
2415
+ errors: ["At least two source files are required"]
2416
+ };
2417
+ }
2418
+ for (const file of sourceFiles) {
2419
+ if (!existsSync(file)) {
2420
+ errors.push(`Source file not found: ${file}`);
2421
+ }
2422
+ }
2423
+ if (errors.length > 0) {
2424
+ return {
2425
+ success: false,
2426
+ collectionsCount: 0,
2427
+ tokensCount: 0,
2428
+ outputFile,
2429
+ errors
2430
+ };
2431
+ }
2432
+ if (verbose) {
2433
+ console.info("\u{1F504} Starting merge process...\n");
2434
+ }
2435
+ const collections = [];
2436
+ for (const file of sourceFiles) {
2437
+ const data = loadJSON(file, verbose);
2438
+ if (!data || !Array.isArray(data) || !data[0]) {
2439
+ errors.push(`Invalid collection structure in: ${file}`);
2440
+ continue;
2441
+ }
2442
+ const name = getCollectionName(data);
2443
+ const collectionData = name ? data[0][name] : data[0];
2444
+ const tokenCount = countTokens(collectionData);
2445
+ if (verbose) {
2446
+ console.info(`\u{1F4E6} Collection: "${name ?? "unknown"}" (${tokenCount} tokens)`);
2447
+ }
2448
+ collections.push({ name, data: collectionData, tokenCount });
2449
+ }
2450
+ if (collections.length < 2) {
2451
+ return {
2452
+ success: false,
2453
+ collectionsCount: collections.length,
2454
+ tokensCount: 0,
2455
+ outputFile,
2456
+ errors: errors.length > 0 ? errors : ["Not enough valid collections to merge"]
2457
+ };
2458
+ }
2459
+ const collectionNames = collections.map((c) => c.name).filter((n) => n !== null);
2460
+ const defaultName = collectionNames[0] ?? "Tokens";
2461
+ const unifiedName = collectionNames.includes("Colors") ? "Colors" : collectionNames.reduce((a, b) => a.length <= b.length ? a : b, defaultName);
2462
+ if (verbose) {
2463
+ console.info(`
2464
+ \u{1F3AF} Unified collection name: "${unifiedName}"`);
2465
+ console.info("\n\u{1F500} Merging structures...");
2466
+ }
2467
+ const firstCollection = collections[0];
2468
+ if (!firstCollection) {
2469
+ return {
2470
+ success: false,
2471
+ collectionsCount: 0,
2472
+ tokensCount: 0,
2473
+ outputFile,
2474
+ errors: ["No collections to merge"]
2475
+ };
2476
+ }
2477
+ let merged = firstCollection.data;
2478
+ for (let i = 1; i < collections.length; i++) {
2479
+ const source = collections[i];
2480
+ if (!source) {
2481
+ continue;
2482
+ }
2483
+ if (strategy === "first") {
2484
+ merged = deepMerge(source.data, merged, verbose);
2485
+ } else {
2486
+ merged = deepMerge(merged, source.data, verbose);
2487
+ }
2488
+ }
2489
+ if (verbose) {
2490
+ console.info("\u{1F5D1}\uFE0F Checking for duplicate sections...");
2491
+ }
2492
+ const modes = merged["modes"];
2493
+ if (modes) {
2494
+ for (const modeName of Object.keys(modes)) {
2495
+ const mode = modes[modeName];
2496
+ const colors = mode?.["colors"];
2497
+ if (colors) {
2498
+ for (const sectionName of Object.keys(colors)) {
2499
+ if (isDuplicateSection(colors[sectionName])) {
2500
+ if (verbose) {
2501
+ console.info(` \u26A0\uFE0F Removing duplicate section: ${sectionName}`);
2502
+ }
2503
+ delete colors[sectionName];
2504
+ }
2505
+ }
2506
+ }
2507
+ }
2508
+ }
2509
+ if (verbose) {
2510
+ const normalized = unifiedName.toLowerCase().replace(/\s+/g, "");
2511
+ console.info(`\u{1F517} Normalizing references to lowercase "{${normalized}." format...`);
2512
+ }
2513
+ for (const coll of collections) {
2514
+ if (coll.name) {
2515
+ updateReferences(merged, coll.name, unifiedName, unifiedName);
2516
+ }
2517
+ }
2518
+ if (verbose) {
2519
+ console.info("\u{1F4CB} Sorting properties alphabetically...");
2520
+ }
2521
+ const sorted = sortProperties(merged);
2522
+ const tokensCount = countTokens(sorted);
2523
+ if (verbose) {
2524
+ console.info(`\u2728 Merged collection: ${tokensCount} tokens
2525
+ `);
2526
+ }
2527
+ const output = [{ [unifiedName]: sorted }];
2528
+ if (!dryRun) {
2529
+ if (!saveJSON(outputFile, output, verbose)) {
2530
+ errors.push(`Failed to write output file: ${outputFile}`);
2531
+ return {
2532
+ success: false,
2533
+ collectionsCount: collections.length,
2534
+ tokensCount,
2535
+ outputFile,
2536
+ errors,
2537
+ conflicts: conflicts.length > 0 ? conflicts : void 0
2538
+ };
2539
+ }
2540
+ }
2541
+ if (verbose) {
2542
+ console.info("\n\u2705 Merge complete!");
2543
+ console.info("\u{1F4CA} Summary:");
2544
+ for (const coll of collections) {
2545
+ console.info(` Source: ${coll.tokenCount} tokens`);
2546
+ }
2547
+ console.info(` Merged: ${tokensCount} tokens`);
2548
+ }
2549
+ return {
2550
+ success: true,
2551
+ collectionsCount: collections.length,
2552
+ tokensCount,
2553
+ outputFile,
2554
+ conflicts: conflicts.length > 0 ? conflicts : void 0,
2555
+ errors: errors.length > 0 ? errors : void 0
2556
+ };
2557
+ }
2558
+ function mergeCollectionsCLI(source1, source2, output) {
2559
+ const result = mergeCollections({
2560
+ sourceFiles: [source1, source2],
2561
+ outputFile: output,
2562
+ verbose: true
2563
+ });
2564
+ if (result.errors) {
2565
+ for (const error of result.errors) {
2566
+ console.error(`\u274C ${error}`);
2567
+ }
2568
+ }
2569
+ return result.success;
2570
+ }
2571
+ var DEFAULT_IGNORE_PATTERNS = [
2572
+ "**/node_modules/**",
2573
+ "**/.git/**",
2574
+ "**/_index.scss",
2575
+ "**/*.test.scss",
2576
+ "**/*.spec.scss",
2577
+ "**/test/**",
2578
+ "**/tests/**"
2579
+ ];
2580
+ function globToRegex(pattern) {
2581
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<<GLOBSTAR>>>").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/<<<GLOBSTAR>>>/g, ".*");
2582
+ return new RegExp(`^${regex}$`);
2583
+ }
2584
+ function matchesIgnorePattern(relativePath, patterns) {
2585
+ for (const pattern of patterns) {
2586
+ const regex = globToRegex(pattern);
2587
+ if (regex.test(relativePath)) {
2588
+ return true;
2589
+ }
2590
+ const fileName = path.basename(relativePath);
2591
+ if (regex.test(fileName)) {
2592
+ return true;
2593
+ }
2594
+ }
2595
+ return false;
2596
+ }
2597
+ async function scanDirectory(dir, rootDir, extension, ignorePatterns, maxDepth, currentDepth, followSymlinks) {
2598
+ const files = [];
2599
+ if (currentDepth > maxDepth) {
2600
+ return files;
2601
+ }
2602
+ try {
2603
+ const entries = await fs3.promises.readdir(dir, { withFileTypes: true });
2604
+ for (const entry of entries) {
2605
+ const entryPath = path.join(dir, entry.name);
2606
+ const relativePath = path.relative(rootDir, entryPath);
2607
+ if (matchesIgnorePattern(relativePath, ignorePatterns)) {
2608
+ continue;
2609
+ }
2610
+ if (entry.isDirectory() || followSymlinks && entry.isSymbolicLink()) {
2611
+ const subFiles = await scanDirectory(
2612
+ entryPath,
2613
+ rootDir,
2614
+ extension,
2615
+ ignorePatterns,
2616
+ maxDepth,
2617
+ currentDepth + 1,
2618
+ followSymlinks
2619
+ );
2620
+ files.push(...subFiles);
2621
+ } else if (entry.isFile() && entry.name.endsWith(`.${extension}`)) {
2622
+ const stat = await fs3.promises.stat(entryPath);
2623
+ files.push({
2624
+ absolutePath: entryPath,
2625
+ relativePath,
2626
+ name: path.basename(entry.name, `.${extension}`),
2627
+ extension,
2628
+ size: stat.size,
2629
+ mtime: stat.mtime,
2630
+ depth: currentDepth
2631
+ });
2632
+ }
2633
+ }
2634
+ } catch (error) {
2635
+ console.warn(`Warning: Could not read directory ${dir}: ${error.message}`);
2636
+ }
2637
+ return files;
2638
+ }
2639
+ async function scanDirectories(options) {
2640
+ const {
2641
+ directories,
2642
+ extension,
2643
+ ignorePatterns = DEFAULT_IGNORE_PATTERNS,
2644
+ followSymlinks = false,
2645
+ maxDepth = 10
2646
+ } = options;
2647
+ const files = [];
2648
+ const scannedDirs = [];
2649
+ const missingDirs = [];
2650
+ for (const dir of directories) {
2651
+ const absoluteDir = path.resolve(dir);
2652
+ try {
2653
+ const stat = await fs3.promises.stat(absoluteDir);
2654
+ if (!stat.isDirectory()) {
2655
+ missingDirs.push(absoluteDir);
2656
+ continue;
2657
+ }
2658
+ } catch {
2659
+ missingDirs.push(absoluteDir);
2660
+ continue;
2661
+ }
2662
+ scannedDirs.push(absoluteDir);
2663
+ const foundFiles = await scanDirectory(
2664
+ absoluteDir,
2665
+ absoluteDir,
2666
+ extension,
2667
+ ignorePatterns,
2668
+ maxDepth,
2669
+ 0,
2670
+ followSymlinks
2671
+ );
2672
+ files.push(...foundFiles);
2673
+ }
2674
+ const totalSize = files.reduce((sum, file) => sum + file.size, 0);
2675
+ return {
2676
+ files,
2677
+ directories: scannedDirs,
2678
+ missingDirectories: missingDirs,
2679
+ totalFiles: files.length,
2680
+ totalSize
2681
+ };
2682
+ }
2683
+ function sortFiles(files, order = "alphabetical") {
2684
+ const sorted = [...files];
2685
+ if (order === "alphabetical") {
2686
+ sorted.sort((a, b) => a.name.localeCompare(b.name));
2687
+ } else if (order === "directory-first") {
2688
+ sorted.sort((a, b) => {
2689
+ const dirA = path.dirname(a.relativePath);
2690
+ const dirB = path.dirname(b.relativePath);
2691
+ if (dirA !== dirB) {
2692
+ return dirA.localeCompare(dirB);
2693
+ }
2694
+ return a.name.localeCompare(b.name);
2695
+ });
2696
+ }
2697
+ return sorted;
2698
+ }
2699
+ function filterFiles(files, patterns, include = false) {
2700
+ return files.filter((file) => {
2701
+ const matches = matchesIgnorePattern(file.relativePath, patterns);
2702
+ return include ? matches : !matches;
2703
+ });
2704
+ }
2705
+ function getDefaultIgnorePatterns() {
2706
+ return [...DEFAULT_IGNORE_PATTERNS];
2707
+ }
2708
+ function generateHeaderComment(_format) {
2709
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2710
+ const comment = `/**
2711
+ * DSAi Design Tokens - Merged Bundle
2712
+ * Generated: ${timestamp}
2713
+ *
2714
+ * This file was automatically generated and includes:
2715
+ * - Generated design tokens
2716
+ * - Merged additional stylesheets
2717
+ *
2718
+ * DO NOT EDIT DIRECTLY - changes will be overwritten on rebuild
2719
+ */`;
2720
+ return comment;
2721
+ }
2722
+ function generateSourceComment(source, _format) {
2723
+ return `/* ========== Source: ${source} ========== */`;
2724
+ }
2725
+ function detectMergeIssues(content, format) {
2726
+ const warnings = [];
2727
+ const rootMatches = content.match(/:root\s*\{/g);
2728
+ const rootCount = rootMatches ? rootMatches.length : 0;
2729
+ if (rootCount > 1) {
2730
+ warnings.push(
2731
+ `Found ${rootCount} :root selectors. Consider consolidating for better performance.`
2732
+ );
2733
+ }
2734
+ const customProps = content.match(/--[\w-]+:/g) ?? [];
2735
+ const uniqueProps = new Set(customProps);
2736
+ if (customProps.length > uniqueProps.size) {
2737
+ warnings.push(
2738
+ `Found duplicate CSS custom properties. Later definitions will override earlier ones.`
2739
+ );
2740
+ }
2741
+ if (format === "scss") {
2742
+ const importAfterContent = /@import\s+['"][^'"]+['"];?\s*$/gm;
2743
+ if (importAfterContent.test(content)) {
2744
+ warnings.push(`Found @import statements that may not be at the top of the file.`);
2745
+ }
2746
+ }
2747
+ return warnings;
2748
+ }
2749
+ async function mergeContent(tokenContent, userContent, config = {}) {
2750
+ const { mergeOrder = "after" } = config;
2751
+ const parts = [];
2752
+ const sources = ["<generated-tokens>"];
2753
+ const warnings = [];
2754
+ parts.push(generateHeaderComment(tokenContent.format));
2755
+ if (mergeOrder === "before") {
2756
+ for (const content of userContent) {
2757
+ parts.push(generateSourceComment(content.source, tokenContent.format));
2758
+ parts.push(content.content);
2759
+ sources.push(content.source);
2760
+ }
2761
+ parts.push(generateSourceComment("<generated-tokens>", tokenContent.format));
2762
+ parts.push(tokenContent.content);
2763
+ } else {
2764
+ parts.push(generateSourceComment("<generated-tokens>", tokenContent.format));
2765
+ parts.push(tokenContent.content);
2766
+ for (const content of userContent) {
2767
+ parts.push(generateSourceComment(content.source, tokenContent.format));
2768
+ parts.push(content.content);
2769
+ sources.push(content.source);
2770
+ }
2771
+ }
2772
+ const mergedContent = parts.join("\n\n");
2773
+ warnings.push(...detectMergeIssues(mergedContent, tokenContent.format));
2774
+ return {
2775
+ content: mergedContent,
2776
+ sources,
2777
+ warnings
2778
+ };
2779
+ }
2780
+ async function loadContent(files, type, format) {
2781
+ const content = [];
2782
+ for (const filePath of files) {
2783
+ try {
2784
+ const fileContent = await fs3.promises.readFile(filePath, "utf-8");
2785
+ content.push({
2786
+ source: filePath,
2787
+ content: fileContent,
2788
+ type,
2789
+ format
2790
+ });
2791
+ } catch (error) {
2792
+ console.warn(`Failed to load file: ${filePath}`, error);
2793
+ }
2794
+ }
2795
+ return content;
2796
+ }
2797
+ function processScssImportHeader(importHeader, configDir) {
2798
+ if (!importHeader) {
2799
+ return "";
2800
+ }
2801
+ const imports = Array.isArray(importHeader) ? importHeader : [importHeader];
2802
+ const statements = [];
2803
+ for (const importPath of imports) {
2804
+ if (importPath.startsWith(".") || importPath.startsWith("/")) {
2805
+ const resolvedPath = path.resolve(configDir, importPath);
2806
+ statements.push(`@import '${resolvedPath}';`);
2807
+ } else {
2808
+ statements.push(`@import '${importPath}';`);
2809
+ }
2810
+ }
2811
+ return `${statements.join("\n")}
2812
+
2813
+ `;
2814
+ }
2815
+ function addScssImportHeader(content, importHeader, configDir) {
2816
+ const header = processScssImportHeader(importHeader, configDir);
2817
+ if (!header) {
2818
+ return content;
2819
+ }
2820
+ return header + content;
2821
+ }
2822
+ function removeSourceComments(content) {
2823
+ return content.replace(/\/\* ========== Source: .+ ========== \*\/\n?/g, "");
2824
+ }
2825
+ function minifyContent(content) {
2826
+ return content.replace(/\/\*(?!!)[^*]*\*+([^/*][^*]*\*+)*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").replace(/;}/g, "}").replace(/[^{}]+\{\s*\}/g, "").trim();
2827
+ }
2828
+ function detectExtension(mergeResult) {
2829
+ const hasScssFeatures = mergeResult.content.match(/@mixin|@include|\$[a-zA-Z]/);
2830
+ return hasScssFeatures ? ".scss" : ".css";
2831
+ }
2832
+ function generateSourcemap(sources, name, extension) {
2833
+ const sourceMapV3 = {
2834
+ version: 3,
2835
+ file: `${name}${extension}`,
2836
+ sources,
2837
+ names: [],
2838
+ mappings: ""
2839
+ };
2840
+ return JSON.stringify(sourceMapV3, null, 2);
2841
+ }
2842
+ async function createBundle(mergeResult, config) {
2843
+ const { name, outputDir, includeSourceComments, sourcemaps, minify } = config;
2844
+ let content = mergeResult.content;
2845
+ if (!includeSourceComments) {
2846
+ content = removeSourceComments(content);
2847
+ }
2848
+ let minifiedSize;
2849
+ if (minify) {
2850
+ const minified = minifyContent(content);
2851
+ minifiedSize = Buffer.byteLength(minified, "utf-8");
2852
+ content = minified;
2853
+ }
2854
+ const extension = detectExtension(mergeResult);
2855
+ const outputPath = path.join(outputDir, `${name}${extension}`);
2856
+ await fs3.promises.mkdir(outputDir, { recursive: true });
2857
+ await fs3.promises.writeFile(outputPath, content, "utf-8");
2858
+ let sourcemap;
2859
+ if (sourcemaps) {
2860
+ sourcemap = generateSourcemap(mergeResult.sources, name, extension);
2861
+ const mapPath = `${outputPath}.map`;
2862
+ await fs3.promises.writeFile(mapPath, sourcemap, "utf-8");
2863
+ const mapRef = `
2864
+ /*# sourceMappingURL=${name}${extension}.map */`;
2865
+ content += mapRef;
2866
+ await fs3.promises.writeFile(outputPath, content, "utf-8");
2867
+ }
2868
+ return {
2869
+ content,
2870
+ sourcemap,
2871
+ outputPath,
2872
+ files: mergeResult.sources,
2873
+ size: Buffer.byteLength(content, "utf-8"),
2874
+ minifiedSize
2875
+ };
2876
+ }
2877
+ async function createBundles(tokenCss, tokenScss, cssFiles, scssFiles, config) {
2878
+ const { baseName = "tokens-bundle", ...bundleConfig } = config;
2879
+ const results = {};
2880
+ const sortedCss = sortFiles(cssFiles);
2881
+ if (tokenCss || sortedCss.length > 0) {
2882
+ const userContent = await loadContent(
2883
+ sortedCss.map((f) => f.absolutePath),
2884
+ "user",
2885
+ "css"
2886
+ );
2887
+ const mergeResult = await mergeContent({ content: tokenCss, format: "css" }, userContent);
2888
+ results.css = await createBundle(mergeResult, {
2889
+ ...bundleConfig,
2890
+ name: baseName
2891
+ });
2892
+ }
2893
+ const sortedScss = sortFiles(scssFiles);
2894
+ if (tokenScss || sortedScss.length > 0) {
2895
+ const userContent = await loadContent(
2896
+ sortedScss.map((f) => f.absolutePath),
2897
+ "user",
2898
+ "scss"
2899
+ );
2900
+ const mergeResult = await mergeContent({ content: tokenScss, format: "scss" }, userContent);
2901
+ results.scss = await createBundle(mergeResult, {
2902
+ ...bundleConfig,
2903
+ name: `_${baseName}`
2904
+ });
2905
+ }
2906
+ return results;
2907
+ }
2908
+ async function createBundleFromFiles(files, format, config) {
2909
+ const sorted = sortFiles(files);
2910
+ const userContent = await loadContent(
2911
+ sorted.map((f) => f.absolutePath),
2912
+ "user",
2913
+ format
2914
+ );
2915
+ const mergeResult = await mergeContent({ content: "", format }, userContent);
2916
+ return createBundle(mergeResult, config);
2917
+ }
2918
+ var DEFAULT_FILE_NAMES = {
2919
+ css: "tokens.css",
2920
+ scss: "_tokens.scss",
2921
+ js: "tokens.js",
2922
+ ts: "tokens.ts",
2923
+ json: "tokens.json",
2924
+ android: "tokens.xml",
2925
+ ios: "Tokens.swift"
2926
+ };
2927
+ function replacePlaceholders(template, values) {
2928
+ const now = /* @__PURE__ */ new Date();
2929
+ const allValues = {
2930
+ theme: values.theme ?? "default",
2931
+ name: values.name ?? "tokens",
2932
+ format: values.format ?? "",
2933
+ date: values.date ?? now.toISOString().split("T")[0] ?? "",
2934
+ timestamp: values.timestamp ?? now.toISOString()
2935
+ };
2936
+ let result = template;
2937
+ for (const [key, value] of Object.entries(allValues)) {
2938
+ result = result.replaceAll(`{${key}}`, value);
2939
+ }
2940
+ return result;
2941
+ }
2942
+ function resolveOutputPath(format, config, values = {}) {
2943
+ const dir = config.formatDirs[format] ?? config.baseDir;
2944
+ let fileName = config.fileNames[format] ?? DEFAULT_FILE_NAMES[format];
2945
+ fileName = replacePlaceholders(fileName, values);
2946
+ return path.join(dir, fileName);
2947
+ }
2948
+ function resolveAllOutputPaths(formats, config, values = {}) {
2949
+ const result = {};
2950
+ for (const format of formats) {
2951
+ result[format] = resolveOutputPath(format, config, values);
2952
+ }
2953
+ return result;
2954
+ }
2955
+ function validateOutputPaths(paths) {
2956
+ const conflicts = [];
2957
+ const pathToFormat = {};
2958
+ for (const [format, outputPath] of Object.entries(paths)) {
2959
+ const normalized = path.normalize(outputPath);
2960
+ const existingFormat = pathToFormat[normalized];
2961
+ if (existingFormat !== void 0) {
2962
+ conflicts.push([existingFormat, format]);
2963
+ } else {
2964
+ pathToFormat[normalized] = format;
2965
+ }
2966
+ }
2967
+ return {
2968
+ valid: conflicts.length === 0,
2969
+ conflicts
2970
+ };
2971
+ }
2972
+ async function ensureOutputDirs(paths) {
2973
+ const directories = /* @__PURE__ */ new Set();
2974
+ for (const outputPath of Object.values(paths)) {
2975
+ directories.add(path.dirname(outputPath));
2976
+ }
2977
+ const created = [];
2978
+ for (const dir of directories) {
2979
+ await fs3.promises.mkdir(dir, { recursive: true });
2980
+ created.push(dir);
2981
+ }
2982
+ return created;
2983
+ }
2984
+ function createOutputConfig(tokenConfig) {
2985
+ return {
2986
+ baseDir: tokenConfig.outputDir ?? "dist",
2987
+ formatDirs: tokenConfig.outputDirs ?? {},
2988
+ fileNames: tokenConfig.outputFileNames ?? {},
2989
+ createDirs: true
2990
+ };
2991
+ }
2992
+
2993
+ // src/tokens/style-dictionary/types.ts
2994
+ function isSDToken(obj) {
2995
+ if (typeof obj !== "object" || obj === null) {
2996
+ return false;
2997
+ }
2998
+ const token = obj;
2999
+ if (typeof token["name"] !== "string") {
3000
+ return false;
3001
+ }
3002
+ if (!Array.isArray(token["path"])) {
3003
+ return false;
3004
+ }
3005
+ return true;
3006
+ }
3007
+ function hasDTCGValue(token) {
3008
+ return token.$value !== void 0;
3009
+ }
3010
+ function getSDTokenValue(token) {
3011
+ return token.$value ?? token.value;
3012
+ }
3013
+ function getSDTokenType(token) {
3014
+ return token.$type ?? token.type;
3015
+ }
3016
+
3017
+ // src/tokens/style-dictionary/formats/css-dark-mode.ts
3018
+ var cssDarkModeVariables = {
3019
+ name: "css/variables-dark-mode",
3020
+ format: ({ dictionary, options }) => {
3021
+ const prefix = options["prefix"] ?? "--";
3022
+ const selector = options["selector"] ?? '[data-bs-theme="dark"]';
3023
+ const variables = dictionary.allTokens.map((token) => {
3024
+ const lines = [];
3025
+ if (token.comment) {
3026
+ lines.push(` /* ${token.comment} */`);
3027
+ }
3028
+ const description = token.$description ?? token.description;
3029
+ if (description && description !== token.comment) {
3030
+ lines.push(` /* ${description} */`);
3031
+ }
3032
+ const tokenValue = token.$value !== void 0 ? token.$value : token.value;
3033
+ const value = typeof tokenValue === "string" ? tokenValue : JSON.stringify(tokenValue);
3034
+ lines.push(` ${prefix}${token.name}: ${value};`);
3035
+ return lines.join("\n");
3036
+ });
3037
+ return `${selector} {
3038
+ ${variables.join("\n")}
3039
+ }
3040
+ `;
3041
+ }
3042
+ };
3043
+
3044
+ // src/tokens/style-dictionary/formats/css-variables.ts
3045
+ var cssVariablesWithComments = {
3046
+ name: "css/variables-with-comments",
3047
+ format: ({ dictionary, options }) => {
3048
+ const prefix = options["prefix"] ?? "--";
3049
+ const variables = dictionary.allTokens.map((token) => {
3050
+ const lines = [];
3051
+ if (token.comment) {
3052
+ lines.push(` /* ${token.comment} */`);
3053
+ }
3054
+ const description = token.$description ?? token.description;
3055
+ if (description && description !== token.comment) {
3056
+ lines.push(` /* ${description} */`);
3057
+ }
3058
+ const tokenValue = token.$value !== void 0 ? token.$value : token.value;
3059
+ const value = typeof tokenValue === "string" ? tokenValue : JSON.stringify(tokenValue);
3060
+ lines.push(` ${prefix}${token.name}: ${value};`);
3061
+ return lines.join("\n");
3062
+ });
3063
+ return `:root {
3064
+ ${variables.join("\n")}
3065
+ }
3066
+ `;
3067
+ }
3068
+ };
3069
+
3070
+ // src/tokens/style-dictionary/formats/typescript.ts
3071
+ function getTypeScriptType(token) {
3072
+ const value = token.value;
3073
+ if (typeof value === "number") {
3074
+ return "number";
3075
+ }
3076
+ if (typeof value === "boolean") {
3077
+ return "boolean";
3078
+ }
3079
+ return "string";
3080
+ }
3081
+ function buildTokenInterface(obj, indent = 0) {
3082
+ const spaces = " ".repeat(indent);
3083
+ let output = "{\n";
3084
+ for (const key of Object.keys(obj)) {
3085
+ if (key.startsWith("_")) {
3086
+ continue;
3087
+ }
3088
+ const value = obj[key];
3089
+ if (!value || typeof value !== "object") {
3090
+ continue;
3091
+ }
3092
+ const quotedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
3093
+ const typedValue = value;
3094
+ if (typedValue._isToken) {
3095
+ output += `${spaces} ${quotedKey}: ${typedValue._type};
3096
+ `;
3097
+ } else {
3098
+ output += `${spaces} ${quotedKey}: ${buildTokenInterface(typedValue, indent + 1)}
3099
+ `;
3100
+ }
3101
+ }
3102
+ output += `${spaces}}`;
3103
+ return output;
3104
+ }
3105
+ function capitalize(str) {
3106
+ return str.charAt(0).toUpperCase() + str.slice(1);
3107
+ }
3108
+ var typescriptDeclarations = {
3109
+ name: "typescript/declarations",
3110
+ format: ({ dictionary }) => {
3111
+ const tokenTree = {};
3112
+ for (const token of dictionary.allTokens) {
3113
+ let current = tokenTree;
3114
+ for (let i = 0; i < token.path.length - 1; i++) {
3115
+ const key = token.path[i];
3116
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
3117
+ continue;
3118
+ }
3119
+ if (!current[key] || typeof current[key] !== "object") {
3120
+ current[key] = {};
3121
+ }
3122
+ current = current[key];
3123
+ }
3124
+ const lastKey = token.path[token.path.length - 1];
3125
+ if (lastKey) {
3126
+ current[lastKey] = {
3127
+ _isToken: true,
3128
+ _type: getTypeScriptType(token)
3129
+ };
3130
+ }
3131
+ }
3132
+ const categories = {};
3133
+ for (const token of dictionary.allTokens) {
3134
+ const category = token.path[0];
3135
+ if (category) {
3136
+ if (!categories[category]) {
3137
+ categories[category] = [];
3138
+ }
3139
+ categories[category].push(token.path.join("."));
3140
+ }
3141
+ }
3142
+ let literalTypes = "";
3143
+ for (const category of Object.keys(categories).sort()) {
3144
+ const categoryTokens = categories[category];
3145
+ if (!categoryTokens) {
3146
+ continue;
3147
+ }
3148
+ const typeName = `${capitalize(category)}TokenName`;
3149
+ literalTypes += `/**
3150
+ * All ${category} token names as string literals
3151
+ */
3152
+ `;
3153
+ literalTypes += `export type ${typeName} =
3154
+ `;
3155
+ literalTypes += categoryTokens.map((t) => ` | '${t}'`).join("\n");
3156
+ literalTypes += ";\n\n";
3157
+ }
3158
+ const allTokenNames = dictionary.allTokens.map((t) => t.path.join("."));
3159
+ literalTypes += `/**
3160
+ * All token names as string literals
3161
+ */
3162
+ `;
3163
+ literalTypes += `export type TokenName =
3164
+ `;
3165
+ literalTypes += allTokenNames.map((t) => ` | '${t}'`).join("\n");
3166
+ literalTypes += ";\n\n";
3167
+ let flatExports = "/**\n * Flat token exports (camelCase names)\n */\n";
3168
+ for (const token of dictionary.allTokens) {
3169
+ const type = getTypeScriptType(token);
3170
+ flatExports += `export declare const ${token.name}: ${type};
3171
+ `;
3172
+ }
3173
+ return `/**
3174
+ * Design Tokens - TypeScript Declarations
3175
+ * Auto-generated by @dsai-io/tools
3176
+ * DO NOT EDIT DIRECTLY
3177
+ *
3178
+ * @packageDocumentation
3179
+ */
3180
+
3181
+ // ============================================================================
3182
+ // String Literal Types (for type-safe token access)
3183
+ // ============================================================================
3184
+
3185
+ ${literalTypes}
3186
+ // ============================================================================
3187
+ // Nested Token Interface
3188
+ // ============================================================================
3189
+
3190
+ /**
3191
+ * Design tokens organized by category
3192
+ */
3193
+ export interface DesignTokens ${buildTokenInterface(tokenTree)}
3194
+
3195
+ // ============================================================================
3196
+ // Flat Token Exports
3197
+ // ============================================================================
3198
+
3199
+ ${flatExports}
3200
+ // ============================================================================
3201
+ // Default Export
3202
+ // ============================================================================
3203
+
3204
+ export declare const tokens: DesignTokens;
3205
+ export default tokens;
3206
+ `;
3207
+ }
3208
+ };
3209
+
3210
+ // src/tokens/style-dictionary/formats/index.ts
3211
+ var builtInFormats = [
3212
+ cssDarkModeVariables,
3213
+ cssVariablesWithComments,
3214
+ typescriptDeclarations
3215
+ ];
3216
+ function registerFormats(sd, customFormats = []) {
3217
+ const allFormats = [...builtInFormats, ...customFormats];
3218
+ for (const format of allFormats) {
3219
+ sd.registerFormat({
3220
+ name: format.name,
3221
+ format: format.format
3222
+ });
3223
+ }
3224
+ }
3225
+
3226
+ // src/tokens/style-dictionary/groups/css.ts
3227
+ var cssTransformGroup = {
3228
+ name: "custom/css",
3229
+ transforms: [
3230
+ "attribute/cti",
3231
+ "name/kebab",
3232
+ "time/seconds",
3233
+ "fontWeight/unitless",
3234
+ // Must run before dimension/rem
3235
+ "lineHeight/unitless",
3236
+ // Must run before dimension/rem
3237
+ "dimension/rem",
3238
+ "color/css"
3239
+ ]
3240
+ };
3241
+
3242
+ // src/tokens/style-dictionary/groups/js.ts
3243
+ var jsTransformGroup = {
3244
+ name: "custom/js",
3245
+ transforms: [
3246
+ "attribute/cti",
3247
+ "name/camel",
3248
+ "fontWeight/unitless",
3249
+ // Must run before dimension/rem
3250
+ "lineHeight/unitless",
3251
+ // Must run before dimension/rem
3252
+ "dimension/rem",
3253
+ "color/css"
3254
+ ]
3255
+ };
3256
+
3257
+ // src/tokens/style-dictionary/groups/scss.ts
3258
+ var scssTransformGroup = {
3259
+ name: "custom/scss",
3260
+ transforms: [
3261
+ "attribute/cti",
3262
+ // Add CTI attributes
3263
+ "name/kebab",
3264
+ // kebab-case names
3265
+ "time/seconds",
3266
+ // Convert time to seconds
3267
+ "fontWeight/unitless",
3268
+ // Font weights stay unitless (MUST run before dimension/rem)
3269
+ "lineHeight/unitless",
3270
+ // Line heights stay unitless (MUST run before dimension/rem)
3271
+ "dimension/rem",
3272
+ // Convert dimensions to rem
3273
+ "color/css"
3274
+ // Convert colors to CSS format
3275
+ ]
3276
+ };
3277
+
3278
+ // src/tokens/style-dictionary/groups/index.ts
3279
+ var transformGroups = [
3280
+ cssTransformGroup,
3281
+ jsTransformGroup,
3282
+ scssTransformGroup
3283
+ ];
3284
+ function registerTransformGroups(sd, customGroups = []) {
3285
+ const allGroups = [...transformGroups, ...customGroups];
3286
+ for (const group of allGroups) {
3287
+ sd.registerTransformGroup({
3288
+ name: group.name,
3289
+ transforms: group.transforms
3290
+ });
3291
+ }
3292
+ }
3293
+
3294
+ // src/tokens/style-dictionary/preprocessors/fix-references.ts
3295
+ var DEFAULT_PATH_MAPPINGS = [
3296
+ ["{colors.brand.", "{color."],
3297
+ ["{colors.neutral.", "{neutral."],
3298
+ ["{borders.width.", "{border.width."]
3299
+ ];
3300
+ function fixValue(value, mappings) {
3301
+ if (typeof value !== "string" || !value.startsWith("{")) {
3302
+ return value;
3303
+ }
3304
+ let result = value;
3305
+ for (const mapping of mappings) {
3306
+ result = result.split(mapping[0]).join(mapping[1]);
3307
+ }
3308
+ return result;
3309
+ }
3310
+ function processTokens(obj, mappings) {
3311
+ for (const key of Object.keys(obj)) {
3312
+ const value = obj[key];
3313
+ if (value && typeof value === "object") {
3314
+ const typedValue = value;
3315
+ if ("$value" in typedValue) {
3316
+ typedValue["$value"] = fixValue(typedValue["$value"], mappings);
3317
+ } else if ("value" in typedValue) {
3318
+ typedValue["value"] = fixValue(typedValue["value"], mappings);
3319
+ } else {
3320
+ processTokens(typedValue, mappings);
3321
+ }
3322
+ }
3323
+ }
3324
+ }
3325
+ var fixReferences = {
3326
+ name: "fix-references",
3327
+ preprocessor: (dictionary) => {
3328
+ processTokens(dictionary, DEFAULT_PATH_MAPPINGS);
3329
+ return dictionary;
3330
+ }
3331
+ };
3332
+ function createFixReferencesPreprocessor(mappings) {
3333
+ return {
3334
+ name: "fix-references-custom",
3335
+ preprocessor: (dictionary) => {
3336
+ processTokens(dictionary, mappings);
3337
+ return dictionary;
3338
+ }
3339
+ };
3340
+ }
3341
+
3342
+ // src/tokens/style-dictionary/preprocessors/index.ts
3343
+ var builtInPreprocessors = [fixReferences];
3344
+ function registerPreprocessors(sd, customPreprocessors = []) {
3345
+ const allPreprocessors = [...builtInPreprocessors, ...customPreprocessors];
3346
+ for (const preprocessor of allPreprocessors) {
3347
+ sd.registerPreprocessor({
3348
+ name: preprocessor.name,
3349
+ preprocessor: preprocessor.preprocessor
3350
+ });
3351
+ }
3352
+ }
3353
+
3354
+ // src/tokens/style-dictionary/transforms/dimension.ts
3355
+ var DEFAULT_BASE_FONT_SIZE = 16;
3356
+ function isDimension(token) {
3357
+ const tokenType = token.$type ?? token.type;
3358
+ if (tokenType === "fontWeight") {
3359
+ return false;
3360
+ }
3361
+ const pathHasFontWeight = token.path?.some((part) => {
3362
+ const lower = String(part).toLowerCase();
3363
+ return lower.includes("fontweight") || lower.includes("font-weight");
3364
+ });
3365
+ if (pathHasFontWeight) {
3366
+ return false;
3367
+ }
3368
+ if (tokenType === "lineHeight") {
3369
+ return false;
3370
+ }
3371
+ const pathHasLineHeight = token.path?.some((part) => {
3372
+ const lower = String(part).toLowerCase();
3373
+ return lower.includes("lineheight") || lower.includes("line-height");
3374
+ });
3375
+ if (pathHasLineHeight) {
3376
+ return false;
3377
+ }
3378
+ const scopeHasLineHeight = token.$scopes?.includes("LINE_HEIGHT");
3379
+ if (scopeHasLineHeight) {
3380
+ return false;
3381
+ }
3382
+ const pathHasGridConfig = token.path?.some((part) => {
3383
+ const lower = String(part).toLowerCase();
3384
+ return lower === "columns" || lower === "row-columns";
3385
+ });
3386
+ if (pathHasGridConfig) {
3387
+ return false;
3388
+ }
3389
+ return tokenType === "dimension" || tokenType === "spacing" || tokenType === "sizing";
3390
+ }
3391
+ var dimensionRem = {
3392
+ name: "dimension/rem",
3393
+ type: "value",
3394
+ filter: isDimension,
3395
+ transform: (token, options) => {
3396
+ const value = token.$value ?? token.value;
3397
+ const baseFontSize = options?.basePxFontSize ?? DEFAULT_BASE_FONT_SIZE;
3398
+ if (typeof value === "number") {
3399
+ if (value === 0) {
3400
+ return "0";
3401
+ }
3402
+ return `${value / baseFontSize}rem`;
3403
+ }
3404
+ if (typeof value === "string" && value.endsWith("px")) {
3405
+ const numValue = Number.parseFloat(value);
3406
+ if (numValue === 0) {
3407
+ return "0";
3408
+ }
3409
+ return `${numValue / baseFontSize}rem`;
3410
+ }
3411
+ return value;
3412
+ }
3413
+ };
3414
+
3415
+ // src/tokens/style-dictionary/transforms/font-weight.ts
3416
+ var NAMED_WEIGHTS = {
3417
+ thin: 100,
3418
+ hairline: 100,
3419
+ extralight: 200,
3420
+ ultralight: 200,
3421
+ light: 300,
3422
+ normal: 400,
3423
+ regular: 400,
3424
+ medium: 500,
3425
+ semibold: 600,
3426
+ demibold: 600,
3427
+ bold: 700,
3428
+ extrabold: 800,
3429
+ ultrabold: 800,
3430
+ black: 900,
3431
+ heavy: 900
3432
+ };
3433
+ function isFontWeight(token) {
3434
+ const tokenType = token.$type ?? token.type;
3435
+ if (tokenType === "fontWeight" || tokenType === "number") {
3436
+ return true;
3437
+ }
3438
+ const pathHasFontWeight = token.path?.some((part) => {
3439
+ const lower = String(part).toLowerCase();
3440
+ return lower === "fontweight" || lower.includes("font-weight") || lower.includes("fontweight");
3441
+ });
3442
+ return pathHasFontWeight ?? false;
3443
+ }
3444
+ var fontWeightUnitless = {
3445
+ name: "fontWeight/unitless",
3446
+ type: "value",
3447
+ filter: isFontWeight,
3448
+ transform: (token) => {
3449
+ const value = token.$value ?? token.value;
3450
+ if (typeof value === "number") {
3451
+ return value;
3452
+ }
3453
+ if (typeof value === "string") {
3454
+ const parsed = Number.parseInt(value, 10);
3455
+ if (!Number.isNaN(parsed)) {
3456
+ return parsed;
3457
+ }
3458
+ const normalized = value.toLowerCase().replace(/[^a-z]/g, "");
3459
+ if (Object.hasOwn(NAMED_WEIGHTS, normalized)) {
3460
+ const namedWeight = NAMED_WEIGHTS[normalized];
3461
+ if (namedWeight !== void 0) {
3462
+ return namedWeight;
3463
+ }
3464
+ }
3465
+ }
3466
+ return value;
3467
+ }
3468
+ };
3469
+
3470
+ // src/tokens/style-dictionary/transforms/line-height.ts
3471
+ var DEFAULT_BASE_FONT_SIZE2 = 16;
3472
+ function isLineHeight(token) {
3473
+ const tokenType = token.$type ?? token.type;
3474
+ if (tokenType === "lineHeight") {
3475
+ return true;
3476
+ }
3477
+ const pathHasLineHeight = token.path?.some((part) => {
3478
+ const lower = String(part).toLowerCase();
3479
+ return lower === "lineheight" || lower.includes("line-height") || lower.includes("lineheight");
3480
+ });
3481
+ const scopeHasLineHeight = token.$scopes?.includes("LINE_HEIGHT");
3482
+ return (pathHasLineHeight ?? false) || (scopeHasLineHeight ?? false);
3483
+ }
3484
+ var lineHeightUnitless = {
3485
+ name: "lineHeight/unitless",
3486
+ type: "value",
3487
+ filter: isLineHeight,
3488
+ transform: (token) => {
3489
+ const value = token.$value ?? token.value;
3490
+ if (typeof value === "number") {
3491
+ if (value <= 3) {
3492
+ return value;
3493
+ }
3494
+ return value / DEFAULT_BASE_FONT_SIZE2;
3495
+ }
3496
+ if (typeof value === "string" && value.endsWith("%")) {
3497
+ return Number.parseFloat(value) / 100;
3498
+ }
3499
+ if (typeof value === "string") {
3500
+ const numValue = Number.parseFloat(value);
3501
+ if (!Number.isNaN(numValue)) {
3502
+ if (numValue <= 3) {
3503
+ return numValue;
3504
+ }
3505
+ return numValue / DEFAULT_BASE_FONT_SIZE2;
3506
+ }
3507
+ }
3508
+ return value;
3509
+ }
3510
+ };
3511
+
3512
+ // src/tokens/style-dictionary/transforms/name.ts
3513
+ var nameKebab = {
3514
+ name: "name/kebab",
3515
+ type: "name",
3516
+ transform: (token) => {
3517
+ return token.path.join("-").replace(/_/g, "-").toLowerCase();
3518
+ }
3519
+ };
3520
+
3521
+ // src/tokens/style-dictionary/transforms/index.ts
3522
+ var builtInTransforms = [
3523
+ fontWeightUnitless,
3524
+ lineHeightUnitless,
3525
+ dimensionRem,
3526
+ nameKebab
3527
+ ];
3528
+ function registerTransforms(sd, customTransforms = []) {
3529
+ const allTransforms = [...builtInTransforms, ...customTransforms];
3530
+ for (const transform of allTransforms) {
3531
+ sd.registerTransform({
3532
+ name: transform.name,
3533
+ type: transform.type,
3534
+ filter: transform.filter,
3535
+ transform: transform.transform
3536
+ });
3537
+ }
3538
+ }
3539
+
3540
+ // src/tokens/style-dictionary/config.ts
3541
+ var DEFAULT_SOURCE_PATTERNS = [
3542
+ "collections/color/*.json",
3543
+ "collections/typography/*.json",
3544
+ "collections/spacing/*.json",
3545
+ "collections/border/*.json",
3546
+ "collections/shadow/*.json",
3547
+ "collections/layout/*.json"
3548
+ ];
3549
+ var DEFAULT_PLATFORMS = ["css", "js", "ts", "scss", "scss-dist", "json"];
3550
+ function createStyleDictionaryConfig(dsaiConfig, options = {}) {
3551
+ const {
3552
+ source = DEFAULT_SOURCE_PATTERNS,
3553
+ prefix = dsaiConfig.tokens.prefix,
3554
+ buildPath = dsaiConfig.tokens.outputDir,
3555
+ baseFontSize = dsaiConfig.tokens.baseFontSize ?? 16,
3556
+ outputReferences = dsaiConfig.tokens.outputReferences ?? true,
3557
+ platforms = DEFAULT_PLATFORMS,
3558
+ verbose = dsaiConfig.global.debug ?? false
3559
+ } = options;
3560
+ const config = {
3561
+ log: {
3562
+ verbosity: verbose ? "verbose" : "default",
3563
+ warnings: "warn",
3564
+ errors: "error"
3565
+ },
3566
+ preprocessors: ["fix-references"],
3567
+ source,
3568
+ platforms: {}
3569
+ };
3570
+ const normalizedBuildPath = buildPath.endsWith("/") ? buildPath : `${buildPath}/`;
3571
+ const platformConfigs = config.platforms ?? {};
3572
+ if (platforms.includes("css")) {
3573
+ platformConfigs["css"] = {
3574
+ transformGroup: "custom/css",
3575
+ buildPath: `${normalizedBuildPath}css/`,
3576
+ files: [
3577
+ {
3578
+ destination: "variables.css",
3579
+ format: "css/variables-with-comments",
3580
+ options: {
3581
+ prefix,
3582
+ outputReferences
3583
+ }
3584
+ }
3585
+ ]
3586
+ };
3587
+ }
3588
+ if (platforms.includes("js")) {
3589
+ platformConfigs["js"] = {
3590
+ transformGroup: "custom/js",
3591
+ buildPath: `${normalizedBuildPath}js/`,
3592
+ files: [
3593
+ {
3594
+ destination: "tokens.js",
3595
+ format: "javascript/es6",
3596
+ options: { outputReferences }
3597
+ },
3598
+ {
3599
+ destination: "tokens.cjs",
3600
+ format: "javascript/module",
3601
+ options: { outputReferences }
3602
+ }
3603
+ ]
3604
+ };
3605
+ }
3606
+ if (platforms.includes("ts")) {
3607
+ platformConfigs["ts"] = {
3608
+ transformGroup: "custom/js",
3609
+ buildPath: `${normalizedBuildPath}ts/`,
3610
+ files: [
3611
+ {
3612
+ destination: "tokens.ts",
3613
+ format: "javascript/es6",
3614
+ options: { outputReferences }
3615
+ },
3616
+ {
3617
+ destination: "tokens.d.ts",
3618
+ format: "typescript/declarations"
3619
+ }
3620
+ ]
3621
+ };
3622
+ }
3623
+ if (platforms.includes("scss")) {
3624
+ platformConfigs["scss"] = {
3625
+ transformGroup: "custom/scss",
3626
+ buildPath: "src/scss/",
3627
+ files: [
3628
+ {
3629
+ destination: "_variables.scss",
3630
+ format: "scss/variables",
3631
+ options: {
3632
+ outputReferences,
3633
+ basePxFontSize: baseFontSize
3634
+ }
3635
+ }
3636
+ ]
3637
+ };
3638
+ }
3639
+ if (platforms.includes("scss-dist")) {
3640
+ platformConfigs["scss-dist"] = {
3641
+ transformGroup: "custom/scss",
3642
+ buildPath: `${normalizedBuildPath}scss/`,
3643
+ files: [
3644
+ {
3645
+ destination: "_variables.scss",
3646
+ format: "scss/variables",
3647
+ options: {
3648
+ outputReferences,
3649
+ basePxFontSize: baseFontSize
3650
+ }
3651
+ }
3652
+ ]
3653
+ };
3654
+ }
3655
+ if (platforms.includes("json")) {
3656
+ platformConfigs["json"] = {
3657
+ transformGroup: "js",
3658
+ buildPath: `${normalizedBuildPath}json/`,
3659
+ files: [
3660
+ { destination: "tokens.json", format: "json/flat" },
3661
+ { destination: "tokens-nested.json", format: "json/nested" }
3662
+ ]
3663
+ };
3664
+ }
3665
+ config.platforms = platformConfigs;
3666
+ return config;
3667
+ }
3668
+ function registerAll(sd, options = {}) {
3669
+ const { customTransforms = [], customFormats = [], customPreprocessors = [] } = options;
3670
+ registerTransforms(sd, customTransforms);
3671
+ registerTransformGroups(sd);
3672
+ registerFormats(sd, customFormats);
3673
+ registerPreprocessors(sd, customPreprocessors);
3674
+ }
3675
+ function setupStyleDictionary(sd, dsaiConfig, options = {}) {
3676
+ registerAll(sd, options);
3677
+ return createStyleDictionaryConfig(dsaiConfig, options);
3678
+ }
3679
+
3680
+ export { DEFAULT_CLEAN_DIRECTORIES, DEFAULT_FILE_NAMES, VALID_TOKEN_TYPES, addScssImportHeader, buildTokens, buildTokensCLI, builtInFormats, builtInPreprocessors, builtInTransforms, cleanTokenOutputs, cleanTokensCLI, createBundle, createBundleFromFiles, createBundles, createFixReferencesPreprocessor, createOutputConfig, createStyleDictionaryConfig, cssTransformGroup, cssVariablesWithComments, detectModes as detectFigmaModes, detectModes2 as detectTransformModes, dimensionRem, ensureOutputDirs, filterFiles, fixReferences, fontWeightUnitless, getDefaultCssDir, getDefaultFiles, getDefaultIgnorePatterns, getDefaultSyncPaths, getDefaultTransformations, getSDTokenType, getSDTokenValue, getTokenDescription, getTokenType, getTokenValue, hasDTCGValue, isDTCGToken, isLegacyToken, isSDToken, isToken, isTokenReference, isValidTokenType, jsTransformGroup, lineHeightUnitless, loadContent, mergeCollections, mergeCollectionsCLI, mergeContent, nameKebab, parseTokenReference, postprocessCLI, postprocessCss, postprocessCssFiles, processScssImportHeader, registerAll, registerFormats, registerPreprocessors, registerTransformGroups, registerTransforms, replacePlaceholders, resolveAllOutputPaths, resolveOutputPath, runBuildCLI, scanDirectories, scssTransformGroup, setupStyleDictionary, sortFiles, syncTokens, syncTokensCLI, toDTCGToken, transformGroups, transformToken, transformTokenTree, transformTokens, transformTokensCLI, transformType, transformValue, typescriptDeclarations, validateFigmaCLI, validateFigmaExports, validateFigmaFile, validateOutputPaths, validateTokens, validateTokensCLI };
3681
+ //# sourceMappingURL=index.js.map
3682
+ //# sourceMappingURL=index.js.map