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