@nice2dev/design-tokens 1.3.17 → 1.4.0
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/dist/cli/index.js +945 -40
- package/dist/index.js +1193 -62
- package/package.json +2 -2
- package/dist/chunk-PF2R7I6P.js +0 -1242
- package/dist/cli/index.cjs +0 -3027
- package/dist/cli/index.d.cts +0 -1
- /package/dist/{chunk-6OYMWYZS.js → cli/chunk-6OYMWYZS.js} +0 -0
- /package/dist/{esm-LPVAZ4IA.js → cli/esm-LPVAZ4IA.js} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
createGenerator
|
|
4
|
-
} from "../chunk-PF2R7I6P.js";
|
|
5
2
|
import {
|
|
6
3
|
__commonJS,
|
|
7
4
|
__require
|
|
8
|
-
} from "
|
|
5
|
+
} from "./chunk-6OYMWYZS.js";
|
|
9
6
|
|
|
10
7
|
// package.json
|
|
11
8
|
var require_package = __commonJS({
|
|
12
9
|
"package.json"(exports, module) {
|
|
13
10
|
module.exports = {
|
|
14
11
|
name: "@nice2dev/design-tokens",
|
|
15
|
-
version: "1.
|
|
12
|
+
version: "1.4.0",
|
|
16
13
|
description: "W3C Design Tokens for Nice2Dev UI with CLI generator",
|
|
17
14
|
type: "module",
|
|
18
15
|
main: "./dist/index.cjs",
|
|
@@ -36,7 +33,7 @@ var require_package = __commonJS({
|
|
|
36
33
|
"tokens"
|
|
37
34
|
],
|
|
38
35
|
scripts: {
|
|
39
|
-
build: "tsup src/index.ts src/cli/index.ts --format esm
|
|
36
|
+
build: "tsup src/index.ts --format esm,cjs --dts --clean && tsup src/cli/index.ts --format esm --dts --out-dir dist/cli",
|
|
40
37
|
dev: "tsup src/index.ts src/cli/index.ts --format esm,cjs --dts --watch",
|
|
41
38
|
generate: "node dist/cli/index.js generate",
|
|
42
39
|
test: "vitest run --passWithNoTests",
|
|
@@ -73,9 +70,917 @@ var require_package = __commonJS({
|
|
|
73
70
|
});
|
|
74
71
|
|
|
75
72
|
// src/cli/index.ts
|
|
76
|
-
import * as
|
|
77
|
-
import * as
|
|
73
|
+
import * as fs3 from "fs";
|
|
74
|
+
import * as path2 from "path";
|
|
78
75
|
import { Command } from "commander";
|
|
76
|
+
|
|
77
|
+
// src/generator.ts
|
|
78
|
+
import * as fs2 from "fs";
|
|
79
|
+
import * as path from "path";
|
|
80
|
+
|
|
81
|
+
// src/parser.ts
|
|
82
|
+
import * as fs from "fs";
|
|
83
|
+
function isDesignToken(obj) {
|
|
84
|
+
return typeof obj === "object" && obj !== null && "$value" in obj && obj.$value !== void 0;
|
|
85
|
+
}
|
|
86
|
+
function isTokenGroup(obj) {
|
|
87
|
+
if (typeof obj !== "object" || obj === null) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (isDesignToken(obj)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return Object.keys(obj).some((key) => !key.startsWith("$"));
|
|
94
|
+
}
|
|
95
|
+
function isReference(value) {
|
|
96
|
+
if (typeof value !== "string") {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return /^\{[\w.]+\}$/.test(value);
|
|
100
|
+
}
|
|
101
|
+
function extractReferencePath(ref) {
|
|
102
|
+
const match = ref.match(/^\{([\w.]+)\}$/);
|
|
103
|
+
if (!match) {
|
|
104
|
+
throw new Error(`Invalid reference: ${ref}`);
|
|
105
|
+
}
|
|
106
|
+
return match[1].split(".");
|
|
107
|
+
}
|
|
108
|
+
function pathToCssVariable(path3, prefix = "nice") {
|
|
109
|
+
return `--${prefix}-${path3.join("-")}`;
|
|
110
|
+
}
|
|
111
|
+
function pathToString(path3) {
|
|
112
|
+
return path3.join(".");
|
|
113
|
+
}
|
|
114
|
+
function parseTokenFile(filePath) {
|
|
115
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
116
|
+
const parsed = JSON.parse(content);
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
function parseTokenFiles(filePaths) {
|
|
120
|
+
const merged = {};
|
|
121
|
+
for (const filePath of filePaths) {
|
|
122
|
+
const tokens = parseTokenFile(filePath);
|
|
123
|
+
mergeTokenFiles(merged, tokens);
|
|
124
|
+
}
|
|
125
|
+
return merged;
|
|
126
|
+
}
|
|
127
|
+
function mergeTokenFiles(target, source) {
|
|
128
|
+
for (const [key, value] of Object.entries(source)) {
|
|
129
|
+
if (key.startsWith("$")) {
|
|
130
|
+
if (!(key in target)) {
|
|
131
|
+
target[key] = value;
|
|
132
|
+
}
|
|
133
|
+
} else if (typeof value === "object" && value !== null) {
|
|
134
|
+
if (!(key in target)) {
|
|
135
|
+
target[key] = {};
|
|
136
|
+
}
|
|
137
|
+
const targetValue = target[key];
|
|
138
|
+
if (typeof targetValue === "object" && targetValue !== null) {
|
|
139
|
+
mergeTokenFiles(targetValue, value);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
target[key] = value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function flattenTokens(tokenFile, prefix = [], inheritedType) {
|
|
147
|
+
const tokens = [];
|
|
148
|
+
const currentType = tokenFile.$type ?? inheritedType;
|
|
149
|
+
for (const [key, value] of Object.entries(tokenFile)) {
|
|
150
|
+
if (key.startsWith("$")) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const currentPath = [...prefix, key];
|
|
154
|
+
if (isDesignToken(value)) {
|
|
155
|
+
const token = value;
|
|
156
|
+
const tokenType = token.$type ?? currentType;
|
|
157
|
+
const references = [];
|
|
158
|
+
findReferences(token.$value, references);
|
|
159
|
+
tokens.push({
|
|
160
|
+
path: currentPath,
|
|
161
|
+
name: key,
|
|
162
|
+
value: token.$value,
|
|
163
|
+
resolvedValue: "",
|
|
164
|
+
// Will be populated by resolver
|
|
165
|
+
type: tokenType,
|
|
166
|
+
description: token.$description,
|
|
167
|
+
cssVariable: pathToCssVariable(currentPath),
|
|
168
|
+
references: references.length > 0 ? references : void 0
|
|
169
|
+
});
|
|
170
|
+
} else if (isTokenGroup(value)) {
|
|
171
|
+
const group = value;
|
|
172
|
+
const groupType = group.$type ?? currentType;
|
|
173
|
+
tokens.push(...flattenTokens(group, currentPath, groupType));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return tokens;
|
|
177
|
+
}
|
|
178
|
+
function findReferences(value, refs) {
|
|
179
|
+
if (typeof value === "string" && isReference(value)) {
|
|
180
|
+
refs.push(extractReferencePath(value).join("."));
|
|
181
|
+
} else if (typeof value === "object" && value !== null) {
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
for (const item of value) {
|
|
184
|
+
findReferences(item, refs);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
for (const v of Object.values(value)) {
|
|
188
|
+
findReferences(v, refs);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function createTokenCollection(tokens) {
|
|
194
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
195
|
+
const byName = /* @__PURE__ */ new Map();
|
|
196
|
+
for (const token of tokens) {
|
|
197
|
+
const pathStr = pathToString(token.path);
|
|
198
|
+
byPath.set(pathStr, token);
|
|
199
|
+
const existing = byName.get(token.name);
|
|
200
|
+
if (!existing || token.path.length > existing.path.length) {
|
|
201
|
+
byName.set(token.name, token);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { tokens, byPath, byName };
|
|
205
|
+
}
|
|
206
|
+
function getCategories(collection) {
|
|
207
|
+
const categories = /* @__PURE__ */ new Set();
|
|
208
|
+
for (const token of collection.tokens) {
|
|
209
|
+
if (token.path.length > 0) {
|
|
210
|
+
categories.add(token.path[0]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return Array.from(categories);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/resolver.ts
|
|
217
|
+
var MAX_RESOLUTION_DEPTH = 10;
|
|
218
|
+
function resolveTokens(collection) {
|
|
219
|
+
const resolvedTokens = collection.tokens.map((token) => {
|
|
220
|
+
const resolved = { ...token };
|
|
221
|
+
resolved.resolvedValue = resolveTokenValue(token.value, collection, 0);
|
|
222
|
+
return resolved;
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
tokens: resolvedTokens,
|
|
226
|
+
byPath: new Map(resolvedTokens.map((t) => [pathToString(t.path), t])),
|
|
227
|
+
byName: new Map(resolvedTokens.map((t) => [t.name, t]))
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function resolveTokenValue(value, collection, depth) {
|
|
231
|
+
if (depth > MAX_RESOLUTION_DEPTH) {
|
|
232
|
+
throw new Error(`Maximum reference depth exceeded while resolving token`);
|
|
233
|
+
}
|
|
234
|
+
if (typeof value === "string") {
|
|
235
|
+
if (isReference(value)) {
|
|
236
|
+
const refPath = extractReferencePath(value);
|
|
237
|
+
const refPathStr = pathToString(refPath);
|
|
238
|
+
const refToken = collection.byPath.get(refPathStr);
|
|
239
|
+
if (!refToken) {
|
|
240
|
+
console.warn(`Reference not found: ${value}`);
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
return resolveTokenValue(refToken.value, collection, depth + 1);
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
if (typeof value === "number") {
|
|
248
|
+
return String(value);
|
|
249
|
+
}
|
|
250
|
+
if (Array.isArray(value)) {
|
|
251
|
+
return value.map((v) => {
|
|
252
|
+
if (typeof v === "string") {
|
|
253
|
+
return v.includes(" ") ? `"${v}"` : v;
|
|
254
|
+
}
|
|
255
|
+
return String(v);
|
|
256
|
+
}).join(", ");
|
|
257
|
+
}
|
|
258
|
+
if (isShadowValue(value)) {
|
|
259
|
+
return resolveShadowValue(value, collection, depth);
|
|
260
|
+
}
|
|
261
|
+
if (Array.isArray(value) && value.length > 0 && isShadowValue(value[0])) {
|
|
262
|
+
return value.map((s) => resolveShadowValue(s, collection, depth)).join(", ");
|
|
263
|
+
}
|
|
264
|
+
if (isTypographyValue(value)) {
|
|
265
|
+
return resolveTypographyValue(value, collection, depth);
|
|
266
|
+
}
|
|
267
|
+
if (isBorderValue(value)) {
|
|
268
|
+
return resolveBorderValue(value, collection, depth);
|
|
269
|
+
}
|
|
270
|
+
return JSON.stringify(value);
|
|
271
|
+
}
|
|
272
|
+
function isShadowValue(value) {
|
|
273
|
+
if (typeof value !== "object" || value === null) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
return "offsetX" in value && "offsetY" in value && "blur" in value;
|
|
277
|
+
}
|
|
278
|
+
function resolveShadowValue(shadow, collection, depth) {
|
|
279
|
+
const offsetX = resolveTokenValue(shadow.offsetX, collection, depth);
|
|
280
|
+
const offsetY = resolveTokenValue(shadow.offsetY, collection, depth);
|
|
281
|
+
const blur = resolveTokenValue(shadow.blur, collection, depth);
|
|
282
|
+
const spread = resolveTokenValue(shadow.spread, collection, depth);
|
|
283
|
+
const color = resolveTokenValue(shadow.color, collection, depth);
|
|
284
|
+
const inset = shadow.inset ? "inset " : "";
|
|
285
|
+
return `${inset}${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
|
|
286
|
+
}
|
|
287
|
+
function isTypographyValue(value) {
|
|
288
|
+
if (typeof value !== "object" || value === null) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
return "fontFamily" in value && "fontSize" in value;
|
|
292
|
+
}
|
|
293
|
+
function resolveTypographyValue(typography, collection, depth) {
|
|
294
|
+
const fontFamily = resolveTokenValue(typography.fontFamily, collection, depth);
|
|
295
|
+
const fontSize = resolveTokenValue(typography.fontSize, collection, depth);
|
|
296
|
+
const fontWeight = resolveTokenValue(typography.fontWeight, collection, depth);
|
|
297
|
+
const lineHeight = typography.lineHeight ? resolveTokenValue(typography.lineHeight, collection, depth) : "normal";
|
|
298
|
+
const fontStyle = typography.fontStyle || "normal";
|
|
299
|
+
return `${fontStyle} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
|
|
300
|
+
}
|
|
301
|
+
function isBorderValue(value) {
|
|
302
|
+
if (typeof value !== "object" || value === null) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
return "color" in value && "width" in value && "style" in value;
|
|
306
|
+
}
|
|
307
|
+
function resolveBorderValue(border, collection, depth) {
|
|
308
|
+
const width = resolveTokenValue(border.width, collection, depth);
|
|
309
|
+
const style = resolveTokenValue(border.style, collection, depth);
|
|
310
|
+
const color = resolveTokenValue(border.color, collection, depth);
|
|
311
|
+
return `${width} ${style} ${color}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/transforms.ts
|
|
315
|
+
function transformToCss(collection, prefix = "nice") {
|
|
316
|
+
const lines = [];
|
|
317
|
+
for (const token of collection.tokens) {
|
|
318
|
+
const varName = `--${prefix}-${token.path.join("-")}`;
|
|
319
|
+
lines.push(` ${varName}: ${token.resolvedValue};`);
|
|
320
|
+
}
|
|
321
|
+
return `:root {
|
|
322
|
+
${lines.join("\n")}
|
|
323
|
+
}`;
|
|
324
|
+
}
|
|
325
|
+
function transformToScss(collection, prefix = "nice") {
|
|
326
|
+
const lines = [];
|
|
327
|
+
for (const token of collection.tokens) {
|
|
328
|
+
const varName = `$${prefix}-${token.path.join("-")}`;
|
|
329
|
+
lines.push(`${varName}: ${token.resolvedValue};`);
|
|
330
|
+
}
|
|
331
|
+
return lines.join("\n");
|
|
332
|
+
}
|
|
333
|
+
function transformToJs(collection, variableName = "tokens") {
|
|
334
|
+
const obj = buildTokenObject(collection);
|
|
335
|
+
return `export const ${variableName} = ${JSON.stringify(obj, null, 2)};`;
|
|
336
|
+
}
|
|
337
|
+
function transformToTs(collection, variableName = "tokens") {
|
|
338
|
+
const obj = buildTokenObject(collection);
|
|
339
|
+
const types = generateTokenTypes(collection);
|
|
340
|
+
return `${types}
|
|
341
|
+
|
|
342
|
+
export const ${variableName}: Tokens = ${JSON.stringify(obj, null, 2)};`;
|
|
343
|
+
}
|
|
344
|
+
function buildTokenObject(collection) {
|
|
345
|
+
const result = {};
|
|
346
|
+
for (const token of collection.tokens) {
|
|
347
|
+
let current = result;
|
|
348
|
+
for (let i = 0; i < token.path.length - 1; i++) {
|
|
349
|
+
const key = token.path[i];
|
|
350
|
+
if (!(key in current)) {
|
|
351
|
+
current[key] = {};
|
|
352
|
+
}
|
|
353
|
+
current = current[key];
|
|
354
|
+
}
|
|
355
|
+
const lastKey = token.path[token.path.length - 1];
|
|
356
|
+
current[lastKey] = token.resolvedValue;
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
function generateTokenTypes(collection) {
|
|
361
|
+
const typeObj = buildTypeStructure(collection);
|
|
362
|
+
return `interface Tokens ${formatTypeStructure(typeObj)}`;
|
|
363
|
+
}
|
|
364
|
+
function buildTypeStructure(collection) {
|
|
365
|
+
const result = {};
|
|
366
|
+
for (const token of collection.tokens) {
|
|
367
|
+
let current = result;
|
|
368
|
+
for (let i = 0; i < token.path.length - 1; i++) {
|
|
369
|
+
const key = token.path[i];
|
|
370
|
+
if (!(key in current)) {
|
|
371
|
+
current[key] = {};
|
|
372
|
+
}
|
|
373
|
+
current = current[key];
|
|
374
|
+
}
|
|
375
|
+
const lastKey = token.path[token.path.length - 1];
|
|
376
|
+
current[lastKey] = "string";
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
function formatTypeStructure(obj, indent = 0) {
|
|
381
|
+
const spaces = " ".repeat(indent);
|
|
382
|
+
const lines = ["{"];
|
|
383
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
384
|
+
const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
385
|
+
if (typeof value === "string") {
|
|
386
|
+
lines.push(`${spaces} ${safeKey}: ${value};`);
|
|
387
|
+
} else {
|
|
388
|
+
lines.push(
|
|
389
|
+
`${spaces} ${safeKey}: ${formatTypeStructure(value, indent + 1)};`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
lines.push(`${spaces}}`);
|
|
394
|
+
return lines.join("\n");
|
|
395
|
+
}
|
|
396
|
+
function transformToTailwind(collection) {
|
|
397
|
+
const colors = {};
|
|
398
|
+
const spacing = {};
|
|
399
|
+
const fontSize = {};
|
|
400
|
+
const fontFamily = {};
|
|
401
|
+
const borderRadius = {};
|
|
402
|
+
const boxShadow = {};
|
|
403
|
+
for (const token of collection.tokens) {
|
|
404
|
+
const path3 = token.path;
|
|
405
|
+
const value = token.resolvedValue;
|
|
406
|
+
if (path3[0] === "color" || path3[0] === "colors") {
|
|
407
|
+
setNestedValue(colors, path3.slice(1), value);
|
|
408
|
+
} else if (path3[0] === "spacing") {
|
|
409
|
+
spacing[path3.slice(1).join("-")] = value;
|
|
410
|
+
} else if (path3[0] === "typography" && path3[1] === "fontSize") {
|
|
411
|
+
fontSize[path3[2]] = value;
|
|
412
|
+
} else if (path3[0] === "typography" && path3[1] === "fontFamily") {
|
|
413
|
+
fontFamily[path3[2]] = value.split(",").map((f) => f.trim());
|
|
414
|
+
} else if (path3[0] === "effects" && path3[1] === "radius") {
|
|
415
|
+
borderRadius[path3[2]] = value;
|
|
416
|
+
} else if (path3[0] === "effects" && path3[1] === "shadow") {
|
|
417
|
+
boxShadow[path3[2]] = value;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const config = {
|
|
421
|
+
theme: {
|
|
422
|
+
extend: {
|
|
423
|
+
colors,
|
|
424
|
+
spacing,
|
|
425
|
+
fontSize,
|
|
426
|
+
fontFamily,
|
|
427
|
+
borderRadius,
|
|
428
|
+
boxShadow
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return `module.exports = ${JSON.stringify(config, null, 2)};`;
|
|
433
|
+
}
|
|
434
|
+
function setNestedValue(obj, path3, value) {
|
|
435
|
+
let current = obj;
|
|
436
|
+
for (let i = 0; i < path3.length - 1; i++) {
|
|
437
|
+
const key = path3[i];
|
|
438
|
+
if (!(key in current)) {
|
|
439
|
+
current[key] = {};
|
|
440
|
+
}
|
|
441
|
+
current = current[key];
|
|
442
|
+
}
|
|
443
|
+
current[path3[path3.length - 1]] = value;
|
|
444
|
+
}
|
|
445
|
+
function applyThemeOverrides(baseCollection, overrides) {
|
|
446
|
+
const mergedTokens = [...baseCollection.tokens];
|
|
447
|
+
for (const override of overrides.tokens) {
|
|
448
|
+
const pathStr = pathToString(override.path);
|
|
449
|
+
const existingIndex = mergedTokens.findIndex((t) => pathToString(t.path) === pathStr);
|
|
450
|
+
if (existingIndex >= 0) {
|
|
451
|
+
mergedTokens[existingIndex] = { ...mergedTokens[existingIndex], ...override };
|
|
452
|
+
} else {
|
|
453
|
+
mergedTokens.push(override);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
tokens: mergedTokens,
|
|
458
|
+
byPath: new Map(mergedTokens.map((t) => [pathToString(t.path), t])),
|
|
459
|
+
byName: new Map(mergedTokens.map((t) => [t.name, t]))
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/themes.ts
|
|
464
|
+
var defaultThemes = {
|
|
465
|
+
light: {
|
|
466
|
+
name: "light",
|
|
467
|
+
displayName: "Light",
|
|
468
|
+
mode: "light",
|
|
469
|
+
selector: ':root, [data-theme="light"]'
|
|
470
|
+
},
|
|
471
|
+
dark: {
|
|
472
|
+
name: "dark",
|
|
473
|
+
displayName: "Dark",
|
|
474
|
+
mode: "dark",
|
|
475
|
+
selector: '[data-theme="dark"]',
|
|
476
|
+
mediaQuery: "(prefers-color-scheme: dark)",
|
|
477
|
+
extends: "light"
|
|
478
|
+
},
|
|
479
|
+
"high-contrast": {
|
|
480
|
+
name: "high-contrast",
|
|
481
|
+
displayName: "High Contrast",
|
|
482
|
+
mode: "high-contrast",
|
|
483
|
+
selector: '[data-theme="high-contrast"]',
|
|
484
|
+
mediaQuery: "(prefers-contrast: more)",
|
|
485
|
+
extends: "light"
|
|
486
|
+
},
|
|
487
|
+
"high-contrast-dark": {
|
|
488
|
+
name: "high-contrast-dark",
|
|
489
|
+
displayName: "High Contrast Dark",
|
|
490
|
+
mode: "high-contrast-dark",
|
|
491
|
+
selector: '[data-theme="high-contrast-dark"]',
|
|
492
|
+
extends: "dark"
|
|
493
|
+
},
|
|
494
|
+
dim: {
|
|
495
|
+
name: "dim",
|
|
496
|
+
displayName: "Dim",
|
|
497
|
+
mode: "dim",
|
|
498
|
+
selector: '[data-theme="dim"]',
|
|
499
|
+
extends: "dark"
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
function createTheme(name, baseTokens, overrideTokens, config) {
|
|
503
|
+
const tokens = overrideTokens ? applyThemeOverrides(baseTokens, overrideTokens) : baseTokens;
|
|
504
|
+
const resolvedTokens = resolveTokens(tokens);
|
|
505
|
+
const themeConfig = { ...defaultThemes.light, ...config, name };
|
|
506
|
+
return {
|
|
507
|
+
name,
|
|
508
|
+
tokens: resolvedTokens,
|
|
509
|
+
extends: themeConfig.extends,
|
|
510
|
+
selector: themeConfig.selector,
|
|
511
|
+
mediaQuery: themeConfig.mediaQuery
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function generateThemesCss(themes, prefix = "nice") {
|
|
515
|
+
const sections = [];
|
|
516
|
+
for (const theme of themes) {
|
|
517
|
+
const vars = generateThemeVariables(theme, prefix);
|
|
518
|
+
sections.push(`${theme.selector} {
|
|
519
|
+
${vars}
|
|
520
|
+
}`);
|
|
521
|
+
if (theme.mediaQuery) {
|
|
522
|
+
sections.push(
|
|
523
|
+
`@media ${theme.mediaQuery} {
|
|
524
|
+
:root {
|
|
525
|
+
${vars.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
526
|
+
}
|
|
527
|
+
}`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return sections.join("\n\n");
|
|
532
|
+
}
|
|
533
|
+
function generateThemeVariables(theme, prefix) {
|
|
534
|
+
const lines = [];
|
|
535
|
+
for (const token of theme.tokens.tokens) {
|
|
536
|
+
const varName = `--${prefix}-${token.path.join("-")}`;
|
|
537
|
+
lines.push(` ${varName}: ${token.resolvedValue};`);
|
|
538
|
+
}
|
|
539
|
+
return lines.join("\n");
|
|
540
|
+
}
|
|
541
|
+
function generateThemeSwitcher() {
|
|
542
|
+
return `
|
|
543
|
+
/**
|
|
544
|
+
* Nice2Dev Theme Switcher
|
|
545
|
+
* Auto-generated by @nice2dev/design-tokens
|
|
546
|
+
*/
|
|
547
|
+
|
|
548
|
+
const THEME_KEY = 'nice-theme';
|
|
549
|
+
const THEMES = ['light', 'dark', 'high-contrast', 'high-contrast-dark', 'dim'];
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Get current theme
|
|
553
|
+
*/
|
|
554
|
+
function getTheme() {
|
|
555
|
+
return document.documentElement.getAttribute('data-theme') || 'light';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Set theme
|
|
560
|
+
*/
|
|
561
|
+
function setTheme(theme) {
|
|
562
|
+
if (!THEMES.includes(theme)) {
|
|
563
|
+
console.warn(\`Invalid theme: \${theme}\`);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
568
|
+
localStorage.setItem(THEME_KEY, theme);
|
|
569
|
+
|
|
570
|
+
// Dispatch event
|
|
571
|
+
window.dispatchEvent(new CustomEvent('nice-theme-change', { detail: { theme } }));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Toggle between light and dark
|
|
576
|
+
*/
|
|
577
|
+
function toggleTheme() {
|
|
578
|
+
const current = getTheme();
|
|
579
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
580
|
+
setTheme(next);
|
|
581
|
+
return next;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Use system preference
|
|
586
|
+
*/
|
|
587
|
+
function useSystemTheme() {
|
|
588
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
589
|
+
const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
|
|
590
|
+
|
|
591
|
+
let theme = 'light';
|
|
592
|
+
if (prefersDark && prefersHighContrast) {
|
|
593
|
+
theme = 'high-contrast-dark';
|
|
594
|
+
} else if (prefersDark) {
|
|
595
|
+
theme = 'dark';
|
|
596
|
+
} else if (prefersHighContrast) {
|
|
597
|
+
theme = 'high-contrast';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
setTheme(theme);
|
|
601
|
+
return theme;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Initialize theme on page load
|
|
606
|
+
*/
|
|
607
|
+
function initTheme() {
|
|
608
|
+
const saved = localStorage.getItem(THEME_KEY);
|
|
609
|
+
|
|
610
|
+
if (saved && THEMES.includes(saved)) {
|
|
611
|
+
setTheme(saved);
|
|
612
|
+
} else {
|
|
613
|
+
useSystemTheme();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Listen for system changes
|
|
617
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
618
|
+
if (!localStorage.getItem(THEME_KEY)) {
|
|
619
|
+
useSystemTheme();
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Auto-init
|
|
625
|
+
if (typeof document !== 'undefined') {
|
|
626
|
+
if (document.readyState === 'loading') {
|
|
627
|
+
document.addEventListener('DOMContentLoaded', initTheme);
|
|
628
|
+
} else {
|
|
629
|
+
initTheme();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export { getTheme, setTheme, toggleTheme, useSystemTheme, initTheme, THEMES };
|
|
634
|
+
`.trim();
|
|
635
|
+
}
|
|
636
|
+
function generateReactThemeContext() {
|
|
637
|
+
return `
|
|
638
|
+
'use client';
|
|
639
|
+
|
|
640
|
+
import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
|
|
641
|
+
|
|
642
|
+
type Theme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark' | 'dim';
|
|
643
|
+
|
|
644
|
+
interface ThemeContextValue {
|
|
645
|
+
theme: Theme;
|
|
646
|
+
setTheme: (theme: Theme) => void;
|
|
647
|
+
toggleTheme: () => void;
|
|
648
|
+
useSystemTheme: () => void;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
652
|
+
|
|
653
|
+
const THEME_KEY = 'nice-theme';
|
|
654
|
+
const THEMES: Theme[] = ['light', 'dark', 'high-contrast', 'high-contrast-dark', 'dim'];
|
|
655
|
+
|
|
656
|
+
interface ThemeProviderProps {
|
|
657
|
+
children: React.ReactNode;
|
|
658
|
+
defaultTheme?: Theme;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export function ThemeProvider({ children, defaultTheme = 'light' }: ThemeProviderProps) {
|
|
662
|
+
const [theme, setThemeState] = useState<Theme>(defaultTheme);
|
|
663
|
+
|
|
664
|
+
useEffect(() => {
|
|
665
|
+
const saved = localStorage.getItem(THEME_KEY) as Theme | null;
|
|
666
|
+
if (saved && THEMES.includes(saved)) {
|
|
667
|
+
setThemeState(saved);
|
|
668
|
+
document.documentElement.setAttribute('data-theme', saved);
|
|
669
|
+
} else {
|
|
670
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
671
|
+
const initial = prefersDark ? 'dark' : 'light';
|
|
672
|
+
setThemeState(initial);
|
|
673
|
+
document.documentElement.setAttribute('data-theme', initial);
|
|
674
|
+
}
|
|
675
|
+
}, []);
|
|
676
|
+
|
|
677
|
+
const setTheme = useCallback((newTheme: Theme) => {
|
|
678
|
+
setThemeState(newTheme);
|
|
679
|
+
document.documentElement.setAttribute('data-theme', newTheme);
|
|
680
|
+
localStorage.setItem(THEME_KEY, newTheme);
|
|
681
|
+
}, []);
|
|
682
|
+
|
|
683
|
+
const toggleTheme = useCallback(() => {
|
|
684
|
+
setTheme(theme === 'dark' ? 'light' : 'dark');
|
|
685
|
+
}, [theme, setTheme]);
|
|
686
|
+
|
|
687
|
+
const useSystemTheme = useCallback(() => {
|
|
688
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
689
|
+
const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
|
|
690
|
+
|
|
691
|
+
let systemTheme: Theme = 'light';
|
|
692
|
+
if (prefersDark && prefersHighContrast) {
|
|
693
|
+
systemTheme = 'high-contrast-dark';
|
|
694
|
+
} else if (prefersDark) {
|
|
695
|
+
systemTheme = 'dark';
|
|
696
|
+
} else if (prefersHighContrast) {
|
|
697
|
+
systemTheme = 'high-contrast';
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
setTheme(systemTheme);
|
|
701
|
+
localStorage.removeItem(THEME_KEY);
|
|
702
|
+
}, [setTheme]);
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme, useSystemTheme }}>
|
|
706
|
+
{children}
|
|
707
|
+
</ThemeContext.Provider>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export function useTheme(): ThemeContextValue {
|
|
712
|
+
const context = useContext(ThemeContext);
|
|
713
|
+
if (!context) {
|
|
714
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
715
|
+
}
|
|
716
|
+
return context;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
export { THEMES };
|
|
720
|
+
export type { Theme };
|
|
721
|
+
`.trim();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/generator.ts
|
|
725
|
+
var defaultOptions = {
|
|
726
|
+
formats: ["css"],
|
|
727
|
+
outputDir: "./dist",
|
|
728
|
+
prefix: "nice",
|
|
729
|
+
includePrimitives: true,
|
|
730
|
+
separateFiles: false,
|
|
731
|
+
includeDarkMode: true,
|
|
732
|
+
includeComponents: true
|
|
733
|
+
};
|
|
734
|
+
var TokenGenerator = class {
|
|
735
|
+
constructor(options = {}) {
|
|
736
|
+
this.plugins = [];
|
|
737
|
+
this.collection = null;
|
|
738
|
+
this.themes = [];
|
|
739
|
+
this.options = { ...defaultOptions, ...options };
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Add plugin
|
|
743
|
+
*/
|
|
744
|
+
use(plugin) {
|
|
745
|
+
this.plugins.push(plugin);
|
|
746
|
+
return this;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Load tokens from files
|
|
750
|
+
*/
|
|
751
|
+
load(files) {
|
|
752
|
+
const tokenFile = parseTokenFiles(files);
|
|
753
|
+
let processedTokens = [tokenFile];
|
|
754
|
+
for (const plugin of this.plugins) {
|
|
755
|
+
if (plugin.beforeResolve) {
|
|
756
|
+
processedTokens = plugin.beforeResolve(processedTokens);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const flatTokens = flattenTokens(processedTokens[0]);
|
|
760
|
+
let collection = createTokenCollection(flatTokens);
|
|
761
|
+
collection = resolveTokens(collection);
|
|
762
|
+
for (const plugin of this.plugins) {
|
|
763
|
+
if (plugin.afterResolve) {
|
|
764
|
+
collection = plugin.afterResolve(collection);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
this.collection = collection;
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Load tokens from directory
|
|
772
|
+
*/
|
|
773
|
+
loadDirectory(dir, pattern = "**/*.json") {
|
|
774
|
+
const glob = __require("fast-glob");
|
|
775
|
+
const files = glob.sync(path.join(dir, pattern));
|
|
776
|
+
return this.load(files);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Add theme
|
|
780
|
+
*/
|
|
781
|
+
addTheme(name, overrideTokens) {
|
|
782
|
+
if (!this.collection) {
|
|
783
|
+
throw new Error("Load tokens first before adding themes");
|
|
784
|
+
}
|
|
785
|
+
const theme = createTheme(name, this.collection, overrideTokens);
|
|
786
|
+
this.themes.push(theme);
|
|
787
|
+
return this;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Generate output files
|
|
791
|
+
*/
|
|
792
|
+
async generate() {
|
|
793
|
+
if (!this.collection) {
|
|
794
|
+
throw new Error("No tokens loaded");
|
|
795
|
+
}
|
|
796
|
+
fs2.mkdirSync(this.options.outputDir, { recursive: true });
|
|
797
|
+
for (const format of this.options.formats) {
|
|
798
|
+
await this.generateFormat(format);
|
|
799
|
+
}
|
|
800
|
+
if (this.themes.length > 0) {
|
|
801
|
+
await this.generateThemeFiles();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Generate single format
|
|
806
|
+
*/
|
|
807
|
+
async generateFormat(format) {
|
|
808
|
+
if (!this.collection) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
for (const plugin of this.plugins) {
|
|
812
|
+
if (plugin.customFormat) {
|
|
813
|
+
const output = plugin.customFormat(this.collection, this.options);
|
|
814
|
+
if (output) {
|
|
815
|
+
const ext = this.getExtension(format);
|
|
816
|
+
fs2.writeFileSync(
|
|
817
|
+
path.join(this.options.outputDir, `tokens.${plugin.name}.${ext}`),
|
|
818
|
+
output
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (this.options.separateFiles) {
|
|
824
|
+
await this.generateSeparateFiles(format);
|
|
825
|
+
} else {
|
|
826
|
+
await this.generateSingleFile(format);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Generate single output file
|
|
831
|
+
*/
|
|
832
|
+
async generateSingleFile(format) {
|
|
833
|
+
if (!this.collection) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
const content = this.transform(this.collection, format);
|
|
837
|
+
const filename = `tokens.${this.getExtension(format)}`;
|
|
838
|
+
const filepath = path.join(this.options.outputDir, filename);
|
|
839
|
+
fs2.writeFileSync(filepath, content);
|
|
840
|
+
console.log(`Generated: ${filepath}`);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Generate separate files by category
|
|
844
|
+
*/
|
|
845
|
+
async generateSeparateFiles(format) {
|
|
846
|
+
if (!this.collection) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const categories = getCategories(this.collection);
|
|
850
|
+
const ext = this.getExtension(format);
|
|
851
|
+
for (const category of categories) {
|
|
852
|
+
const tokens = this.collection.tokens.filter((t) => t.path[0] === category);
|
|
853
|
+
const catCollection = {
|
|
854
|
+
tokens,
|
|
855
|
+
byPath: new Map(tokens.map((t) => [t.path.join("."), t])),
|
|
856
|
+
byName: new Map(tokens.map((t) => [t.name, t]))
|
|
857
|
+
};
|
|
858
|
+
const content = this.transform(catCollection, format);
|
|
859
|
+
const filename = `${category}.${ext}`;
|
|
860
|
+
const filepath = path.join(this.options.outputDir, filename);
|
|
861
|
+
fs2.writeFileSync(filepath, content);
|
|
862
|
+
console.log(`Generated: ${filepath}`);
|
|
863
|
+
}
|
|
864
|
+
this.generateIndexFile(categories, format);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Generate index file for separated files
|
|
868
|
+
*/
|
|
869
|
+
generateIndexFile(categories, format) {
|
|
870
|
+
const ext = this.getExtension(format);
|
|
871
|
+
let content = "";
|
|
872
|
+
switch (format) {
|
|
873
|
+
case "css":
|
|
874
|
+
content = categories.map((c) => `@import './${c}.css';`).join("\n");
|
|
875
|
+
break;
|
|
876
|
+
case "scss":
|
|
877
|
+
content = categories.map((c) => `@import './${c}';`).join("\n");
|
|
878
|
+
break;
|
|
879
|
+
case "js":
|
|
880
|
+
case "ts":
|
|
881
|
+
content = categories.map((c) => `export * from './${c}';`).join("\n");
|
|
882
|
+
break;
|
|
883
|
+
default:
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const filepath = path.join(this.options.outputDir, `index.${ext}`);
|
|
887
|
+
fs2.writeFileSync(filepath, content);
|
|
888
|
+
console.log(`Generated: ${filepath}`);
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Transform collection to format
|
|
892
|
+
*/
|
|
893
|
+
transform(collection, format) {
|
|
894
|
+
const prefix = this.options.prefix ?? "nice";
|
|
895
|
+
switch (format) {
|
|
896
|
+
case "css":
|
|
897
|
+
return transformToCss(collection, prefix);
|
|
898
|
+
case "scss":
|
|
899
|
+
return transformToScss(collection, prefix);
|
|
900
|
+
case "js":
|
|
901
|
+
return transformToJs(collection);
|
|
902
|
+
case "ts":
|
|
903
|
+
return transformToTs(collection);
|
|
904
|
+
case "json":
|
|
905
|
+
return JSON.stringify(this.buildNestedObject(collection), null, 2);
|
|
906
|
+
case "tailwind":
|
|
907
|
+
return transformToTailwind(collection);
|
|
908
|
+
default:
|
|
909
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Build nested object from flat tokens
|
|
914
|
+
*/
|
|
915
|
+
buildNestedObject(collection) {
|
|
916
|
+
const result = {};
|
|
917
|
+
for (const token of collection.tokens) {
|
|
918
|
+
let current = result;
|
|
919
|
+
for (let i = 0; i < token.path.length - 1; i++) {
|
|
920
|
+
const key = token.path[i];
|
|
921
|
+
if (!(key in current)) {
|
|
922
|
+
current[key] = {};
|
|
923
|
+
}
|
|
924
|
+
current = current[key];
|
|
925
|
+
}
|
|
926
|
+
current[token.path[token.path.length - 1]] = token.resolvedValue;
|
|
927
|
+
}
|
|
928
|
+
return result;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Get file extension for format
|
|
932
|
+
*/
|
|
933
|
+
getExtension(format) {
|
|
934
|
+
switch (format) {
|
|
935
|
+
case "css":
|
|
936
|
+
return "css";
|
|
937
|
+
case "scss":
|
|
938
|
+
return "scss";
|
|
939
|
+
case "js":
|
|
940
|
+
return "js";
|
|
941
|
+
case "ts":
|
|
942
|
+
return "ts";
|
|
943
|
+
case "json":
|
|
944
|
+
return "json";
|
|
945
|
+
case "tailwind":
|
|
946
|
+
return "tailwind.js";
|
|
947
|
+
default:
|
|
948
|
+
return "txt";
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Generate theme-related files
|
|
953
|
+
*/
|
|
954
|
+
async generateThemeFiles() {
|
|
955
|
+
const prefix = this.options.prefix ?? "nice";
|
|
956
|
+
const themesCss = generateThemesCss(this.themes, prefix);
|
|
957
|
+
fs2.writeFileSync(path.join(this.options.outputDir, "themes.css"), themesCss);
|
|
958
|
+
console.log(`Generated: themes.css`);
|
|
959
|
+
const switcherJs = generateThemeSwitcher();
|
|
960
|
+
fs2.writeFileSync(path.join(this.options.outputDir, "theme-switcher.js"), switcherJs);
|
|
961
|
+
console.log(`Generated: theme-switcher.js`);
|
|
962
|
+
const reactContext = generateReactThemeContext();
|
|
963
|
+
fs2.writeFileSync(path.join(this.options.outputDir, "ThemeContext.tsx"), reactContext);
|
|
964
|
+
console.log(`Generated: ThemeContext.tsx`);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Get generated token collection
|
|
968
|
+
*/
|
|
969
|
+
getCollection() {
|
|
970
|
+
return this.collection;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Get generated themes
|
|
974
|
+
*/
|
|
975
|
+
getThemes() {
|
|
976
|
+
return this.themes;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
function createGenerator(options) {
|
|
980
|
+
return new TokenGenerator(options);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/cli/index.ts
|
|
79
984
|
var pkg = require_package();
|
|
80
985
|
var program = new Command();
|
|
81
986
|
program.name("nice-tokens").description("Generate design tokens in various formats").version(pkg.version);
|
|
@@ -86,8 +991,8 @@ program.command("build").description("Build design tokens from JSON source files
|
|
|
86
991
|
).option("-p, --prefix <prefix>", "CSS variable prefix", "nice").option("--separate", "Generate separate files by category").option("--no-dark", "Exclude dark mode tokens").option("--no-components", "Exclude component tokens").option("--watch", "Watch for changes and rebuild").action(async (options) => {
|
|
87
992
|
const formats = options.formats.split(",").map((f) => f.trim());
|
|
88
993
|
console.log("Nice2Dev Design Tokens Generator\n");
|
|
89
|
-
console.log(`Input: ${
|
|
90
|
-
console.log(`Output: ${
|
|
994
|
+
console.log(`Input: ${path2.resolve(options.input)}`);
|
|
995
|
+
console.log(`Output: ${path2.resolve(options.output)}`);
|
|
91
996
|
console.log(`Formats: ${formats.join(", ")}`);
|
|
92
997
|
console.log("");
|
|
93
998
|
try {
|
|
@@ -104,13 +1009,13 @@ program.command("build").description("Build design tokens from JSON source files
|
|
|
104
1009
|
console.log("\nBuild complete!");
|
|
105
1010
|
if (options.watch) {
|
|
106
1011
|
console.log("\nWatching for changes...");
|
|
107
|
-
const chokidar = await import("
|
|
108
|
-
const watcher = chokidar.watch(
|
|
1012
|
+
const chokidar = await import("./esm-LPVAZ4IA.js");
|
|
1013
|
+
const watcher = chokidar.watch(path2.join(options.input, "**/*.json"), {
|
|
109
1014
|
ignoreInitial: true
|
|
110
1015
|
});
|
|
111
1016
|
watcher.on("change", async (file) => {
|
|
112
1017
|
console.log(`
|
|
113
|
-
File changed: ${
|
|
1018
|
+
File changed: ${path2.basename(file)}`);
|
|
114
1019
|
try {
|
|
115
1020
|
const gen = createGenerator({
|
|
116
1021
|
outputDir: options.output,
|
|
@@ -134,11 +1039,11 @@ File changed: ${path.basename(file)}`);
|
|
|
134
1039
|
}
|
|
135
1040
|
});
|
|
136
1041
|
program.command("init").description("Initialize a new design tokens project").option("-d, --dir <dir>", "Directory to initialize", ".").action(async (options) => {
|
|
137
|
-
const dir =
|
|
138
|
-
const tokensDir =
|
|
1042
|
+
const dir = path2.resolve(options.dir);
|
|
1043
|
+
const tokensDir = path2.join(dir, "tokens");
|
|
139
1044
|
console.log("Initializing Nice2Dev Design Tokens\n");
|
|
140
|
-
|
|
141
|
-
|
|
1045
|
+
fs3.mkdirSync(tokensDir, { recursive: true });
|
|
1046
|
+
fs3.mkdirSync(path2.join(dir, "dist"), { recursive: true });
|
|
142
1047
|
const colorsJson = {
|
|
143
1048
|
color: {
|
|
144
1049
|
$type: "color",
|
|
@@ -159,7 +1064,7 @@ program.command("init").description("Initialize a new design tokens project").op
|
|
|
159
1064
|
}
|
|
160
1065
|
}
|
|
161
1066
|
};
|
|
162
|
-
|
|
1067
|
+
fs3.writeFileSync(path2.join(tokensDir, "colors.json"), JSON.stringify(colorsJson, null, 2));
|
|
163
1068
|
const typographyJson = {
|
|
164
1069
|
typography: {
|
|
165
1070
|
fontFamily: {
|
|
@@ -177,8 +1082,8 @@ program.command("init").description("Initialize a new design tokens project").op
|
|
|
177
1082
|
}
|
|
178
1083
|
}
|
|
179
1084
|
};
|
|
180
|
-
|
|
181
|
-
|
|
1085
|
+
fs3.writeFileSync(
|
|
1086
|
+
path2.join(tokensDir, "typography.json"),
|
|
182
1087
|
JSON.stringify(typographyJson, null, 2)
|
|
183
1088
|
);
|
|
184
1089
|
const spacingJson = {
|
|
@@ -192,7 +1097,7 @@ program.command("init").description("Initialize a new design tokens project").op
|
|
|
192
1097
|
"8": { $value: "2rem" }
|
|
193
1098
|
}
|
|
194
1099
|
};
|
|
195
|
-
|
|
1100
|
+
fs3.writeFileSync(path2.join(tokensDir, "spacing.json"), JSON.stringify(spacingJson, null, 2));
|
|
196
1101
|
console.log("Created token files in ./tokens/");
|
|
197
1102
|
console.log("");
|
|
198
1103
|
console.log("Next steps:");
|
|
@@ -204,7 +1109,7 @@ program.command("validate").description("Validate design token files").argument(
|
|
|
204
1109
|
const glob = __require("fast-glob");
|
|
205
1110
|
let tokenFiles = files;
|
|
206
1111
|
if (options.dir) {
|
|
207
|
-
tokenFiles = await glob(
|
|
1112
|
+
tokenFiles = await glob(path2.join(options.dir, "**/*.json"));
|
|
208
1113
|
}
|
|
209
1114
|
if (tokenFiles.length === 0) {
|
|
210
1115
|
console.log("No token files specified");
|
|
@@ -214,11 +1119,11 @@ program.command("validate").description("Validate design token files").argument(
|
|
|
214
1119
|
let hasErrors = false;
|
|
215
1120
|
for (const file of tokenFiles) {
|
|
216
1121
|
try {
|
|
217
|
-
const content =
|
|
1122
|
+
const content = fs3.readFileSync(file, "utf-8");
|
|
218
1123
|
JSON.parse(content);
|
|
219
|
-
console.log(`OK ${
|
|
1124
|
+
console.log(`OK ${path2.basename(file)}`);
|
|
220
1125
|
} catch (err) {
|
|
221
|
-
console.log(`FAIL ${
|
|
1126
|
+
console.log(`FAIL ${path2.basename(file)}: ${err.message}`);
|
|
222
1127
|
hasErrors = true;
|
|
223
1128
|
}
|
|
224
1129
|
}
|
|
@@ -236,9 +1141,9 @@ program.command("convert").description("Convert between token formats").argument
|
|
|
236
1141
|
try {
|
|
237
1142
|
const generator = createGenerator({
|
|
238
1143
|
formats: [format],
|
|
239
|
-
outputDir:
|
|
1144
|
+
outputDir: path2.dirname(output)
|
|
240
1145
|
});
|
|
241
|
-
const inputStat =
|
|
1146
|
+
const inputStat = fs3.statSync(input);
|
|
242
1147
|
if (inputStat.isDirectory()) {
|
|
243
1148
|
generator.loadDirectory(input);
|
|
244
1149
|
} else {
|
|
@@ -253,9 +1158,9 @@ program.command("convert").description("Convert between token formats").argument
|
|
|
253
1158
|
});
|
|
254
1159
|
program.command("info").description("Show information about token files").argument("<dir>", "Directory containing token files").action(async (dir) => {
|
|
255
1160
|
const glob = __require("fast-glob");
|
|
256
|
-
const files = await glob(
|
|
1161
|
+
const files = await glob(path2.join(dir, "**/*.json"));
|
|
257
1162
|
console.log("Token Information\n");
|
|
258
|
-
console.log(`Directory: ${
|
|
1163
|
+
console.log(`Directory: ${path2.resolve(dir)}`);
|
|
259
1164
|
console.log(`Files: ${files.length}`);
|
|
260
1165
|
console.log("");
|
|
261
1166
|
const generator = createGenerator();
|
|
@@ -286,8 +1191,8 @@ program.command("diff").description("Compare two token files or directories").ar
|
|
|
286
1191
|
console.log("Comparing tokens\n");
|
|
287
1192
|
const generatorA = createGenerator();
|
|
288
1193
|
const generatorB = createGenerator();
|
|
289
|
-
const statA =
|
|
290
|
-
const statB =
|
|
1194
|
+
const statA = fs3.statSync(source);
|
|
1195
|
+
const statB = fs3.statSync(target);
|
|
291
1196
|
if (statA.isDirectory()) {
|
|
292
1197
|
generatorA.loadDirectory(source);
|
|
293
1198
|
} else {
|
|
@@ -309,21 +1214,21 @@ program.command("diff").description("Compare two token files or directories").ar
|
|
|
309
1214
|
const added = [];
|
|
310
1215
|
const removed = [];
|
|
311
1216
|
const changed = [];
|
|
312
|
-
for (const
|
|
313
|
-
if (!pathsA.has(
|
|
314
|
-
added.push(
|
|
1217
|
+
for (const path3 of pathsB) {
|
|
1218
|
+
if (!pathsA.has(path3)) {
|
|
1219
|
+
added.push(path3);
|
|
315
1220
|
}
|
|
316
1221
|
}
|
|
317
|
-
for (const
|
|
318
|
-
if (!pathsB.has(
|
|
319
|
-
removed.push(
|
|
1222
|
+
for (const path3 of pathsA) {
|
|
1223
|
+
if (!pathsB.has(path3)) {
|
|
1224
|
+
removed.push(path3);
|
|
320
1225
|
}
|
|
321
1226
|
}
|
|
322
1227
|
for (const tokenA of collectionA.tokens) {
|
|
323
|
-
const
|
|
324
|
-
const tokenB = collectionB.byPath.get(
|
|
1228
|
+
const path3 = tokenA.path.join(".");
|
|
1229
|
+
const tokenB = collectionB.byPath.get(path3);
|
|
325
1230
|
if (tokenB && tokenA.resolvedValue !== tokenB.resolvedValue) {
|
|
326
|
-
changed.push(`${
|
|
1231
|
+
changed.push(`${path3}: ${tokenA.resolvedValue} \u2192 ${tokenB.resolvedValue}`);
|
|
327
1232
|
}
|
|
328
1233
|
}
|
|
329
1234
|
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|